133 lines
3.5 KiB
JavaScript
133 lines
3.5 KiB
JavaScript
const express = require("express");
|
|
const { AlphaInvitation, User } = require("../models");
|
|
const { authenticateToken, optionalAuth } = require("../middleware/auth");
|
|
const { alphaCodeValidationLimiter } = require("../middleware/rateLimiter");
|
|
const logger = require("../utils/logger");
|
|
const crypto = require("crypto");
|
|
const router = express.Router();
|
|
|
|
// Helper function to check if user has alpha access
|
|
async function checkAlphaAccess(req) {
|
|
// Bypass alpha access check if feature is disabled
|
|
if (process.env.ALPHA_TESTING_ENABLED !== 'true') {
|
|
return true;
|
|
}
|
|
|
|
// Check 1: Valid alpha access cookie
|
|
if (req.cookies && req.cookies.alphaAccessCode) {
|
|
const { code } = req.cookies.alphaAccessCode;
|
|
const invitation = await AlphaInvitation.findOne({
|
|
where: { code, status: ["pending", "active"] },
|
|
});
|
|
if (invitation) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check 2: Authenticated user who has used an invitation
|
|
if (req.user && req.user.id) {
|
|
const invitation = await AlphaInvitation.findOne({
|
|
where: { usedBy: req.user.id },
|
|
});
|
|
if (invitation) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* POST /api/alpha/validate-code
|
|
* Validates an alpha invitation code and grants access
|
|
*/
|
|
router.post("/validate-code", alphaCodeValidationLimiter, async (req, res) => {
|
|
try {
|
|
const { code } = req.body;
|
|
|
|
if (!code) {
|
|
return res.status(400).json({
|
|
error: "Code is required",
|
|
});
|
|
}
|
|
|
|
// Normalize code (uppercase, trim)
|
|
const normalizedCode = code.trim().toUpperCase();
|
|
|
|
// Validate code format
|
|
if (!/^ALPHA-[A-Z0-9]{8}$/.test(normalizedCode)) {
|
|
logger.warn(`Invalid code format attempted: ${code}`);
|
|
return res.status(400).json({
|
|
error: "Invalid alpha code",
|
|
});
|
|
}
|
|
|
|
// Find invitation in database
|
|
const invitation = await AlphaInvitation.findOne({
|
|
where: { code: normalizedCode },
|
|
});
|
|
|
|
// Generic error for invalid code (prevent enumeration)
|
|
if (!invitation) {
|
|
logger.warn(`Code not found: ${normalizedCode}`);
|
|
return res.status(400).json({
|
|
error: "Invalid alpha code",
|
|
});
|
|
}
|
|
|
|
// Check if code is revoked
|
|
if (invitation.status === "revoked") {
|
|
logger.warn(`Revoked code attempted: ${normalizedCode}`);
|
|
return res.status(400).json({
|
|
error: "Invalid alpha code",
|
|
});
|
|
}
|
|
|
|
// Set httpOnly cookie for alpha access
|
|
const cookieData = {
|
|
code: normalizedCode,
|
|
validatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
res.cookie("alphaAccessCode", cookieData, {
|
|
httpOnly: true,
|
|
secure: ["production", "prod", "qa"].includes(process.env.NODE_ENV),
|
|
sameSite: "strict",
|
|
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
});
|
|
|
|
logger.info(`Alpha code validated successfully: ${normalizedCode}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Access granted",
|
|
});
|
|
} catch (error) {
|
|
logger.error(`Error validating alpha code: ${error.message}`, { error });
|
|
res.status(500).json({
|
|
error: "Server error",
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/alpha/verify-session
|
|
* Checks if current session has alpha access
|
|
*/
|
|
router.get("/verify-session", optionalAuth, async (req, res) => {
|
|
try {
|
|
const hasAccess = await checkAlphaAccess(req);
|
|
|
|
res.json({
|
|
hasAccess,
|
|
});
|
|
} catch (error) {
|
|
logger.error(`Error verifying alpha session: ${error.message}`, { error });
|
|
res.status(500).json({
|
|
error: "Server error",
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = { router, checkAlphaAccess };
|