// src/lib/auth/providers/jwt.ts
import * as jose from "jose";
import { logger } from "../../utils/logger.js";
import { AuthError } from "../errors.js";
import { BaseAuthProvider } from "./BaseAuthProvider.js";
/**
 * Generic JWT Provider
 *
 * Supports validation of JWT tokens using either symmetric secrets (HS256/384/512)
 * or asymmetric keys (RS256/384/512, ES256/384/512).
 *
 * Features:
 * - Symmetric secret validation (HMAC)
 * - Asymmetric key validation (RSA, ECDSA)
 * - Configurable algorithms
 * - Issuer and audience validation
 * - Token signing (symmetric keys only)
 * - Session management (inherited from BaseAuthProvider)
 *
 * @example
 * ```typescript
 * // Symmetric key (HMAC)
 * const jwtProvider = new JWTProvider({
 *   type: "jwt",
 *   secret: "your-256-bit-secret",
 *   algorithms: ["HS256"],
 *   issuer: "your-app",
 *   audience: "your-api",
 * });
 *
 * // Asymmetric key (RSA/ECDSA)
 * const jwtProvider = new JWTProvider({
 *   type: "jwt",
 *   publicKey: "-----BEGIN PUBLIC KEY-----...",
 *   algorithms: ["RS256"],
 *   issuer: "your-app",
 * });
 *
 * const result = await jwtProvider.authenticateToken(token);
 * ```
 */
export class JWTProvider extends BaseAuthProvider {
    type = "jwt";
    secret;
    publicKey;
    algorithms;
    issuer;
    audience;
    keyObject = null;
    constructor(config) {
        super(config);
        if (!config.secret && !config.publicKey) {
            throw AuthError.create("CONFIGURATION_ERROR", "JWT requires either secret (for HMAC) or publicKey (for RSA/ECDSA)", {
                details: { provider: "jwt", missingFields: ["secret", "publicKey"] },
            });
        }
        this.secret = config.secret;
        this.publicKey = config.publicKey;
        this.algorithms =
            config.algorithms ?? (config.secret ? ["HS256"] : ["RS256"]);
        this.issuer = config.issuer;
        this.audience = config.audience;
    }
    /**
     * Initialize the key for verification
     */
    async initialize() {
        try {
            if (this.secret) {
                // Symmetric key (HMAC)
                this.keyObject = new TextEncoder().encode(this.secret);
                logger.debug("JWT provider initialized with symmetric secret");
            }
            else if (this.publicKey) {
                // Asymmetric key (RSA/ECDSA)
                this.keyObject = await jose.importSPKI(this.publicKey, this.algorithms[0]);
                logger.debug("JWT provider initialized with asymmetric public key");
            }
        }
        catch (error) {
            throw AuthError.create("PROVIDER_INIT_FAILED", `Failed to initialize JWT key: ${error instanceof Error ? error.message : String(error)}`, {
                details: { provider: "jwt" },
                cause: error instanceof Error ? error : undefined,
            });
        }
    }
    /**
     * Validate JWT token
     */
    async authenticateToken(token, _context) {
        if (!this.keyObject) {
            await this.initialize();
        }
        try {
            const keyObject = this.keyObject;
            if (!keyObject) {
                throw AuthError.create("PROVIDER_INIT_FAILED", "JWT verification key was not initialized", { details: { provider: "jwt" } });
            }
            const verifyOptions = {};
            if (this.algorithms.length > 0) {
                verifyOptions.algorithms = this
                    .algorithms;
            }
            if (this.issuer) {
                verifyOptions.issuer = this.issuer;
            }
            if (this.audience) {
                verifyOptions.audience = this.audience;
            }
            const { payload } = await jose.jwtVerify(token, keyObject, verifyOptions);
            // Reject tokens without a non-empty sub claim
            if (!payload.sub) {
                return {
                    valid: false,
                    error: "JWT is missing required 'sub' claim: cannot identify user",
                };
            }
            // Extract user from standard JWT claims
            const user = {
                id: payload.sub,
                email: payload.email,
                name: payload.name,
                picture: payload.picture,
                emailVerified: payload.email_verified,
                roles: payload.roles ?? [],
                permissions: payload.permissions ??
                    payload.scope?.split(" ") ??
                    [],
                metadata: {
                    iss: payload.iss,
                    aud: payload.aud,
                    jti: payload.jti,
                },
            };
            return {
                valid: true,
                payload: payload,
                user,
                expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
                tokenType: "jwt",
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            logger.warn("JWT validation failed:", message);
            // Provide specific error messages
            let errorDetail = message;
            if (message.includes("JWTExpired")) {
                errorDetail = "Token has expired";
            }
            else if (message.includes("signature")) {
                errorDetail = "Invalid token signature";
            }
            else if (message.includes("audience")) {
                errorDetail = "Invalid token audience";
            }
            else if (message.includes("issuer")) {
                errorDetail = "Invalid token issuer";
            }
            return {
                valid: false,
                error: errorDetail,
            };
        }
    }
    /**
     * Create a signed JWT token
     *
     * Useful for issuing tokens from this provider.
     */
    async signToken(payload, options) {
        if (!this.secret) {
            throw AuthError.create("CONFIGURATION_ERROR", "Token signing requires a secret (symmetric key)", { details: { provider: "jwt" } });
        }
        if (!this.keyObject) {
            await this.initialize();
        }
        const jwt = new jose.SignJWT(payload)
            .setProtectedHeader({ alg: this.algorithms[0] })
            .setIssuedAt();
        if (this.issuer) {
            jwt.setIssuer(this.issuer);
        }
        if (this.audience) {
            jwt.setAudience(this.audience);
        }
        if (options?.expiresIn) {
            jwt.setExpirationTime(options.expiresIn);
        }
        return jwt.sign(this.keyObject);
    }
    /**
     * Health check
     */
    async healthCheck() {
        try {
            // Verify the key is properly initialized
            if (!this.keyObject) {
                await this.initialize();
            }
            return {
                healthy: this.keyObject !== null,
                providerConnected: true,
                sessionStorageHealthy: true,
            };
        }
        catch (error) {
            return {
                healthy: false,
                providerConnected: false,
                sessionStorageHealthy: true,
                error: error instanceof Error ? error.message : String(error),
            };
        }
    }
}
//# sourceMappingURL=jwt.js.map