feat(mam-api): add auth middleware with session and Bearer token support
This commit is contained in:
parent
2d8c44c529
commit
f57ed1b498
1 changed files with 54 additions and 9 deletions
|
|
@ -2,17 +2,62 @@
|
|||
* Authentication middleware.
|
||||
*
|
||||
* When AUTH_ENABLED=true in the environment, every protected route requires
|
||||
* an active session (set by POST /api/v1/auth/login).
|
||||
* either:
|
||||
* - An active session (set by POST /api/v1/auth/login), or
|
||||
* - A valid Bearer token in Authorization header (set by POST /api/v1/tokens)
|
||||
*
|
||||
* When AUTH_ENABLED is unset or any other value, the middleware is a no-op
|
||||
* so the stack can be deployed and tested without setting up users first.
|
||||
* Set AUTH_ENABLED=true in production after running POST /api/v1/auth/setup
|
||||
* to create the first admin account.
|
||||
* When AUTH_ENABLED is unset or any other value, all middleware is a no-op so
|
||||
* the stack can be run without user accounts during development.
|
||||
*/
|
||||
export const requireAuth = (req, res, next) => {
|
||||
import crypto from 'crypto';
|
||||
import pool from '../db/pool.js';
|
||||
|
||||
export const requireAuth = async (req, res, next) => {
|
||||
if (process.env.AUTH_ENABLED !== 'true') return next();
|
||||
if (!req.session || !req.session.userId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
|
||||
// ── Session-based auth ────────────────────────────────────────
|
||||
if (req.session?.userId) {
|
||||
req.user = {
|
||||
id: req.session.userId,
|
||||
username: req.session.username,
|
||||
role: req.session.role,
|
||||
};
|
||||
return next();
|
||||
}
|
||||
next();
|
||||
|
||||
// ── Bearer token auth ─────────────────────────────────────────
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
const raw = authHeader.slice(7).trim();
|
||||
const hash = crypto.createHash('sha256').update(raw).digest('hex');
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT t.user_id AS id, u.username, u.role
|
||||
FROM api_tokens t
|
||||
JOIN users u ON u.id = t.user_id
|
||||
WHERE t.token_hash = $1
|
||||
AND (t.expires_at IS NULL OR t.expires_at > NOW())`,
|
||||
[hash]
|
||||
);
|
||||
if (rows.length > 0) {
|
||||
req.user = rows[0];
|
||||
// Fire-and-forget last_used_at update
|
||||
pool.query(
|
||||
'UPDATE api_tokens SET last_used_at = NOW() WHERE token_hash = $1',
|
||||
[hash]
|
||||
).catch(() => {});
|
||||
return next();
|
||||
}
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
};
|
||||
|
||||
export const requireAdmin = (req, res, next) => {
|
||||
if (process.env.AUTH_ENABLED !== 'true') return next();
|
||||
if (req.user?.role === 'admin') return next();
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue