Files
rentall-app/backend/routes/alpha.js
2026-01-17 11:12:40 -05:00

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 };