- Cookie Secure flag now uses req.secure (respects trust proxy + X-Forwarded-Proto) instead of NODE_ENV. Works correctly over both HTTP (local dev) and HTTPS (production tunnel). - SameSite=Strict over HTTPS, SameSite=Lax over HTTP (browsers reject Strict cookies over plain HTTP). - Un-track generated nginx/conf.d/api.conf and services.conf (gitignored, regenerated from templates at startup). - Update CLAUDE.md: ENCRYPTION_KEY now required in all environments. Bunker Admin
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { authenticate } from '../../middleware/auth.middleware';
|
|
import { requireRole } from '../../middleware/rbac.middleware';
|
|
import { validate } from '../../middleware/validate';
|
|
import { quickJoinRateLimit } from '../../middleware/rate-limit';
|
|
import { volunteerInviteService } from './volunteer-invite.service';
|
|
import { generateInviteSchema, redeemInviteSchema } from './volunteer-invite.schemas';
|
|
import { env } from '../../config/env';
|
|
|
|
const router = Router();
|
|
|
|
// POST /api/volunteer-invite/generate — Admin-only: create a signed invite token
|
|
router.post(
|
|
'/generate',
|
|
authenticate,
|
|
requireRole('SUPER_ADMIN', 'MAP_ADMIN', 'INFLUENCE_ADMIN'),
|
|
validate(generateInviteSchema),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { cutId, shiftId } = req.body;
|
|
const token = volunteerInviteService.generateInviteToken(req.user!.id, cutId, shiftId);
|
|
res.json({ token });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
// POST /api/volunteer-invite/redeem — Public: redeem an invite token
|
|
router.post(
|
|
'/redeem',
|
|
quickJoinRateLimit,
|
|
validate(redeemInviteSchema),
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const result = await volunteerInviteService.redeemInvite(req.body);
|
|
// Set refresh token as httpOnly cookie (Secure flag based on actual protocol)
|
|
res.cookie('cml_refresh', result.tokens.refreshToken, {
|
|
httpOnly: true,
|
|
secure: req.secure,
|
|
sameSite: req.secure ? 'strict' : 'lax',
|
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
path: '/api/auth',
|
|
});
|
|
res.json({
|
|
accessToken: result.tokens.accessToken,
|
|
cutId: result.cutId,
|
|
shiftId: result.shiftId,
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
export { router as volunteerInviteRouter };
|