ken_nogi/NodeSrv/apps/lineworks-user-auth/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

95 lines
2.6 KiB
JavaScript

"use strict";
const crypto = require("node:crypto");
const SESSION_COOKIE_NAME = "lwauth_admin_session";
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
const TEMP_KEY_TTL_MS = 60 * 60 * 1000;
const KIND_TTL_MS = { session: SESSION_TTL_MS, temp: TEMP_KEY_TTL_MS };
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 buildToken(kind, secret, now) {
const payload = `${kind}:${now}`;
return `${payload}.${sign(payload, secret)}`;
}
function createSessionToken(secret, now = Date.now()) {
return buildToken("session", secret, now);
}
function createTempKey(secret, now = Date.now()) {
return buildToken("temp", secret, now);
}
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 [kind, tsStr] = payload.split(":");
const ttl = KIND_TTL_MS[kind];
if (!ttl) return false;
const issuedAt = Number(tsStr);
if (!Number.isFinite(issuedAt)) return false;
return now - issuedAt < ttl;
}
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, maxAgeMs = SESSION_TTL_MS) {
return [
`${SESSION_COOKIE_NAME}=${encodeURIComponent(token)}`,
"HttpOnly",
"Secure",
"Path=/auth",
"SameSite=Strict",
`Max-Age=${Math.floor(maxAgeMs / 1000)}`,
].join("; ");
}
function buildLogoutCookieHeader() {
return [`${SESSION_COOKIE_NAME}=`, "HttpOnly", "Secure", "Path=/auth", "SameSite=Strict", "Max-Age=0"].join("; ");
}
module.exports = {
SESSION_COOKIE_NAME,
SESSION_TTL_MS,
TEMP_KEY_TTL_MS,
verifyMasterKey,
createSessionToken,
createTempKey,
verifySessionToken,
parseCookies,
buildSessionCookieHeader,
buildLogoutCookieHeader,
};