130 lines
4.3 KiB
JavaScript
130 lines
4.3 KiB
JavaScript
const { normalizePermissionKeys } = require('../../rbac');
|
|
|
|
function createSessionService(options) {
|
|
const sessionCookieName = String(options && options.sessionCookieName || '').trim();
|
|
const sessionMaxAgeMs = Number(options && options.sessionMaxAgeMs);
|
|
const hashSessionToken = options && options.hashSessionToken;
|
|
const createSessionToken = options && options.createSessionToken;
|
|
|
|
if (!sessionCookieName || !Number.isFinite(sessionMaxAgeMs) || typeof hashSessionToken !== 'function' || typeof createSessionToken !== 'function') {
|
|
throw new Error('createSessionService requires the session dependencies.');
|
|
}
|
|
|
|
function parseCookies(cookieHeader) {
|
|
return String(cookieHeader || '').split(/;\s*/).reduce(function (cookies, pair) {
|
|
if (!pair) {
|
|
return cookies;
|
|
}
|
|
const separatorIndex = pair.indexOf('=');
|
|
if (separatorIndex === -1) {
|
|
return cookies;
|
|
}
|
|
const name = decodeURIComponent(pair.slice(0, separatorIndex).trim());
|
|
const value = decodeURIComponent(pair.slice(separatorIndex + 1).trim());
|
|
if (name) {
|
|
cookies[name] = value;
|
|
}
|
|
return cookies;
|
|
}, {});
|
|
}
|
|
|
|
function serializeCookie(name, value, options) {
|
|
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
if (options && options.maxAge !== undefined) {
|
|
parts.push(`Max-Age=${Math.max(0, Math.trunc(Number(options.maxAge) / 1000))}`);
|
|
}
|
|
parts.push('Path=/');
|
|
parts.push('HttpOnly');
|
|
parts.push('SameSite=Lax');
|
|
return parts.join('; ');
|
|
}
|
|
|
|
function clearSessionCookie(res) {
|
|
res.setHeader('Set-Cookie', serializeCookie(sessionCookieName, '', { maxAge: 0 }));
|
|
}
|
|
|
|
function setSessionCookie(res, token) {
|
|
res.setHeader('Set-Cookie', serializeCookie(sessionCookieName, token, { maxAge: sessionMaxAgeMs }));
|
|
}
|
|
|
|
async function loadCurrentUser(pool, req) {
|
|
const cookies = parseCookies(req.headers.cookie || '');
|
|
const token = cookies[sessionCookieName];
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
|
|
const tokenHash = hashSessionToken(token);
|
|
const [rows] = await pool.query(
|
|
`SELECT s.user_id, u.id, u.name, u.username
|
|
FROM auth_sessions s
|
|
JOIN users u ON u.id = s.user_id
|
|
WHERE s.session_hash = ?
|
|
AND s.expires_at > NOW()
|
|
LIMIT 1`,
|
|
[tokenHash]
|
|
);
|
|
if (!rows.length) {
|
|
return null;
|
|
}
|
|
|
|
const userId = Number(rows[0].id);
|
|
const [roleRows] = await pool.query(
|
|
`SELECT r.role_key
|
|
FROM user_roles ur
|
|
JOIN roles r ON r.id = ur.role_id
|
|
WHERE ur.user_id = ?
|
|
ORDER BY r.name ASC`,
|
|
[userId]
|
|
);
|
|
const [permissionRows] = await pool.query(
|
|
`SELECT p.permission_key
|
|
FROM user_roles ur
|
|
JOIN role_permissions rp ON rp.role_id = ur.role_id
|
|
JOIN permissions p ON p.id = rp.permission_id
|
|
WHERE ur.user_id = ?
|
|
ORDER BY p.section_name ASC, p.name ASC`,
|
|
[userId]
|
|
);
|
|
|
|
await pool.query('UPDATE auth_sessions SET last_used_at = CURRENT_TIMESTAMP, modified_by = ? WHERE session_hash = ?', [rows[0].user_id, tokenHash]);
|
|
return Object.assign({}, rows[0], {
|
|
roleKeys: roleRows.map(function (row) {
|
|
return String(row.role_key || '').trim();
|
|
}).filter(Boolean),
|
|
permissionKeys: normalizePermissionKeys(permissionRows.map(function (row) {
|
|
return String(row.permission_key || '').trim();
|
|
}))
|
|
});
|
|
}
|
|
|
|
async function createUserSession(pool, userId) {
|
|
const token = createSessionToken();
|
|
const tokenHash = hashSessionToken(token);
|
|
const expiresAt = new Date(Date.now() + sessionMaxAgeMs);
|
|
await pool.query(
|
|
'INSERT INTO auth_sessions (session_hash, user_id, expires_at, created_by, modified_by) VALUES (?, ?, ?, ?, ?)',
|
|
[tokenHash, userId, expiresAt, userId, userId]
|
|
);
|
|
return token;
|
|
}
|
|
|
|
function requireAuth(req, res, next) {
|
|
if (req.currentUser) {
|
|
return next();
|
|
}
|
|
res.redirect('/login?message=' + encodeURIComponent('Please sign in to continue.'));
|
|
}
|
|
|
|
return {
|
|
parseCookies: parseCookies,
|
|
serializeCookie: serializeCookie,
|
|
clearSessionCookie: clearSessionCookie,
|
|
setSessionCookie: setSessionCookie,
|
|
loadCurrentUser: loadCurrentUser,
|
|
createUserSession: createUserSession,
|
|
requireAuth: requireAuth
|
|
};
|
|
}
|
|
|
|
module.exports = { createSessionService }; |