chore: archive AgentDock v1 implementation

This commit is contained in:
leefer
2026-08-24 17:14:08 +08:00
commit f512bd58ec
66 changed files with 10663 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
import crypto from "node:crypto";
const COOKIE_NAME = "agentdock_session";
const MAX_AGE_MS = 12 * 60 * 60 * 1000;
function parseCookies(header = "") {
return Object.fromEntries(header.split(";").map((part) => {
const index = part.indexOf("=");
if (index < 0) return ["", ""];
return [part.slice(0, index).trim(), decodeURIComponent(part.slice(index + 1).trim())];
}).filter(([key]) => key));
}
export class AuthService {
constructor(password, { secureCookie = true } = {}) {
this.salt = crypto.randomBytes(16);
this.passwordHash = crypto.scryptSync(password, this.salt, 64);
this.sessions = new Map();
this.attempts = new Map();
this.secureCookie = secureCookie;
}
verifyPassword(password) {
const candidate = crypto.scryptSync(String(password || ""), this.salt, 64);
return crypto.timingSafeEqual(candidate, this.passwordHash);
}
login(req, res, password) {
const remote = req.ip || "unknown";
const attempt = this.attempts.get(remote) || { count: 0, resetAt: Date.now() + 60_000 };
if (Date.now() > attempt.resetAt) {
attempt.count = 0;
attempt.resetAt = Date.now() + 60_000;
}
if (attempt.count >= 8) return { ok: false, status: 429, error: "尝试次数过多,请一分钟后重试" };
if (!this.verifyPassword(password)) {
attempt.count += 1;
this.attempts.set(remote, attempt);
return { ok: false, status: 401, error: "管理员密码不正确" };
}
this.attempts.delete(remote);
const token = crypto.randomBytes(32).toString("base64url");
this.sessions.set(token, Date.now() + MAX_AGE_MS);
const secure = this.secureCookie ? "; Secure" : "";
res.setHeader("Set-Cookie", `${COOKIE_NAME}=${token}; Path=/; HttpOnly${secure}; SameSite=Strict; Max-Age=${MAX_AGE_MS / 1000}`);
return { ok: true };
}
logout(req, res) {
const token = parseCookies(req.headers.cookie)[COOKIE_NAME];
if (token) this.sessions.delete(token);
const secure = this.secureCookie ? "; Secure" : "";
res.setHeader("Set-Cookie", `${COOKIE_NAME}=; Path=/; HttpOnly${secure}; SameSite=Strict; Max-Age=0`);
}
isAuthenticated(req) {
const token = parseCookies(req.headers.cookie)[COOKIE_NAME];
if (!token) return false;
const expiresAt = this.sessions.get(token);
if (!expiresAt || expiresAt < Date.now()) {
this.sessions.delete(token);
return false;
}
return true;
}
middleware() {
return (req, res, next) => {
if (!this.isAuthenticated(req)) return res.status(401).json({ error: "Unauthorized" });
next();
};
}
}