"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const asyncHandler_1 = require("../utils/asyncHandler");
const db_1 = require("../db");
const schema_1 = require("../db/schema");
const drizzle_orm_1 = require("drizzle-orm");
const auth_1 = require("../utils/auth");
const errorHandler_1 = require("../middleware/errorHandler");
const zod_1 = require("zod");
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const crypto_1 = __importDefault(require("crypto"));
const router = (0, express_1.Router)();
// Rate limiting for auth endpoints
const authLimiter = (0, express_rate_limit_1.default)({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 10, // limit each IP to 10 requests per windowMs
    message: {
        error: {
            code: 'RATE_LIMITED',
            message: 'Too many attempts. Please try again later.',
        },
    },
    standardHeaders: true,
    legacyHeaders: false,
});
const loginLimiter = (0, express_rate_limit_1.default)({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // stricter limit for login
    message: {
        error: {
            code: 'RATE_LIMITED',
            message: 'Too many login attempts. Please try again later.',
        },
    },
    standardHeaders: true,
    legacyHeaders: false,
});
const passwordResetLimiter = (0, express_rate_limit_1.default)({
    windowMs: 60 * 60 * 1000, // 1 hour
    max: 3, // very strict for password reset
    message: {
        error: {
            code: 'RATE_LIMITED',
            message: 'Too many password reset requests. Please try again later.',
        },
    },
    standardHeaders: true,
    legacyHeaders: false,
});
const usernameSchema = zod_1.z
    .string()
    .min(3)
    .max(30)
    .regex(/^[a-zA-Z0-9_.-]+$/, 'Username can only contain letters, numbers, dots, dashes and underscores');
const registerSchema = zod_1.z.object({
    username: usernameSchema,
    password: zod_1.z.string().min(8),
});
const loginSchema = zod_1.z.object({
    username: zod_1.z.string(),
    password: zod_1.z.string(),
});
const forgotPasswordSchema = zod_1.z.object({
    username: zod_1.z.string().min(1),
});
const resetPasswordSchema = zod_1.z.object({
    token: zod_1.z.string().min(1),
    password: zod_1.z.string().min(8),
});
const BCRYPT_ROUNDS = 12;
async function hashPassword(password) {
    return bcryptjs_1.default.hash(password, BCRYPT_ROUNDS);
}
async function verifyPassword(password, hash) {
    return bcryptjs_1.default.compare(password, hash);
}
function generateResetToken() {
    return crypto_1.default.randomBytes(32).toString('hex');
}
router.post('/register', authLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = registerSchema.parse(req.body);
    const existing = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
    if (existing.length > 0) {
        throw new errorHandler_1.AppError('USERNAME_EXISTS', 'Username already registered', 409);
    }
    const userId = (0, auth_1.generateId)('user');
    const now = (0, auth_1.getCurrentTimestamp)();
    await db_1.db.insert(schema_1.users).values({
        id: userId,
        username: data.username,
        passwordHash: await hashPassword(data.password),
        createdAt: now,
    });
    const token = (0, auth_1.generateToken)({ userId, username: data.username });
    res.status(201).json({
        user: { id: userId, username: data.username },
        token,
    });
}));
router.post('/login', loginLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = loginSchema.parse(req.body);
    const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
    if (user.length === 0 || !verifyPassword(data.password, user[0].passwordHash)) {
        throw new errorHandler_1.AppError('INVALID_CREDENTIALS', 'Invalid username or password', 401);
    }
    const token = (0, auth_1.generateToken)({ userId: user[0].id, username: user[0].username });
    res.json({
        user: { id: user[0].id, username: user[0].username },
        token,
    });
}));
router.get('/me', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    if (!req.user) {
        throw new errorHandler_1.AppError('UNAUTHORIZED', 'Authentication required', 401);
    }
    const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.id, req.user.userId)).limit(1);
    if (user.length === 0) {
        throw new errorHandler_1.AppError('NOT_FOUND', 'User not found', 404);
    }
    res.json({ id: user[0].id, username: user[0].username });
}));
router.post('/forgot-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = forgotPasswordSchema.parse(req.body);
    const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
    // Always return success to prevent username enumeration
    if (user.length === 0) {
        res.json({ success: true });
        return;
    }
    const resetToken = generateResetToken();
    const resetTokenExpiry = Date.now() + 3600000; // 1 hour
    await db_1.db
        .update(schema_1.users)
        .set({ resetToken, resetTokenExpiry })
        .where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id));
    // In production, send email with reset link
    // For now, return token in response (dev only)
    if (process.env.NODE_ENV !== 'production') {
        res.json({ success: true, resetToken });
    }
    else {
        res.json({ success: true });
    }
}));
router.post('/reset-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = resetPasswordSchema.parse(req.body);
    const user = await db_1.db
        .select()
        .from(schema_1.users)
        .where((0, drizzle_orm_1.eq)(schema_1.users.resetToken, data.token))
        .limit(1);
    if (user.length === 0 || !user[0].resetTokenExpiry || user[0].resetTokenExpiry < Date.now()) {
        throw new errorHandler_1.AppError('INVALID_TOKEN', 'Invalid or expired reset token', 400);
    }
    const newPasswordHash = await hashPassword(data.password);
    await db_1.db
        .update(schema_1.users)
        .set({ passwordHash: newPasswordHash, resetToken: null, resetTokenExpiry: null })
        .where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id));
    res.json({ success: true });
}));
exports.default = router;
//# sourceMappingURL=auth.js.map