"use strict";
/*
 * Copyright 2025 Matti Airas
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateState = generateState;
exports.generateNonce = generateNonce;
exports.createAuthState = createAuthState;
exports.validateState = validateState;
exports.encryptState = encryptState;
exports.decryptState = decryptState;
const crypto_1 = require("crypto");
const types_1 = require("./types");
const pkce_1 = require("./pkce");
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
/**
 * Generate a cryptographically random state parameter
 * Used for CSRF protection in OAuth2 flows
 */
function generateState() {
    return (0, crypto_1.randomBytes)(32).toString('base64url');
}
/**
 * Generate a cryptographically random nonce
 * Used for ID token replay protection
 */
function generateNonce() {
    return (0, crypto_1.randomBytes)(32).toString('base64url');
}
/**
 * Create a complete auth state object for the OIDC flow
 */
function createAuthState(redirectUri, originalUrl) {
    return {
        state: generateState(),
        codeVerifier: (0, pkce_1.generateCodeVerifier)(),
        nonce: generateNonce(),
        redirectUri,
        originalUrl,
        createdAt: Date.now()
    };
}
/**
 * Validate that the returned state matches the stored state
 * and that the state hasn't expired
 */
function validateState(returnedState, storedState) {
    if (returnedState !== storedState.state) {
        throw new types_1.OIDCError('State mismatch - possible CSRF attack', 'INVALID_STATE');
    }
    const age = Date.now() - storedState.createdAt;
    if (age > types_1.STATE_MAX_AGE_MS) {
        throw new types_1.OIDCError(`State expired (${Math.round(age / 1000)}s old, max ${types_1.STATE_MAX_AGE_MS / 1000}s)`, 'STATE_EXPIRED');
    }
}
/**
 * Derive a 32-byte encryption key from the secret key
 */
function deriveKey(secretKey) {
    return (0, crypto_1.createHash)('sha256').update(secretKey).digest();
}
/**
 * Encrypt the auth state for storage in a cookie
 * Uses AES-256-GCM for authenticated encryption
 */
function encryptState(state, secretKey) {
    const key = deriveKey(secretKey);
    const iv = (0, crypto_1.randomBytes)(IV_LENGTH);
    const cipher = (0, crypto_1.createCipheriv)(ALGORITHM, key, iv);
    const plaintext = JSON.stringify(state);
    const encrypted = Buffer.concat([
        cipher.update(plaintext, 'utf8'),
        cipher.final()
    ]);
    const authTag = cipher.getAuthTag();
    // Combine IV + authTag + ciphertext
    const combined = Buffer.concat([iv, authTag, encrypted]);
    return combined.toString('base64url');
}
/**
 * Decrypt the auth state from a cookie
 */
function decryptState(encryptedState, secretKey) {
    const key = deriveKey(secretKey);
    const combined = Buffer.from(encryptedState, 'base64url');
    // Extract IV, authTag, and ciphertext
    const iv = combined.subarray(0, IV_LENGTH);
    const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
    const ciphertext = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
    const decipher = (0, crypto_1.createDecipheriv)(ALGORITHM, key, iv);
    decipher.setAuthTag(authTag);
    const decrypted = Buffer.concat([
        decipher.update(ciphertext),
        decipher.final()
    ]);
    return JSON.parse(decrypted.toString('utf8'));
}
//# sourceMappingURL=state.js.map