Files
picpeak/backend/src/utils/tokenUtils.js
T
paul 8c77b30de6
Mirror to GitHub / mirror (push) Successful in 42s
Test and Lint / backend-test (push) Successful in 1m36s
Test and Lint / frontend-test (push) Successful in 2m5s
Version and Release / version-bump (push) Successful in 1m0s
Version and Release / trigger-drone (push) Successful in 3s
Default auth cookies to non-secure for HTTP installs
2025-09-18 16:48:14 +02:00

130 lines
3.3 KiB
JavaScript

const ADMIN_COOKIE_NAME = 'admin_token';
const GALLERY_COOKIE_NAME = 'gallery_token';
const GALLERY_COOKIE_PREFIX = 'gallery_token_';
const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
const secureCookie = (() => {
if (typeof process.env.COOKIE_SECURE === 'string') {
return process.env.COOKIE_SECURE.toLowerCase() === 'true';
}
// Default to false so native HTTP installs stay functional. Operators can
// opt-in via COOKIE_SECURE=true when serving behind HTTPS.
return false;
})();
const sameSiteDefault = process.env.COOKIE_SAMESITE || 'Lax';
const cookieDomain = process.env.COOKIE_DOMAIN;
function buildCookieBaseOptions() {
const options = {
httpOnly: true,
secure: secureCookie,
sameSite: sameSiteDefault,
path: '/',
};
if (cookieDomain) {
options.domain = cookieDomain;
}
return options;
}
function buildCookieOptionsWithExpiry(maxAgeMs = DEFAULT_MAX_AGE_MS) {
return {
...buildCookieBaseOptions(),
maxAge: maxAgeMs,
};
}
function sanitizeSlugForCookie(slug = '') {
return String(slug).replace(/[^A-Za-z0-9_-]/g, '_');
}
function setAdminAuthCookie(res, token) {
if (!token) return;
res.cookie(ADMIN_COOKIE_NAME, token, buildCookieOptionsWithExpiry());
}
function clearAdminAuthCookie(res) {
res.clearCookie(ADMIN_COOKIE_NAME, buildCookieBaseOptions());
}
function setGalleryAuthCookies(res, token, slug) {
if (!token) return;
const options = buildCookieOptionsWithExpiry();
res.cookie(GALLERY_COOKIE_NAME, token, options);
if (slug) {
const cookieName = `${GALLERY_COOKIE_PREFIX}${sanitizeSlugForCookie(slug)}`;
res.cookie(cookieName, token, options);
}
}
function clearGalleryAuthCookies(res, slug) {
const baseOptions = buildCookieBaseOptions();
res.clearCookie(GALLERY_COOKIE_NAME, baseOptions);
const cookies = res.req?.cookies || {};
if (slug) {
const cookieName = `${GALLERY_COOKIE_PREFIX}${sanitizeSlugForCookie(slug)}`;
res.clearCookie(cookieName, baseOptions);
} else {
Object.keys(cookies).forEach((name) => {
if (name.startsWith(GALLERY_COOKIE_PREFIX)) {
res.clearCookie(name, baseOptions);
}
});
}
}
function getAdminTokenFromRequest(req) {
const header = req.headers?.authorization;
if (header && header.startsWith('Bearer ')) {
return header.substring(7);
}
return req.cookies?.[ADMIN_COOKIE_NAME] || null;
}
function getGalleryTokenFromRequest(req, slug) {
const header = req.headers?.authorization;
if (header && header.startsWith('Bearer ')) {
return header.substring(7);
}
if (!req.cookies) {
return null;
}
if (slug) {
const cookieName = `${GALLERY_COOKIE_PREFIX}${sanitizeSlugForCookie(slug)}`;
if (req.cookies[cookieName]) {
return req.cookies[cookieName];
}
}
if (req.cookies[GALLERY_COOKIE_NAME]) {
return req.cookies[GALLERY_COOKIE_NAME];
}
const prefixed = Object.keys(req.cookies).find((name) => name.startsWith(GALLERY_COOKIE_PREFIX));
if (prefixed) {
return req.cookies[prefixed];
}
return null;
}
module.exports = {
ADMIN_COOKIE_NAME,
GALLERY_COOKIE_NAME,
GALLERY_COOKIE_PREFIX,
sanitizeSlugForCookie,
setAdminAuthCookie,
clearAdminAuthCookie,
setGalleryAuthCookies,
clearGalleryAuthCookies,
getAdminTokenFromRequest,
getGalleryTokenFromRequest,
};