Genesis
This commit is contained in:
70
src/api/routes/auth/login.js
Normal file
70
src/api/routes/auth/login.js
Normal 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' });
|
||||
}
|
||||
});
|
||||
}
|
||||
36
src/api/routes/auth/logout.js
Normal file
36
src/api/routes/auth/logout.js
Normal 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
19
src/api/routes/auth/me.js
Normal 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 });
|
||||
});
|
||||
}
|
||||
5
src/api/routes/auth/meJson.js
Normal file
5
src/api/routes/auth/meJson.js
Normal 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user