alpha
This commit is contained in:
127
backend/routes/alpha.js
Normal file
127
backend/routes/alpha.js
Normal file
@@ -0,0 +1,127 @@
|
||||
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) {
|
||||
// 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: process.env.NODE_ENV === "production",
|
||||
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 };
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { OAuth2Client } = require("google-auth-library");
|
||||
const { User } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const { User, AlphaInvitation } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const logger = require("../utils/logger");
|
||||
const emailService = require("../services/emailService");
|
||||
const crypto = require("crypto");
|
||||
@@ -64,6 +64,35 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
// Alpha access validation
|
||||
let alphaInvitation = null;
|
||||
if (req.signedCookies && req.signedCookies.alphaAccessCode) {
|
||||
const { code } = req.signedCookies.alphaAccessCode;
|
||||
if (code) {
|
||||
alphaInvitation = await AlphaInvitation.findOne({
|
||||
where: { code },
|
||||
});
|
||||
|
||||
if (!alphaInvitation) {
|
||||
return res.status(403).json({
|
||||
error: "Invalid alpha access code",
|
||||
});
|
||||
}
|
||||
|
||||
if (alphaInvitation.status === "revoked") {
|
||||
return res.status(403).json({
|
||||
error: "This alpha access code is no longer valid",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alphaInvitation) {
|
||||
return res.status(403).json({
|
||||
error: "Alpha access required. Please enter your invitation code first.",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.create({
|
||||
username,
|
||||
email,
|
||||
@@ -73,6 +102,13 @@ router.post(
|
||||
phone,
|
||||
});
|
||||
|
||||
// Link alpha invitation to user
|
||||
await alphaInvitation.update({
|
||||
usedBy: user.id,
|
||||
usedAt: new Date(),
|
||||
status: "active",
|
||||
});
|
||||
|
||||
// Generate verification token and send email
|
||||
await user.generateVerificationToken();
|
||||
|
||||
@@ -318,6 +354,20 @@ router.post(
|
||||
isVerified: true,
|
||||
verifiedAt: new Date(),
|
||||
});
|
||||
|
||||
// Check if there's an alpha invitation for this email
|
||||
const alphaInvitation = await AlphaInvitation.findOne({
|
||||
where: { email: email.toLowerCase().trim() },
|
||||
});
|
||||
|
||||
if (alphaInvitation && !alphaInvitation.usedBy) {
|
||||
// Link invitation to new user
|
||||
await alphaInvitation.update({
|
||||
usedBy: user.id,
|
||||
usedAt: new Date(),
|
||||
status: "active",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate JWT tokens
|
||||
|
||||
Reference in New Issue
Block a user