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>
79 lines
2.2 KiB
JavaScript
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,
|
|
};
|