This commit is contained in:
seb
2026-06-23 11:04:07 +02:00
commit 667f498573
19 changed files with 4299 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
import jwt from 'jsonwebtoken';
import { OAuth2Client } from 'google-auth-library';
import env from '../../../config/env.js';
import * as db from '../../../db/googleAuth.js';
const googleClient = new OAuth2Client(env.googleClientId);
/**
* Registers the login route (Google SSO → JWT cookie session).
* @param {import('express').Application} app - Express app
* @param {string} [basePath=''] - Optional URL prefix for all routes in this module
*/
export function register(app, basePath = '') {
/**
* @route POST /auth/login
* @description Verifies a Google ID token, checks allowlist, and sets an HTTP-only `token` cookie.
* @body {string} idToken - Google ID token from Google Identity Services
* @returns {Object} 200 - `{ message, user }` with `{ id, email, picture? }` in `user`
* @returns {Object} 400 - Missing token or Google client not configured
* @returns {Object} 401 - Invalid Google token
* @returns {Object} 403 - Email not on allowlist
* @returns {Object} 500 - Server error
*/
app.post(`${basePath}/auth/login`, async (req, res) => {
try {
const { idToken } = req.body;
if (!idToken) {
return res.status(400).json({ error: 'idToken is required' });
}
if (!env.googleClientId) {
return res.status(400).json({ error: 'GOOGLE_CLIENT_ID is not configured' });
}
const ticket = await googleClient.verifyIdToken({
idToken,
audience: env.googleClientId,
});
const payload = ticket.getPayload();
const email = payload?.email?.toLowerCase();
if (!email || !payload.email_verified) {
return res.status(401).json({ error: 'Invalid Google account' });
}
const allowed = db.getAllowedEmail(email);
if (!allowed) {
return res.status(403).json({ error: 'Email is not authorized' });
}
const user = {
id: allowed.id,
email: allowed.email,
...(payload.picture && { picture: payload.picture }),
};
const token = jwt.sign(user, env.jwtSecret, { expiresIn: '1d' });
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000,
});
res.status(200).json({ message: 'Login successful', user });
} catch (error) {
res.status(401).json({ error: 'Invalid Google token' });
}
});
}

View File

@@ -0,0 +1,36 @@
import jwt from 'jsonwebtoken';
import env from '../../../config/env.js';
import { clearCachedUser } from '../../middleware/auth.js';
/**
* Registers the logout route (clears session cookie).
* @param {import('express').Application} app - Express app
* @param {string} [basePath=''] - Optional URL prefix for all routes in this module
*/
export function register(app, basePath = '') {
/**
* @route POST /auth/logout
* @description Clears the HTTP-only `token` cookie set at login.
* @returns {Object} 200 - `{ message }` confirming logout
*/
app.post(`${basePath}/auth/logout`, (req, res) => {
const token = req.cookies.token;
if (token) {
try {
const decoded = jwt.verify(token, env.jwtSecret, { ignoreExpiration: true });
if (decoded?.id) {
clearCachedUser(decoded.id);
}
} catch {
// Ignore errors during cache clearing on logout
}
}
res.clearCookie('token', {
httpOnly: true,
secure: true,
sameSite: 'strict',
});
res.status(200).json({ message: 'Logged out successfully' });
});
}

19
src/api/routes/auth/me.js Normal file
View File

@@ -0,0 +1,19 @@
import { meJson } from './meJson.js';
/**
* Registers the current-user route (requires prior auth middleware populating `req.user`).
* @param {import('express').Application} app - Express app
* @param {string} [basePath=''] - Optional URL prefix for all routes in this module
*/
export function register(app, basePath = '') {
/**
* @route GET /auth/me
* @description Returns the authenticated user from `req.user` and the Google client ID.
* @returns {Object} 200 - `{ user: { id, email, picture? }, googleClientId }`
*/
app.get(`${basePath}/auth/me`, (req, res) => {
const { id, email, picture } = req.user;
const user = { id, email, ...(picture && { picture }) };
meJson(res, 200, { user });
});
}

View File

@@ -0,0 +1,5 @@
import env from '../../../config/env.js';
export function meJson(res, status, body) {
return res.status(status).json({ ...body, googleClientId: env.googleClientId });
}