package com.sismics.docs.rest.resource;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.*;
import com.sismics.docs.core.dao.criteria.GroupCriteria;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.GroupDto;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.event.PasswordLostEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.*;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.RoutingUtil;
import com.sismics.docs.core.util.authentication.AuthenticationUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.security.UserPrincipal;
import com.sismics.util.JsonUtil;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sismics.util.totp.GoogleAuthenticator;
import com.sismics.util.totp.GoogleAuthenticatorKey;
import org.apache.commons.lang.StringUtils;

import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.servlet.http.Cookie;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * User REST resources.
 *
 * @author jtremeaux
 */
@Path("/user")
public class UserResource extends BaseResource {
    /**
     * Creates a new user.
     *
     * @api {put} /user Register a new user
     * @apiName PutUser
     * @apiGroup User
     * @apiParam {String{3..50}} username Username
     * @apiParam {String{8..50}} password Password
     * @apiParam {String{1..100}} email E-mail
     * @apiParam {Number} storage_quota Storage quota (in bytes)
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied
     * @apiError (client) ValidationError Validation error
     * @apiError (server) PrivateKeyError Error while generating a private key
     * @apiError (client) AlreadyExistingUsername Login already used
     * @apiError (server) UnknownError Unknown server error
     * @apiPermission admin
     * @apiVersion 1.5.0
     *
     * @param username User's username
     * @param password Password
     * @param email E-Mail
     * @return Response
     */
    @PUT
    public Response register(
        @FormParam("username") String username,
        @FormParam("password") String password,
        @FormParam("email") String email,
        @FormParam("storage_quota") String storageQuotaStr) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }
        checkBaseFunction(BaseFunction.ADMIN);

        // Validate the input data
        username = ValidationUtil.validateLength(username, "username", 3, 50);
        ValidationUtil.validateAlphanumeric(username, "username");
        password = ValidationUtil.validateLength(password, "password", 8, 50);
        email = ValidationUtil.validateLength(email, "email", 1, 100);
        Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
        ValidationUtil.validateEmail(email, "email");

        // Create the user
        User user = new User();
        user.setRoleId(Constants.DEFAULT_USER_ROLE);
        user.setUsername(username);
        user.setPassword(password);
        user.setEmail(email);
        user.setStorageQuota(storageQuota);
        user.setOnboarding(true);

        // Create the user
        UserDao userDao = new UserDao();
        try {
            userDao.create(user, principal.getId());
        } catch (Exception e) {
            if ("AlreadyExistingUsername".equals(e.getMessage())) {
                throw new ClientException("AlreadyExistingUsername", "Login already used", e);
            } else {
                throw new ServerException("UnknownError", "Unknown server error", e);
            }
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Updates the current user informations.
     *
     * @api {post} /user Update the current user
     * @apiName PostUser
     * @apiGroup User
     * @apiParam {String{8..50}} password Password
     * @apiParam {String{1..100}} email E-mail
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or connected as guest
     * @apiError (client) ValidationError Validation error
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @param password Password
     * @param email E-Mail
     * @return Response
     */
    @POST
    public Response update(
        @FormParam("password") String password,
        @FormParam("email") String email) {
        if (!authenticate() || principal.isGuest()) {
            throw new ForbiddenClientException();
        }

        // Validate the input data
        password = ValidationUtil.validateLength(password, "password", 8, 50, true);
        email = ValidationUtil.validateLength(email, "email", 1, 100, true);

        // Update the user
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(principal.getName());
        if (email != null) {
            user.setEmail(email);
        }
        user = userDao.update(user, principal.getId());

        // Change the password
        if (StringUtils.isNotBlank(password)) {
            user.setPassword(password);
            userDao.updatePassword(user, principal.getId());
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Updates a user informations.
     *
     * @api {post} /user/:username Update a user
     * @apiName PostUserUsername
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiParam {String{8..50}} password Password
     * @apiParam {String{1..100}} email E-mail
     * @apiParam {Number} storage_quota Storage quota (in bytes)
     * @apiParam {Boolean} disabled Disabled status
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied
     * @apiError (client) ValidationError Validation error
     * @apiError (client) UserNotFound User not found
     * @apiPermission admin
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @param password Password
     * @param email E-Mail
     * @return Response
     */
    @POST
    @Path("{username: [a-zA-Z0-9_]+}")
    public Response update(
        @PathParam("username") String username,
        @FormParam("password") String password,
        @FormParam("email") String email,
        @FormParam("storage_quota") String storageQuotaStr,
        @FormParam("disabled") Boolean disabled) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }
        checkBaseFunction(BaseFunction.ADMIN);

        // Validate the input data
        password = ValidationUtil.validateLength(password, "password", 8, 50, true);
        email = ValidationUtil.validateLength(email, "email", 1, 100, true);

        // Check if the user exists
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(username);
        if (user == null) {
            throw new ClientException("UserNotFound", "The user does not exist");
        }

        // Update the user
        if (email != null) {
            user.setEmail(email);
        }
        if (StringUtils.isNotBlank(storageQuotaStr)) {
            Long storageQuota = ValidationUtil.validateLong(storageQuotaStr, "storage_quota");
            user.setStorageQuota(storageQuota);
        }
        if (disabled != null) {
            // Cannot disable the admin user or the guest user
            RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
            Set<String> baseFunctionSet = userBaseFuction.findByRoleId(Sets.newHashSet(user.getRoleId()));
            if (Constants.GUEST_USER_ID.equals(username) || baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
                disabled = false;
            }

            if (disabled && user.getDisableDate() == null) {
                // Recording the disabled date
                user.setDisableDate(new Date());
            } else if (!disabled && user.getDisableDate() != null) {
                // Emptying the disabled date
                user.setDisableDate(null);
            }
        }
        user = userDao.update(user, principal.getId());

        // Change the password
        if (StringUtils.isNotBlank(password)) {
            user.setPassword(password);
            userDao.updatePassword(user, principal.getId());
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Checks if a username is available.
     * Search only on active accounts.
     *
     * @api {get} /user/check_username Check username availability
     * @apiName GetUserCheckUsername
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiSuccess {String} status Status OK or KO
     * @apiPermission none
     * @apiVersion 1.5.0
     *
     * @param username Username to check
     * @return Response
     */
    @GET
    @Path("check_username")
    public Response checkUsername(
        @QueryParam("username") String username) {
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(username);

        JsonObjectBuilder response = Json.createObjectBuilder();
        if (user != null) {
            response.add("status", "ko")
                    .add("message", "Username already registered");
        } else {
            response.add("status", "ok");
        }

        return Response.ok().entity(response.build()).build();
    }

    /**
     * This resource is used to authenticate the user and create a user session.
     * The "session" is only used to identify the user, no other data is stored in the session.
     *
     * @api {post} /user/login Login a user
     * @apiDescription This resource creates an authentication token and gives it back in a cookie.
     * All authenticated resources will check this cookie to find the user currently logged in.
     * @apiName PostUserLogin
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiParam {String} password Password (optional for guest login)
     * @apiParam {String} code TOTP validation code
     * @apiParam {Boolean} remember If true, create a long lasted token
     * @apiSuccess {String} auth_token A cookie named auth_token containing the token ID
     * @apiError (client) ForbiddenError Access denied
     * @apiError (client) ValidationCodeRequired A TOTP validation code is required
     * @apiPermission none
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @param password Password
     * @param longLasted Remember the user next time, create a long lasted session.
     * @return Response
     */
    @POST
    @Path("login")
    public Response login(
        @FormParam("username") String username,
        @FormParam("password") String password,
        @FormParam("code") String validationCodeStr,
        @FormParam("remember") boolean longLasted) {
        // Validate the input data
        username = StringUtils.strip(username);
        password = StringUtils.strip(password);

        // Get the user
        UserDao userDao = new UserDao();
        User user = null;
        if (Constants.GUEST_USER_ID.equals(username)) {
            if (ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN)) {
                // Login as guest
                user = userDao.getActiveByUsername(Constants.GUEST_USER_ID);
            }
        } else {
            // Login as a normal user
            user = AuthenticationUtil.authenticate(username, password);
        }
        if (user == null) {
            throw new ForbiddenClientException();
        }

        // Two factor authentication
        if (user.getTotpKey() != null) {
            // If TOTP is enabled, ask a validation code
            if (Strings.isNullOrEmpty(validationCodeStr)) {
                throw new ClientException("ValidationCodeRequired", "An OTP validation code is required");
            }

            // Check the validation code
            int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
            GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
            if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
                throw new ForbiddenClientException();
            }
        }

        // Get the remote IP
        String ip = request.getHeader("x-forwarded-for");
        if (Strings.isNullOrEmpty(ip)) {
            ip = request.getRemoteAddr();
        }

        // Create a new session token
        AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
        AuthenticationToken authenticationToken = new AuthenticationToken()
            .setUserId(user.getId())
            .setLongLasted(longLasted)
            .setIp(ip)
            .setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
        String token = authenticationTokenDao.create(authenticationToken);

        // Cleanup old session tokens
        authenticationTokenDao.deleteOldSessionToken(user.getId());

        JsonObjectBuilder response = Json.createObjectBuilder();
        int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
        NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, token, "/", null, null, maxAge, false);
        return Response.ok().entity(response.build()).cookie(cookie).build();
    }

    /**
     * Logs out the user and deletes the active session.
     *
     * @api {post} /user/logout Logout a user
     * @apiDescription This resource deletes the authentication token created by POST /user/login and removes the cookie.
     * @apiName PostUserLogout
     * @apiGroup User
     * @apiSuccess {String} auth_token An expired cookie named auth_token containing no value
     * @apiError (client) ForbiddenError Access denied
     * @apiError (server) AuthenticationTokenError Error deleting the authentication token
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @POST
    @Path("logout")
    public Response logout() {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        // Get the value of the session token
        String authToken = getAuthToken();

        AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
        AuthenticationToken authenticationToken = null;
        if (authToken != null) {
            authenticationToken = authenticationTokenDao.get(authToken);
        }

        // No token : nothing to do
        if (authenticationToken == null) {
            throw new ForbiddenClientException();
        }

        // Deletes the server token
        try {
            authenticationTokenDao.delete(authToken);
        } catch (Exception e) {
            throw new ServerException("AuthenticationTokenError", "Error deleting the authentication token: " + authToken, e);
        }

        // Deletes the client token in the HTTP response
        JsonObjectBuilder response = Json.createObjectBuilder();
        NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, null, "/", null, 1, null, -1, new Date(1), false, false);
        return Response.ok().entity(response.build()).cookie(cookie).build();
    }

    /**
     * Deletes the current user.
     *
     * @api {delete} /user Delete the current user
     * @apiDescription All associated entities will be deleted as well.
     * @apiName DeleteUser
     * @apiGroup User
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or the user cannot be deleted
     * @apiError (client) UserUsedInRouteModel The user is used in a route model
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @DELETE
    public Response delete() {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        // Ensure that the admin or guest users are not deleted
        if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) {
            throw new ClientException("ForbiddenError", "This user cannot be deleted");
        }

        // Check that this user is not used in any workflow
        String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, principal.getName());
        if (routeModelName != null) {
            throw new ClientException("UserUsedInRouteModel", routeModelName);
        }

        // Find linked data
        DocumentDao documentDao = new DocumentDao();
        List<Document> documentList = documentDao.findByUserId(principal.getId());
        FileDao fileDao = new FileDao();
        List<File> fileList = fileDao.findByUserId(principal.getId());

        // Delete the user
        UserDao userDao = new UserDao();
        userDao.delete(principal.getName(), principal.getId());

        // Raise deleted events for documents
        for (Document document : documentList) {
            DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
            documentDeletedAsyncEvent.setUserId(principal.getId());
            documentDeletedAsyncEvent.setDocumentId(document.getId());
            ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
        }

        // Raise deleted events for files (don't bother sending document updated event)
        for (File file : fileList) {
            FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
            fileDeletedAsyncEvent.setUserId(principal.getId());
            fileDeletedAsyncEvent.setFileId(file.getId());
            ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Deletes a user.
     *
     * @api {delete} /user/:username Delete a user
     * @apiDescription All associated entities will be deleted as well.
     * @apiName DeleteUserUsername
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or the user cannot be deleted
     * @apiError (client) UserNotFound The user does not exist
     * @apiError (client) UserUsedInRouteModel The user is used in a route model
     * @apiPermission admin
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @return Response
     */
    @DELETE
    @Path("{username: [a-zA-Z0-9_]+}")
    public Response delete(@PathParam("username") String username) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }
        checkBaseFunction(BaseFunction.ADMIN);

        // Cannot delete the guest user
        if (Constants.GUEST_USER_ID.equals(username)) {
            throw new ClientException("ForbiddenError", "The guest user cannot be deleted");
        }

        // Check that the user exists
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(username);
        if (user == null) {
            throw new ClientException("UserNotFound", "The user does not exist");
        }

        // Ensure that the admin user is not deleted
        RoleBaseFunctionDao roleBaseFunctionDao = new RoleBaseFunctionDao();
        Set<String> baseFunctionSet = roleBaseFunctionDao.findByRoleId(Sets.newHashSet(user.getRoleId()));
        if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
            throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
        }

        // Check that this user is not used in any workflow
        String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, username);
        if (routeModelName != null) {
            throw new ClientException("UserUsedInRouteModel", routeModelName);
        }

        // Find linked data
        DocumentDao documentDao = new DocumentDao();
        List<Document> documentList = documentDao.findByUserId(user.getId());
        FileDao fileDao = new FileDao();
        List<File> fileList = fileDao.findByUserId(user.getId());

        // Delete the user
        userDao.delete(user.getUsername(), principal.getId());

        // Raise deleted events for documents
        for (Document document : documentList) {
            DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
            documentDeletedAsyncEvent.setUserId(principal.getId());
            documentDeletedAsyncEvent.setDocumentId(document.getId());
            ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
        }

        // Raise deleted events for files (don't bother sending document updated event)
        for (File file : fileList) {
            FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
            fileDeletedAsyncEvent.setUserId(principal.getId());
            fileDeletedAsyncEvent.setFileId(file.getId());
            ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Disable time-based one-time password for a specific user.
     *
     * @api {post} /user/:username/disable_totp Disable TOTP authentication for a specific user
     * @apiName PostUserUsernameDisableTotp
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or connected as guest
     * @apiError (client) ValidationError Validation error
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @return Response
     */
    @POST
    @Path("{username: [a-zA-Z0-9_]+}/disable_totp")
    public Response disableTotpUsername(@PathParam("username") String username) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }
        checkBaseFunction(BaseFunction.ADMIN);

        // Get the user
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(username);
        if (user == null) {
            throw new ForbiddenClientException();
        }

        // Remove the TOTP key
        user.setTotpKey(null);
        userDao.update(user, principal.getId());

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Returns the information about the connected user.
     *
     * @api {get} /user Get the current user
     * @apiName GetUser
     * @apiGroup User
     * @apiSuccess {Boolean} anonymous True if no user is connected
     * @apiSuccess {Boolean} is_default_password True if the admin has the default password
     * @apiSuccess {Boolean} onboarding True if the UI needs to display the onboarding
     * @apiSuccess {String} username Username
     * @apiSuccess {String} email E-mail
     * @apiSuccess {Number} storage_quota Storage quota (in bytes)
     * @apiSuccess {Number} storage_current Quota used (in bytes)
     * @apiSuccess {Boolean} totp_enabled True if TOTP authentication is enabled
     * @apiSuccess {String[]} base_functions Base functions
     * @apiSuccess {String[]} groups Groups
     * @apiPermission none
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @GET
    public Response info() {
        JsonObjectBuilder response = Json.createObjectBuilder();
        if (!authenticate()) {
            response.add("anonymous", true);

            // Check if admin has the default password
            UserDao userDao = new UserDao();
            User adminUser = userDao.getById("admin");
            if (adminUser != null && adminUser.getDeleteDate() == null) {
                response.add("is_default_password", Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword()));
            }
        } else {
            // Update the last connection date
            String authToken = getAuthToken();
            AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
            authenticationTokenDao.updateLastConnectionDate(authToken);

            // Build the response
            response.add("anonymous", false);
            UserDao userDao = new UserDao();
            GroupDao groupDao = new GroupDao();
            User user = userDao.getById(principal.getId());
            List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
                    .setUserId(user.getId())
                    .setRecursive(true), null);

            response.add("username", user.getUsername())
                    .add("email", user.getEmail())
                    .add("storage_quota", user.getStorageQuota())
                    .add("storage_current", user.getStorageCurrent())
                    .add("totp_enabled", user.getTotpKey() != null)
                    .add("onboarding", user.isOnboarding());

            // Base functions
            JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
            for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
                baseFunctions.add(baseFunction);
            }

            // Groups
            JsonArrayBuilder groups = Json.createArrayBuilder();
            for (GroupDto groupDto : groupDtoList) {
                groups.add(groupDto.getName());
            }

            response.add("base_functions", baseFunctions)
                    .add("groups", groups)
                    .add("is_default_password", hasBaseFunction(BaseFunction.ADMIN) && Constants.DEFAULT_ADMIN_PASSWORD.equals(user.getPassword()));
        }

        return Response.ok().entity(response.build()).build();
    }

    /**
     * Returns the information about a user.
     *
     * @api {get} /user/:username Get a user
     * @apiName GetUserUsername
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiSuccess {String} username Username
     * @apiSuccess {String} email E-mail
     * @apiSuccess {Boolean} totp_enabled True if TOTP authentication is enabled
     * @apiSuccess {Number} storage_quota Storage quota (in bytes)
     * @apiSuccess {Number} storage_current Quota used (in bytes)
     * @apiSuccess {String[]} groups Groups
     * @apiSuccess {Boolean} disabled True if the user is disabled
     * @apiError (client) ForbiddenError Access denied
     * @apiError (client) UserNotFound The user does not exist
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @return Response
     */
    @GET
    @Path("{username: [a-zA-Z0-9_]+}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response view(@PathParam("username") String username) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(username);
        if (user == null) {
            throw new ClientException("UserNotFound", "The user does not exist");
        }

        // Groups
        GroupDao groupDao = new GroupDao();
        List<GroupDto> groupDtoList = groupDao.findByCriteria(
                new GroupCriteria().setUserId(user.getId()),
                new SortCriteria(1, true));
        JsonArrayBuilder groups = Json.createArrayBuilder();
        for (GroupDto groupDto : groupDtoList) {
            groups.add(groupDto.getName());
        }

        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("username", user.getUsername())
                .add("groups", groups)
                .add("email", user.getEmail())
                .add("totp_enabled", user.getTotpKey() != null)
                .add("storage_quota", user.getStorageQuota())
                .add("storage_current", user.getStorageCurrent())
                .add("disabled", user.getDisableDate() != null);
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Returns all active users.
     *
     * @api {get} /user/list Get users
     * @apiName GetUserList
     * @apiGroup User
     * @apiParam {Number} sort_column Column index to sort on
     * @apiParam {Boolean} asc If true, sort in ascending order
     * @apiParam {String} group Filter on this group
     * @apiSuccess {Object[]} users List of users
     * @apiSuccess {String} users.id ID
     * @apiSuccess {String} users.username Username
     * @apiSuccess {String} users.email E-mail
     * @apiSuccess {Boolean} users.totp_enabled True if TOTP authentication is enabled
     * @apiSuccess {Number} users.storage_quota Storage quota (in bytes)
     * @apiSuccess {Number} users.storage_current Quota used (in bytes)
     * @apiSuccess {Number} users.create_date Create date (timestamp)
     * @apiSuccess {Number} users.disabled True if the user is disabled
     * @apiError (client) ForbiddenError Access denied
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @param sortColumn Sort index
     * @param asc If true, ascending sorting, else descending
     * @param groupName Only return users from this group
     * @return Response
     */
    @GET
    @Path("list")
    public Response list(
            @QueryParam("sort_column") Integer sortColumn,
            @QueryParam("asc") Boolean asc,
            @QueryParam("group") String groupName) {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        JsonArrayBuilder users = Json.createArrayBuilder();
        SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);

        // Validate the group
        String groupId = null;
        if (!Strings.isNullOrEmpty(groupName)) {
            GroupDao groupDao = new GroupDao();
            Group group = groupDao.getActiveByName(groupName);
            if (group != null) {
                groupId = group.getId();
            }
        }

        UserDao userDao = new UserDao();
        List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setGroupId(groupId), sortCriteria);
        for (UserDto userDto : userDtoList) {
            users.add(Json.createObjectBuilder()
                    .add("id", userDto.getId())
                    .add("username", userDto.getUsername())
                    .add("email", userDto.getEmail())
                    .add("totp_enabled", userDto.getTotpKey() != null)
                    .add("storage_quota", userDto.getStorageQuota())
                    .add("storage_current", userDto.getStorageCurrent())
                    .add("create_date", userDto.getCreateTimestamp())
                    .add("disabled", userDto.getDisableTimestamp() != null));
        }

        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("users", users);
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Returns all active sessions.
     *
     * @api {get} /user/session Get active sessions
     * @apiDescription This resource lists all active token which can be used to log in to the current user account.
     * @apiName GetUserSession
     * @apiGroup User
     * @apiSuccess {Object[]} sessions List of sessions
     * @apiSuccess {Number} create_date Create date of this token
     * @apiSuccess {String} ip IP used to log in
     * @apiSuccess {String} user_agent User agent used to log in
     * @apiSuccess {Number} last_connection_date Last connection date (timestamp)
     * @apiSuccess {Boolean} current If true, this token is the current one
     * @apiError (client) ForbiddenError Access denied
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @GET
    @Path("session")
    public Response session() {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        // Get the value of the session token
        String authToken = getAuthToken();

        JsonArrayBuilder sessions = Json.createArrayBuilder();

        // The guest user cannot see other sessions
        if (!principal.isGuest()) {
            AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
            for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
                JsonObjectBuilder session = Json.createObjectBuilder()
                        .add("create_date", authenticationToken.getCreationDate().getTime())
                        .add("ip", JsonUtil.nullable(authenticationToken.getIp()))
                        .add("user_agent", JsonUtil.nullable(authenticationToken.getUserAgent()));
                if (authenticationToken.getLastConnectionDate() != null) {
                    session.add("last_connection_date", authenticationToken.getLastConnectionDate().getTime());
                }
                session.add("current", authenticationToken.getId().equals(authToken));
                sessions.add(session);
            }
        }

        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("sessions", sessions);
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Deletes all active sessions except the one used for this request.
     *
     * @api {delete} /user/session Delete all sessions
     * @apiDescription This resource deletes all active token linked to this account, except the one used to make this request.
     * @apiName DeleteUserSession
     * @apiGroup User
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or connected as guest
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @DELETE
    @Path("session")
    public Response deleteSession() {
        if (!authenticate() || principal.isGuest()) {
            throw new ForbiddenClientException();
        }

        // Get the value of the session token
        String authToken = getAuthToken();

        // Remove other tokens
        AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
        authenticationTokenDao.deleteByUserId(principal.getId(), authToken);

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Mark the onboarding experience as passed.
     *
     * @api {post} /user/onboarded Mark the onboarding experience as passed
     * @apiDescription Once the onboarding experience has been passed by the user, this resource prevent it from being displayed again.
     * @apiName PostUserOnboarded
     * @apiGroup User
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied
     * @apiPermission user
     * @apiVersion 1.7.0
     *
     * @return Response
     */
    @POST
    @Path("onboarded")
    public Response onboarded() {
        if (!authenticate()) {
            throw new ForbiddenClientException();
        }

        // Save it
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(principal.getName());
        user.setOnboarding(false);
        userDao.updateOnboarding(user);

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Enable time-based one-time password.
     *
     * @api {post} /user/enable_totp Enable TOTP authentication
     * @apiDescription This resource enables the Time-based One-time Password authentication.
     * All following login will need a validation code generated from the given secret seed.
     * @apiName PostUserEnableTotp
     * @apiGroup User
     * @apiSuccess {String} secret Secret TOTP seed to initiate the algorithm
     * @apiError (client) ForbiddenError Access denied or connected as guest
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @return Response
     */
    @POST
    @Path("enable_totp")
    public Response enableTotp() {
        if (!authenticate() || principal.isGuest()) {
            throw new ForbiddenClientException();
        }

        // Create a new TOTP key
        GoogleAuthenticator gAuth = new GoogleAuthenticator();
        final GoogleAuthenticatorKey key = gAuth.createCredentials();

        // Save it
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(principal.getName());
        user.setTotpKey(key.getKey());
        userDao.update(user, principal.getId());

        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("secret", key.getKey());
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Test time-based one-time password.
     *
     * @api {post} /user/test_totp Test TOTP authentication
     * @apiDescription Test a TOTP validation code.
     * @apiName PostUserTestTotp
     * @apiParam {String} code TOTP validation code
     * @apiGroup User
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError The validation code is not valid or access denied
     * @apiPermission user
     * @apiVersion 1.6.0
     *
     * @return Response
     */
    @POST
    @Path("test_totp")
    public Response testTotp(@FormParam("code") String validationCodeStr) {
        if (!authenticate() || principal.isGuest()) {
            throw new ForbiddenClientException();
        }

        // Get the user
        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(principal.getName());

        // Test the validation code
        if (user.getTotpKey() != null) {
            int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
            GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
            if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
                throw new ForbiddenClientException();
            }
        }

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Disable time-based one-time password for the current user.
     *
     * @api {post} /user/disable_totp Disable TOTP authentication for the current user
     * @apiName PostUserDisableTotp
     * @apiGroup User
     * @apiParam {String{1..100}} password Password
     * @apiSuccess {String} status Status OK
     * @apiError (client) ForbiddenError Access denied or connected as guest
     * @apiError (client) ValidationError Validation error
     * @apiPermission user
     * @apiVersion 1.5.0
     *
     * @param password Password
     * @return Response
     */
    @POST
    @Path("disable_totp")
    public Response disableTotp(@FormParam("password") String password) {
        if (!authenticate() || principal.isGuest()) {
            throw new ForbiddenClientException();
        }

        // Validate the input data
        password = ValidationUtil.validateLength(password, "password", 1, 100, false);

        // Check the password and get the user
        UserDao userDao = new UserDao();
        User user = userDao.authenticate(principal.getName(), password);
        if (user == null) {
            throw new ForbiddenClientException();
        }

        // Remove the TOTP key
        user.setTotpKey(null);
        userDao.update(user, principal.getId());

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Create a key to reset a password and send it by email.
     *
     * @api {post} /user/password_lost Create a key to reset a password and send it by email
     * @apiName PostUserPasswordLost
     * @apiGroup User
     * @apiParam {String} username Username
     * @apiSuccess {String} status Status OK
     * @apiError (client) UserNotFound The user is not found
     * @apiError (client) ValidationError Validation error
     * @apiPermission none
     * @apiVersion 1.5.0
     *
     * @param username Username
     * @return Response
     */
    @POST
    @Path("password_lost")
    @Produces(MediaType.APPLICATION_JSON)
    public Response passwordLost(@FormParam("username") String username) {
        authenticate();

        // Validate input data
        ValidationUtil.validateStringNotBlank("username", username);

        // Check for user existence
        UserDao userDao = new UserDao();
        List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setUserName(username), null);
        if (userDtoList.isEmpty()) {
            throw new ClientException("UserNotFound", "User not found: " + username);
        }
        UserDto user = userDtoList.get(0);

        // Create the password recovery key
        PasswordRecoveryDao passwordRecoveryDao = new PasswordRecoveryDao();
        PasswordRecovery passwordRecovery = new PasswordRecovery();
        passwordRecovery.setUsername(user.getUsername());
        passwordRecoveryDao.create(passwordRecovery);

        // Fire a password lost event
        PasswordLostEvent passwordLostEvent = new PasswordLostEvent();
        passwordLostEvent.setUser(user);
        passwordLostEvent.setPasswordRecovery(passwordRecovery);
        AppContext.getInstance().getMailEventBus().post(passwordLostEvent);

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Reset the user's password.
     *
     * @api {post} /user/password_reset Reset the user's password
     * @apiName PostUserPasswordReset
     * @apiGroup User
     * @apiParam {String} key Password recovery key
     * @apiParam {String} password New password
     * @apiSuccess {String} status Status OK
     * @apiError (client) KeyNotFound Password recovery key not found
     * @apiError (client) ValidationError Validation error
     * @apiPermission none
     * @apiVersion 1.5.0
     *
     * @param passwordResetKey Password reset key
     * @param password New password
     * @return Response
     */
    @POST
    @Path("password_reset")
    @Produces(MediaType.APPLICATION_JSON)
    public Response passwordReset(
            @FormParam("key") String passwordResetKey,
            @FormParam("password") String password) {
        authenticate();

        // Validate input data
        ValidationUtil.validateRequired("key", passwordResetKey);
        password = ValidationUtil.validateLength(password, "password", 8, 50, true);

        // Load the password recovery key
        PasswordRecoveryDao passwordRecoveryDao = new PasswordRecoveryDao();
        PasswordRecovery passwordRecovery = passwordRecoveryDao.getActiveById(passwordResetKey);
        if (passwordRecovery == null) {
            throw new ClientException("KeyNotFound", "Password recovery key not found");
        }

        UserDao userDao = new UserDao();
        User user = userDao.getActiveByUsername(passwordRecovery.getUsername());

        // Change the password
        user.setPassword(password);
        user = userDao.updatePassword(user, principal.getId());

        // Deletes password recovery requests
        passwordRecoveryDao.deleteActiveByLogin(user.getUsername());

        // Always return OK
        JsonObjectBuilder response = Json.createObjectBuilder()
                .add("status", "ok");
        return Response.ok().entity(response.build()).build();
    }

    /**
     * Returns the authentication token value.
     *
     * @return Token value
     */
    private String getAuthToken() {
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())
                        && !Strings.isNullOrEmpty(cookie.getValue())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
}


Q:

Replace substring in a string

My function finds in string hex notation (hexadecimal CSS colors) and replaces with the short notation.
For example: #000000 can be represented as #000
import re

def to_short_hex (string):
    match = re.findall(r'#[\w\d]{6}\b', string)

    for i in match:
        if not re.findall(r'#' + i[1] + '{6}', i):
            match.pop(match.index(i))

    for i in match:
        string = string.replace(i, i[:-3])

    return string;

to_short_hex('text #FFFFFF text #000000 #08088')

Out:
text #FFF text #000 #08088

Is there any way to optimize my code using list comprehension etc..?

A:

How about this? You can speed it up embedding is6hexdigit into to_short_hex, but I wanted it to be more readable.
hexdigits = "0123456789abcdef"

def is6hexdigit(sub):
    l = sub.lower()
    return (l[0] in hexdigits) and (l.count(l[0]) == 6)

def to_short_hex(may_have_hexes):
    replaced = ((sub[3:] if is6hexdigit(sub[:6]) else sub)
                        for sub in may_have_hexes.split('#'))
    return '#'.join(replaced)

A:

This is what re.sub is for! It's not a great idea to use a regex to find something and then do a further sequence of search-and-replace operations to change it. For one thing, it's easy to accidentally replace things you didn't mean to, and for another it does a lot of redundant work.
Also, you might want to shorten '#aaccee' to '#ace'. This example does that too:
def to_short_hex(s):
    def shorten_match(match):
        hex_string = match.group(0)
        if hex_string[1::2]==hex_string[2::2]:
            return '#'+hex_string[1::2]
        return hex_string
    return re.sub(r"#[\da-fA-F]{6}\b", shorten_match, s)

Explanation
re.sub can take a function to apply to each match. It receives the match object and returns the string to substitute at that point.
Slice notation allows you to apply a stride. hex_string[1::2] takes every second character from the string, starting at index 1 and running to the end of the string. hex_string[2::2] takes every second character from the string, starting at index 2 and running to the end. So for the string "#aaccee", we get "ace" and "ace", which match. For the string "#123456", we get "135" and "246", which don't match.


function f(obj) {
    const {c} = obj;
}


Q:

How to change text of an  once clicked?

I have this custom anchor button on my WordPress website:
 Buy Now

I'd like it to change its text on click event. Is it possible to do with javascript/php?

A:

You can change the innerHTML via JavaScript after the click
with jQuery:
$(".add-to-cart-button").click(function () {
  $(this).html('New Text');
});


304 Supplex with Grey Fur

Supplex Mad Bomber with Grey Fur and Rubber Colored Contrast Stitching. Quilted lining in crown with 100% Natural Rabbit fur trim. Snaplock at chin to hold flap up or down around chin for that extra warmth.


---
author:
- |
    \
    [ **Wilfried Buchmüller, Koichi Hamaguchi, Oleg Lebedev**]{}\
    [*Deutsches Elektronen-Synchrotron DESY, 22603 Hamburg, Germany*]{}\
    [**Michael Ratz**]{}\
    [*Physikalisches Institut der Universität Bonn,*]{}\
    [*Nussallee 12, 53115 Bonn, Germany*]{}
title: |
    [DESY 04-216]{}

    \
---

In string theory gauge and Yukawa couplings are dynamical quantities whose values are determined by expectation values of scalar fields (moduli). In perturbation theory their potential is flat, and their stabilization is a topic of central importance in string theory. Recently, it has been proposed that a complete stabilization of all moduli fields [@Kachru:2003aw] is possible using a combination of fluxes [@Giddings:2001yu] and non-perturbative effects such as D–brane instantons and gaugino condensation [@Derendinger:1985kk].

The moduli potentials receive important thermal corrections [@Buchmuller:2003is; @Buchmuller:2004xr]. These corrections destabilize the moduli at sufficiently high temperature, i.e. drive them to the ‘run–away’ minimum [@Dine:1985he] where the compact dimensions decompactify. In order to avoid this cosmological disaster the temperature in the early universe has to be smaller than a maximal temperature $T_{\rm crit}$.

In the following we discuss generality of our previous analysis [@Buchmuller:2004xr] on dilaton destabilization by thermal effects and apply it to the KKLT scenario [@Kachru:2003aw]. We find again that, generically, the maximal temperature is given by the scale of supersymmetry breaking, i.e., $T_{\rm crit} \sim \sqrt{m_{3/2}M_{\rm P}}$. Temperature effects in some field theoretical flux compactifications have recently been considered in [@Navarro:2004mm].\
[**Moduli destabilization at high temperature**]{}\
In thermal field theory, the free energy $F$ plays the role of an effective potential. For a Yang-Mills theory at high temperature $T$, the free energy has a perturbative expansion in terms of the gauge coupling $g$, $$F(g,T)\ =\ -{\pi^2 T^4 \over 24} \Bigl\{\alpha_0 + \alpha_2 g^2 + {\cal O}(g^3)
\Bigr\}\;.
\label{F}$$ The crucial point of our subsequent discussion is that $F$ increases with increasing $g$. For a supersymmetric SU($N_c$) gauge theory with $N_f$ matter multiplets, one has (cf. [@Kapusta:1989tk]) $\alpha_0=N_c^2 +2 N_c N_f -1$ and $$\alpha_2\ =\ -{3\over 8 \pi^2} (N_c^2-1)(N_c+3 N_f) < 0 \;.$$ To order ${\cal O}(g^2)$ the free energy is determined by one- and two-loop diagrams (cf. Fig. 1). The qualitative behaviour of the free energy, $\partial F/\partial g^2 > 0$, is also valid at higher–loop level and even non-perturbatively for $g = {\cal O}(1)$ [@Buchmuller:2004xr]. Furthermore, it holds for Abelian theories and for Yukawa couplings.

In string theory the gauge coupling is related to some modulus $\Phi$, $$g^2\ =\ {\kappa\over \Phi} \;,$$ where $\kappa$ is a constant. Since thermal effects increase the effective potential, they will drive the modulus towards larger values, corresponding to smaller couplings, and eventually destabilize the system. From Eq. (\[F\]) one obtains $$\label{vth}
 V_{\rm th}(\Phi,T) \equiv T^4  v_{\rm th}(\Phi)
 \ =\
 T^4\left(a_0 + a_2 {1\over \Phi} + \dots\right) \;,
\label{therm}$$ where $a_0$ and $a_2$ are constants, with $a_2>0$. Clearly, the minimum of this potential is at $\Phi \rightarrow \infty$.

The effective potential for $\Phi$ is the sum of the zero-temperature potential $V$ and the thermal correction, $$V_{\rm eff}(\Phi,T)
 \ =\
 V_{\rm th}(\Phi,T)+ V(\Phi) \;.$$ The supergravity potential $V(\Phi)$ for moduli is generated non–perturbatively. In known examples, it is related to supersymmetry breaking at least for some moduli. Such potentials allow one to stabilize the moduli at appropriate values, yet there is always the ‘run–away’ minimum at $\Phi \rightarrow \infty$, which is separated from the local minimum by a barrier related to SUSY breaking (Fig. \[fig:fluxfig\]).

[$\vcenter{\hbox{\includegraphics[scale=1]{TypicalModuliPotential.eps}}}$]{}

Clearly, when the temperature is high enough, the thermal potential (\[therm\]) will dominate and drive $\Phi$ to infinity. Since the size of the potential near the local minimum is ${\cal O}(m_{3/2}^2 M_{\rm P}^2)$, this occurs at the critical temperature $$T_{\rm crit}
 \ \sim\
 \sqrt{m_{3/2} M_{\rm P}} \;,
\label{max}$$ where $m_{3/2}$ is the gravitino mass and $M_{\rm P}=2.4\times 10^{18}$ GeV. An example of moduli destabilization by thermal corrections is shown in Fig. \[fig:ModuliDestablization\].

The critical temperature is defined by the appearance of a saddle point at some value $\Phi_{\rm crit}$: $$\begin{aligned}
 V'_{\rm eff}(\Phi_{\rm crit}, T_{\rm crit}) & =& 0 \;, \nonumber\\
 V''_{\rm eff}(\Phi_{\rm crit}, T_{\rm crit}) &= & 0 \;,\end{aligned}$$ where the prime indicates differentiation with respect to $\Phi$. Usually $V(\Phi)$ is a steep function, with exponential field dependence, while $V_{\rm th}(\Phi)$ varies rather slowly. Thus, $V_{\rm th}$ can be well approximated by a linear term in the region of interest and one obtains $$\begin{aligned}
 V''(\Phi_{\rm crit}) & = &0 \;, \nonumber\\
 T_{\rm crit} & = & \left(- {V'(\Phi_{\rm crit})\over
 v'_{\rm th}(\Phi_{\rm crit})}\right)^{1/4} \;.
\label{Tcr}\end{aligned}$$ Note that $V'(\Phi_{\rm crit})$ is the maximal slope of the supergravity potential.

It is important to realize that the moduli are not in thermal equilibrium. On the contrary, the moduli interaction rate $\Gamma_{\Phi} \sim T^3/M_{\rm P}^2$ is much smaller than the Hubble parameter $H \sim T^2/M_{\rm P}$. Hence, the moduli never reach thermal equilibrium. In particular, they do not attain a thermal mass[^1]. Rather, they behave as classical backgrounds. This is analogous to the behaviour of the gravitational metric in a thermal bath where the thermal expectation value of the energy momentum tensor drives the cosmological expansion. On the other hand, gauge and/or matter fields are in thermal equilibrium and contribute to the effective potential through loops. In this way, the gauge plasma exerts a force on the classical backgrounds and drives the moduli.

Let us summarize the conditions under which Eq. (\[max\]) for the critical temperature in the radiation dominated phase applies:

1.  The modulus $\Phi$ is related to some coupling $g$.

2.  The barrier separating a local minimum in $\Phi$ from the ‘run–away’ minimum is related to SUSY breaking.

3.  Gauge and matter fields which couple with strength $g$ are in thermal equilibrium.

A few comments are in order. First, $g$ is not necessarily the gauge coupling. For instance, the Yukawa couplings of twisted states are functions of the $T$–moduli, $Y\sim e^{-\alpha T}$. Then the diagram Fig. 1(c) generates an effective potential for these moduli, as long as the matter fields are in thermal equilibrium. Second, if $g$ is a gauge coupling, $\Phi$ is not necessarily the dilaton. It can, for instance, be a volume modulus, as it happens in D–brane models. Third, Eq. (\[max\]) holds for positive, negative or zero cosmological constant, as long as condition (ii) is satisfied.

[**Critical temperature in the KKLT scenario**]{}\
Background fluxes induce a potential which can stabilize the dilaton and the complex structure moduli at appropriate values [@Giddings:2001yu]. However, at least one Kähler modulus, corresponding to the overall volume, is not affected by the fluxes and remains undetermined. The volume can be stabilized by non–perturbative contributions to the superpotential such as D–brane instantons or gaugino condensation [@Kachru:2003aw]. The result is a supersymmetric AdS vacuum.

In order to lift the negative cosmological constant of the AdS vacuum to a small positive value, extra contributions are added to the scalar potential. These can be due to the presence of $\overline{\rm D3}$–branes, as in the original KKLT model or SUSY breaking D–terms [@Burgess:2003ic]. A common feature of these modifications is that the new vacuum is separated from the ‘run–away’ vacuum [@Dine:1985he] by a barrier whose height is given by the SUSY breaking scale (cf. Fig. 2).

In D–brane models, the gauge and matter fields of the standard model and the hidden sector can live on D3 or D7 branes [@Camara:2003ku]. At high temperature, thermalized gauge and matter fields of unbroken gauge groups on D7–branes will contribute to the scalar potential of the volume modulus. Depending on the D–brane model, the thermalized fields can belong to the standard model or to additional gauge groups for which no gaugino condensation occurs. This will lead to the existence of a maximal temperature beyond which the volume modulus is destabilized. In the following we calculate this critical temperature.

The superpotential and the Kähler potential for the volume modulus $\rho=\sigma+i\alpha$ are given by [@Kachru:2003aw] $$\begin{aligned}
 W & = & W_0 + A\, e^{-\,a\,\rho}\;,\nonumber \\
 K & = & -3\,\ln ( 2 \sigma )\;.\end{aligned}$$ Here $W_0$ is a constant induced by the fluxes, and $W-W_0$ represents a non–perturbative contribution to the superpotential due to D–brane instantons or gaugino condensation. In the latter case the exponent is given by the $\beta$-function of the corresponding gauge group, $4\pi a = 3/(2\beta)$. The effective 4D Yang–Mills gauge coupling on the D7–branes is related to $\sigma$ as $$\sigma
\ = \
{4\pi \over g^2} \;.$$ We can always choose the constant $W_0$ to be real and negative. $\alpha = {\rm Im}(\rho)$ is then stabilized at a value where the remaining potential for $\sigma={\rm Re}(\rho)$ reads $$V_0 \ =\ \frac{a\,A\,e^{-a\sigma}}{2\sigma^2}
 \left[\frac{1}{3}\,a\,A\,\sigma \,e^{-a\,\sigma}+W_0+A\,e^{-a\,\sigma}\right]
 \;,$$ with $A$ real and positive. $V_0$ has an AdS minimum. This potential is amended by a supersymmetry breaking term, $$V\ =\ V_0 + {D_n \over \sigma^n} \;.$$ Here $n=3$ corresponds to the KKLT potential [@Kachru:2003aw], which can be realized as a Fayet-Iliopoulos D–term term [@Burgess:2003ic], whereas $n=2$ occurs for the explicitly SUSY breaking contribution of an $\overline{\rm D3}$–brane [@Kachru:2003sx].

For certain choices of the parameters, one can obtain dS vacua with a small cosmological constant. An example is $W_0=-10^{-4}$, $A=1$, $a=0.1$, $D_3 = 3\times 10^{-9}$ ($D_2 = 2.6 \times 10^{-11}$); for $n=3$ this is the KKLT potential. Both cases, $n=3$ and $n=2$ are shown in Fig. \[fig:VKKLT\]. Numerically, they are almost indistinguishable. The local minimum is at $\sigma_{\rm min} \simeq 115$, corresponding to the gauge coupling $g=0.3$.

[$\vcenter{\hbox{\includegraphics[scale=1]{VKKLT.eps}}}$]{}

To calculate the critical temperature, we need the first and the second derivative of the potential. For $\sigma \gg 1$ and $a\sigma \gg 1$, $V'$ and $V''$ are well approximated by $$\begin{aligned}
V' &\simeq& -{a^3 A^2 \over 3 \sigma} e^{-2 a \sigma}
-{a^2 A W_0 \over 2 \sigma^2} e^{- a \sigma}\;, \\
V'' &\simeq& {2a^4 A^2 \over 3 \sigma} e^{-2 a \sigma}
+{a^3 A W_0 \over 2 \sigma^2} e^{- a \sigma} \;.\end{aligned}$$ Note that for the derivatives $D_n/\sigma^n$ is negligible. Setting $V''=0$, which defines $\sigma_{\rm cr}$, one finds the maximal slope of the potential: $$V_{\rm max}'
 \ \simeq \
 {a^3 A^2 \over 3 \sigma_{\rm cr}}
 e^{-2 a \sigma_{\rm cr}}\;.
\label{vp}$$

It is straightforward to relate the maximal slope to the scale of supersymmetry breaking. Since the cosmological constant is negligibly small, the gravitino mass is given by $$\begin{aligned}
 m_{3/2}^2 &=& e^K\ |W|^2 \Bigl\vert_{\rm min}\
 =\  - {1\over 3}\ V_0(\sigma_{\rm min}) \nonumber\\
 &\simeq& {a^2 A^2 \over 18 \sigma_{\rm min}} e^{-2 a \sigma_{\rm min}}\;.
\label{m32}\end{aligned}$$ For the KKLT parameters the gravitino mass is very large, $m_{3/2} \sim 10^{10}\ {\rm GeV}$. Note, that in the case of explicit SUSY breaking, $m_{3/2}$ is not the physical gravitino mass but just a parameter which controls the scale of SUSY breaking. Due to steepness of the potential, $\sigma_{\rm min}$ and $\sigma_{\rm cr}$ are very close to each other. Hence, combining (\[vp\]) and (\[m32\]), one obtains $$V_{\rm max}'\ \simeq\ 6 a\,  m_{3/2}^2 \;.$$

From Eqs. (\[vth\]) and (\[Tcr\]) one now obtains for the critical temperature: $$\label{tcrit}
T_{\rm crit}\ \simeq\ c\, \sqrt{m_{3/2}}\;,$$ with $c\simeq \vert 6a / v_{\rm th}'(\sigma_{\rm cr})\vert^{1/4}
={\cal O}(1)$. Note that this result holds both for $\delta V \propto 1/\sigma^3$ and $\delta V \propto 1/\sigma^2$. Numerically, for $m_{3/2} \sim 100$ GeV, the maximal temperature is $$T_{\rm crit}\ \sim \ 10^{10}~{\rm GeV} \;.$$

In the case of gaugino condensation one has $4\pi a = 3/(2\beta)$. Using $1/g^2 = 4\pi \sigma$, Eq. (\[tcrit\]) reads explicitly $$T_{\rm crit}
 \ \sim\ \sqrt{m_{3/2}} \left({2\over B}\right)^{1/4}
 \left({3\over \beta}\right)^{1/4} \left({1\over g^2}\right)^{3/8}\;,$$ where $B = (1/T^4) \partial F/\partial g(\Phi_{\rm crit})$ [@Buchmuller:2004xr]. It is instructive to compare this result with racetrack models, where the Kähler potential is also logarithmic and the superpotential is a sum of two exponential terms. In this case the critical temperature is given by [@Buchmuller:2004xr] $$T_{\rm crit}\ \sim\ \sqrt{m_{3/2}} \left({2\over B}\right)^{1/4}
 \left({3\over \beta}\right)^{3/4} \left({1\over g^2}\right)^{7/8}\;.$$ Clearly, both expressions are very similar. The different powers of $1/\beta$ and $1/g^2$ reflect the differences of the superpotential and Kähler potential in the two cases. For typical values, e.g. $1/g^2 \simeq 2$ and $\beta = 0.1$, the critical temperature in racetrack models is larger by about a factor five.\
[**Discussion**]{}\
The maximal temperature derived above places a bound on the temperature in the radiation dominated phase of the early universe. In particular, it bounds the reheating temperature, $$T_{\rm reheat}
 \ <\
 T_{\rm crit} \;.
\label{reh}$$ In addition, it places a bound on the maximal temperature in the preheating epoch [@kt], $$T_{\rm preheat}
 \ =\
 (T_{\rm reheat}^2 H_{\rm inf} M_{\rm P})^{1/4}
 \ < \
 T_{\rm crit} \;,$$ unless the inflaton coupling to $\Phi$ is strong enough to overcome the destabilizing thermal effects. This bound is usually more severe than (\[reh\]), yet it is also less universal.

We note that these bounds on the temperature of the early universe, unlike the gravitino bound, cannot be circumvented by late entropy production or other cosmological mechanisms. They are also independent of the ‘overshoot problem’ [@Brustein:1992nk], that during the cosmological evolution a modulus with a steep potential may not settle in a shallow minimum, but rather roll over the barrier to infinity. This problem can be solved by tuning the initial conditions or by implementing a mechanism to slow down the modulus (cf. [@Brustein:2004jp] and references therein). On the contrary, the constraint $T < T_{\rm crit}$ is unavoidable since there is no local minimum for $T > T_{\rm crit}$ and the modulus inevitably runs away to infinity.

While finalizing this work we received a related paper by Kallosh and Linde [@Kallosh:2004yh] which addresses moduli destabilization during inflation in KKLT models. The authors also present a model where, at special points in parameter space, the size of the barrier separating the physical vacuum from the unphysical one is unrelated to the gravitino mass. However, for generic parameters, the barrier is related to the scale of supersymmetry breaking and the analysis of the present paper applies.\
[**Acknowledgements.**]{} We would like to thank R. Brustein for correspondence, and J. Louis and H.-P. Nilles for discussions. One of us (M.R.) would like to thank the Aspen Center for Physics for support. This work was partially supported by the EU 6th Framework Program MRTN-CT-2004-503369 “Quest for Unification” and MRTN-CT-2004-005104 “ForcesUniverse”.

[99]{}

S. Kachru, R. Kallosh, A. Linde and S. P. Trivedi, Phys. Rev. D [**68**]{} (2003) 046005. S. B. Giddings, S. Kachru and J. Polchinski, Phys. Rev. D [**66**]{} (2002) 106006. J. P. Derendinger, L. E. Ibáñez and H. P. Nilles, Phys. Lett. B [**155**]{} (1985) 65; M. Dine, R. Rohm, N. Seiberg and E. Witten, Phys. Lett. B [**156**]{} (1985) 55. W. Buchmüller, K. Hamaguchi and M. Ratz, Phys. Lett. B [**574**]{} (2003) 156. W. Buchmüller, K. Hamaguchi, O. Lebedev and M. Ratz, Nucl. Phys. B [**699**]{} (2004) 292. M. Dine and N. Seiberg, Phys. Lett. B [**162**]{} (1985) 299.

I. Navarro and J. Santiago, JCAP [**0409**]{} (2004) 005.

J. I. Kapusta, “Finite Temperature Field Theory,” Cambridge, 1989.

P. Binetruy and M. K. Gaillard, Phys. Rev. D [**34**]{} (1986) 3069; K. A. Olive and M. A. Srednicki, Phys. Lett. B [**148**]{} (1984) 437.

C. P. Burgess, R. Kallosh and F. Quevedo, JHEP [**0310**]{} (2003) 056. P. G. Camara, L. E. Ibáñez and A. M. Uranga, Nucl. Phys. B [**689**]{} (2004) 195; hep-th/0408036; M. Graña, T. W. Grimm, H. Jockers and J. Louis, Nucl. Phys. B [**690**]{} (2004) 21; F. Marchesano and G. Shiu, hep-th/0408059; hep-th/0409132; H. Jockers and J. Louis, hep-th/0409098; D. Lüst, S. Reffert and S. Stieberger, hep-th/0410074.

S. Kachru, R. Kallosh, A. Linde, J. Maldacena, L. McAllister and S. P. Trivedi, JCAP [**0310**]{} (2003) 013.

E. Kolb, M. Turner, The Early Universe, Addison-Wesley, Redwood City, CA 1990.

R. Brustein and P. J. Steinhardt, Phys. Lett. B [**302**]{} (1993) 196. R. Brustein, S. P. de Alwis and P. Martens, hep-th/0408160; N. Kaloper, J. Rahmfeld and L. Sorbo, hep-th/0409226.

R. Kallosh and A. Linde, hep-th/0411011.

[^1]: This is one of the main differences between our results and those of Ref. [@Binetruy:1986ss].