"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppleAuthProvider = void 0;
const apple_auth_1 = __importDefault(require("apple-auth"));
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const crypto_1 = require("crypto");
const jwt = __importStar(require("jsonwebtoken"));
const base_auth_provider_1 = require("./base-auth.provider");
const auth_constants_1 = require("../../auth.constants");
const user_entity_1 = require("../../user/entities/user.entity");
const identity_entity_1 = require("../../user/entities/identity.entity");
const jwt_service_1 = require("../services/jwt.service");
const APPLE_ISSUER = 'https://appleid.apple.com';
const DEFAULT_APPLE_JWKS_URL = 'https://appleid.apple.com/auth/keys';
const JWKS_TTL_MS = 10 * 60 * 1000;
let AppleAuthProvider = class AppleAuthProvider extends base_auth_provider_1.BaseAuthProvider {
    userRepository;
    authIdentityRepository;
    jwtService;
    providerName = auth_constants_1.APPLE_AUTH_PROVIDER;
    skipMfa = true;
    appleConfig;
    appleAuth;
    jwksCache;
    constructor(userRepository, authIdentityRepository, jwtService) {
        super(userRepository, authIdentityRepository);
        this.userRepository = userRepository;
        this.authIdentityRepository = authIdentityRepository;
        this.jwtService = jwtService;
        this.appleConfig = this.options.apple;
        this.enabled = Boolean(this.options.apple);
        if (this.enabled &&
            this.appleConfig?.privateKey &&
            this.appleConfig?.teamId &&
            this.appleConfig?.keyId) {
            this.appleAuth = new apple_auth_1.default({
                scope: 'email name',
                redirect_uri: this.appleConfig.redirectUri,
                team_id: this.appleConfig.teamId,
                key_id: this.appleConfig.keyId,
                client_id: this.appleConfig.clientId,
            }, this.appleConfig.privateKey, this.appleConfig.privateKeyMethod || 'text');
        }
    }
    async validate(credentials) {
        const token = credentials?.token;
        if (!token) {
            throw new common_1.UnauthorizedException({
                message: 'Missing Apple token',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        const decoded = this.safeDecode(token);
        const isIdentityToken = decoded?.payload?.iss === APPLE_ISSUER;
        if (isIdentityToken) {
            return this.verifyIdentityToken(token, decoded.header, credentials);
        }
        return this.exchangeAuthorizationCode(token, credentials);
    }
    getRequiredFields() {
        return ['token'];
    }
    async verifyIdentityToken(token, header, credentials) {
        const audiences = this.appleConfig?.audiences?.length
            ? this.appleConfig.audiences
            : [this.appleConfig?.clientId].filter(Boolean);
        if (audiences.length === 0) {
            throw new common_1.UnauthorizedException({
                message: 'Apple audiences are not configured',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        const publicKey = await this.getApplePublicKey(header?.kid);
        let payload;
        try {
            payload = jwt.verify(token, publicKey, {
                algorithms: ['RS256'],
                audience: audiences,
                issuer: APPLE_ISSUER,
            });
        }
        catch (error) {
            throw new common_1.UnauthorizedException({
                message: 'Invalid Apple identity token',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        if (credentials.nonce && payload.nonce !== credentials.nonce) {
            throw new common_1.UnauthorizedException({
                message: 'Apple nonce mismatch',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        const emailVerified = payload.email_verified === true || payload.email_verified === 'true';
        return {
            userId: payload.sub,
            email: payload.email || '',
            metadata: {
                ...payload,
                name: credentials.name,
                emailVerified,
            },
        };
    }
    async getApplePublicKey(kid) {
        const jwk = await this.findJwk(kid);
        if (!jwk) {
            throw new common_1.UnauthorizedException({
                message: 'Apple signing key not found',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        return (0, crypto_1.createPublicKey)({ key: jwk, format: 'jwk' });
    }
    async findJwk(kid) {
        const fresh = this.jwksCache && Date.now() - this.jwksCache.fetchedAt < JWKS_TTL_MS;
        if (!fresh) {
            await this.refreshJwks();
        }
        let jwk = this.jwksCache?.keys.find((k) => k.kid === kid);
        if (!jwk && fresh) {
            await this.refreshJwks();
            jwk = this.jwksCache?.keys.find((k) => k.kid === kid);
        }
        return jwk;
    }
    async refreshJwks() {
        const url = this.appleConfig?.jwksUrl || DEFAULT_APPLE_JWKS_URL;
        let res;
        try {
            res = await fetch(url);
        }
        catch (error) {
            throw new common_1.UnauthorizedException({
                message: 'Could not reach Apple to verify the token',
                code: 'OAUTH_PROVIDER_ERROR',
            });
        }
        if (!res.ok) {
            throw new common_1.UnauthorizedException({
                message: 'Apple key endpoint returned an error',
                code: 'OAUTH_PROVIDER_ERROR',
            });
        }
        const json = await res.json();
        this.jwksCache = { keys: json.keys ?? [], fetchedAt: Date.now() };
    }
    async exchangeAuthorizationCode(code, credentials) {
        if (!this.appleAuth) {
            throw new common_1.UnauthorizedException({
                message: 'Apple authorization-code exchange is not configured (set apple.teamId/keyId/privateKey), or pass a native identityToken instead.',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
        try {
            const response = await this.appleAuth.accessToken(code);
            const user = this.jwtService.decodeToken(response.id_token);
            return {
                userId: user.sub,
                email: user.email || '',
                metadata: { ...user, name: credentials.name },
            };
        }
        catch (error) {
            throw new common_1.UnauthorizedException({
                message: 'Invalid Apple token',
                code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
            });
        }
    }
    safeDecode(token) {
        try {
            return jwt.decode(token, { complete: true });
        }
        catch {
            return null;
        }
    }
};
exports.AppleAuthProvider = AppleAuthProvider;
exports.AppleAuthProvider = AppleAuthProvider = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(user_entity_1.NestAuthUser)),
    __param(1, (0, typeorm_1.InjectRepository)(identity_entity_1.NestAuthIdentity)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        jwt_service_1.JwtService])
], AppleAuthProvider);
//# sourceMappingURL=apple-auth.provider.js.map