"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 __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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLocalRequest = isLocalRequest;
exports.verifyToken = verifyToken;
exports.generateLocalToken = generateLocalToken;
exports.authMiddleware = authMiddleware;
const jwt = __importStar(require("jsonwebtoken"));
const config_1 = require("./config");
const logger_1 = require("./logger");
const log = (0, logger_1.modLogger)('auth');
/**
 * Populate ALS context with the identified user so downstream log events
 * tagged via the shared mutable store pick it up (see request-log.ts I1 fix).
 * Safe noop outside ALS scope.
 */
function tagUserInAls(username) {
    const ctx = logger_1.als.getStore();
    if (ctx && !ctx.user)
        ctx.user = username;
}
/**
 * Check if request originates from localhost.
 * Uses socket.remoteAddress directly — not req.ip — so this is NOT affected by
 * X-Forwarded-For even if `trust proxy` is enabled. A reverse proxy terminating
 * on the same host will still show as 127.0.0.1, which is acceptable: operator
 * opted in to that deployment.
 */
function isLocalRequest(req) {
    const ip = req.socket.remoteAddress || '';
    return (ip === '127.0.0.1' ||
        ip === '::1' ||
        ip === '::ffff:127.0.0.1');
}
function verifyToken(token) {
    try {
        const config = (0, config_1.getConfig)();
        const decoded = jwt.verify(token, config.jwtSecret);
        // Reject non-user JWTs (e.g. plugin-session tokens) that happen to share
        // the same signing secret — prevents type confusion between credential
        // classes (codex review #2a).
        if (decoded.typ && decoded.typ !== 'user')
            return null;
        if (typeof decoded.username !== 'string')
            return null;
        return { username: decoded.username };
    }
    catch {
        return null;
    }
}
/** Generate a JWT for local access (no credentials needed).
 * Requires config.json — callers (currently only `/api/auth/local-token`) must handle the throw.
 * Previously fell back to a constant-secret token; removed because any attacker who can coerce
 * the verifier into the same fallback path would forge admin tokens.
 */
function generateLocalToken() {
    const config = (0, config_1.getConfig)();
    return jwt.sign({ username: config.username }, config.jwtSecret, { expiresIn: '30d' });
}
let _firstRunWarned = false;
function authMiddleware(req, res, next) {
    // Local access: auto-authenticate without token
    if (isLocalRequest(req)) {
        try {
            const config = (0, config_1.getConfig)();
            req.user = { username: config.username };
        }
        catch {
            // Config not yet created (e.g. first launch without `ccweb setup`).
            // Treat localhost as admin — use the sentinel '__local_admin__' which
            // isAdminUser() recognises when config.json is absent.
            // IMPORTANT: this path bypasses JWT entirely. Log loudly once so operators
            // running ccweb behind a reverse proxy (where every request may appear
            // loopback after proxy termination) notice they should complete setup.
            if (!_firstRunWarned) {
                log.warn('no config.json — localhost treated as admin via __local_admin__ sentinel. Run `ccweb setup`; until then all localhost bypasses auth.');
                _firstRunWarned = true;
            }
            req.user = { username: '__local_admin__' };
        }
        tagUserInAls(req.user.username);
        next();
        return;
    }
    // Remote access: require Bearer token (header or query param for <img>/<audio> etc.)
    const authHeader = req.headers['authorization'];
    const queryToken = req.query['token'];
    const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : queryToken;
    if (!token) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
    }
    const user = verifyToken(token);
    if (!user) {
        res.status(401).json({ error: 'Invalid or expired token' });
        return;
    }
    req.user = user;
    tagUserInAls(user.username);
    next();
}
//# sourceMappingURL=auth.js.map