ken_nogi/NodeSrv/apps/app-portal/src/adminAuth.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

79 lines
2.2 KiB
JavaScript

const crypto = require('crypto');
const SESSION_COOKIE_NAME = 'portal_admin_session';
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
function timingSafeEqualStrings(a, b) {
const bufA = Buffer.from(String(a));
const bufB = Buffer.from(String(b));
if (bufA.length !== bufB.length) {
crypto.timingSafeEqual(bufA, bufA);
return false;
}
return crypto.timingSafeEqual(bufA, bufB);
}
function verifyMasterKey(input, masterKey) {
if (!masterKey || !input) return false;
return timingSafeEqualStrings(input, masterKey);
}
function sign(payload, secret) {
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}
function createSessionToken(secret, now = Date.now()) {
const payload = String(now);
return `${payload}.${sign(payload, secret)}`;
}
function verifySessionToken(token, secret, now = Date.now()) {
if (!token || typeof token !== 'string') return false;
const parts = token.split('.');
if (parts.length !== 2) return false;
const [payload, signature] = parts;
if (!timingSafeEqualStrings(signature, sign(payload, secret))) return false;
const issuedAt = Number(payload);
if (!Number.isFinite(issuedAt)) return false;
return now - issuedAt < SESSION_TTL_MS;
}
function parseCookies(cookieHeader) {
const result = {};
if (!cookieHeader) return result;
cookieHeader.split(';').forEach((pair) => {
const idx = pair.indexOf('=');
if (idx === -1) return;
const key = pair.slice(0, idx).trim();
const value = pair.slice(idx + 1).trim();
if (key) result[key] = decodeURIComponent(value);
});
return result;
}
function buildSessionCookieHeader(token) {
return [
`${SESSION_COOKIE_NAME}=${encodeURIComponent(token)}`,
'HttpOnly',
'Secure',
'Path=/admin',
'SameSite=Strict',
`Max-Age=${Math.floor(SESSION_TTL_MS / 1000)}`,
].join('; ');
}
function buildLogoutCookieHeader() {
return [`${SESSION_COOKIE_NAME}=`, 'HttpOnly', 'Secure', 'Path=/admin', 'SameSite=Strict', 'Max-Age=0'].join('; ');
}
module.exports = {
SESSION_COOKIE_NAME,
SESSION_TTL_MS,
verifyMasterKey,
createSessionToken,
verifySessionToken,
parseCookies,
buildSessionCookieHeader,
buildLogoutCookieHeader,
};