JwtManager.java

/*
 * Copyright © 2023-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */

package org.ctan.site.services.account;

import java.util.Date;
import java.util.UUID;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.JWTVerifier;

import lombok.NonNull;

/**
 * The class <code>JwtManager</code> contains static utility methods for dealing
 * with JWTs.
 *
 * @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
 */
public class JwtManager {

    /**
     * The field <code>SUBJECT</code> contains the expected subject of the JWT.
     */
    private static final String SUBJECT = "ctan.org site";

    /**
     * The field <code>ISSUER</code> contains the issuer field of the JWT.
     */
    private static final String ISSUER = "CTAN.org";

    /**
     * The field <code>SECRET</code> contains the dynamically generated random
     * secret.
     */
    private static final String SECRET = Double.toHexString(Math.random());

    /**
     * The field <code>ALGORITHM</code> contains the algorithm to sign the JWT.
     */
    private static final Algorithm ALGORITHM = Algorithm.HMAC512(SECRET);

    /**
     * The field <code>VERIFIER</code> contains the verifier. It is visible
     * outside for testing purpose.
     */
    protected static final JWTVerifier VERIFIER_AUTH = JWT.require(ALGORITHM)
        .withIssuer(ISSUER)
        .withSubject(SUBJECT)
        .withClaim("for", "A")
        .build();

    /**
     * The field <code>VERIFIER</code> contains the verifier. It is visible
     * outside for testing purpose.
     */
    protected static final JWTVerifier VERIFIER_REFRESH = JWT.require(ALGORITHM)
        .withIssuer(ISSUER)
        .withSubject(SUBJECT)
        .withClaim("for", "R")
        .build();

    /**
     * Create a JWT with standard options set.
     *
     * @param account the user id;
     * @param ttl time to live in ms
     * @return a new JWT
     */
    private static String create(String account, String type, long ttl) {

        return JWT.create()
            .withIssuer(ISSUER)
            .withSubject(SUBJECT)
            .withClaim("account", account)
            .withClaim("for", type)
            .withIssuedAt(new Date())
            .withExpiresAt(new Date(System.currentTimeMillis() + ttl))
            .withJWTId(UUID.randomUUID().toString())
            .sign(ALGORITHM);
    }

    /**
     * Create a JWT with standard options set for authentication.
     *
     * @param account the user id;
     * @return a new JWT
     */
    public static String createAuth(@NonNull String account) {

        return create(account, "A", 1000L * 60 * 60 * 24);
    }

    /**
     * Create a JWT with standard options set for refresh.
     *
     * @param account the user id;
     * @return a new JWT
     */
    public static String createRefresh(@NonNull String account) {

        return create(account, "R", 1000L * 60 * 60 * 24 * 100);
    }

    /**
     * The method <code>verifyAuth</code> provides means to verify a JWT and
     * return the account.
     *
     * @param token the JWT
     * @return the verified user id or {@code null}
     */
    public static String verifyAuth(String token) {

        try {
            return VERIFIER_AUTH.verify(token)
                .getClaim("account")
                .asString();
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    /**
     * The method <code>verify</code> provides means to verify a JWT and return
     * the account.
     *
     * @param token the JWT
     * @return the verified user id or {@code null}
     */
    public static String verifyRefresh(String token) {

        try {
            return VERIFIER_REFRESH.verify(token)
                .getClaim("account")
                .asString();
        } catch (JWTVerificationException e) {
            return null;
        }
    }

    /**
     * This is the constructor for <code>JwtManager</code>.
     *
     * <p>
     * Attention: It can not be used since this class exposes static methods
     * only!
     * </p>
     */
    private JwtManager() {

    }
}