契约 19 路径全量:引导/会话/改密、engine 桩、keys、overview/logs、payments、 qpu、members、audit、成员视图。鉴权优先、422 信封、登录节流、上游验证接线。
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
import type { Context, Next } from "hono";
|
|
import { getCookie } from "hono/cookie";
|
|
import { ERR, hashToken, type ErrorBody } from "@new-api-key-usage/core";
|
|
import type { Repos } from "@new-api-key-usage/db";
|
|
|
|
export const SESSION_COOKIE = "admin_session";
|
|
export const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;
|
|
|
|
export type AppVars = {
|
|
repos: Repos;
|
|
skKeyEncryptionSecret: string;
|
|
newapiBase: string;
|
|
cookieSecure: boolean;
|
|
/** 登记时调上游验证;测试可注入 */
|
|
fetchTokenUsage?: (skKey: string) => Promise<
|
|
| { ok: true; data: import("@new-api-key-usage/core").TokenUsage }
|
|
| { ok: false; kind: string }
|
|
>;
|
|
adminInitUsername?: string;
|
|
adminInitPassword?: string;
|
|
adminUsername?: string;
|
|
adminUserId?: number;
|
|
memberId?: number;
|
|
};
|
|
|
|
/** 错误信封统一出口;返回 any 以兼容 OpenAPIHono 严格响应联合 */
|
|
export function jsonError(
|
|
c: Context,
|
|
status: 400 | 401 | 404 | 409 | 422 | 429 | 500 | 502,
|
|
body: ErrorBody,
|
|
): any {
|
|
return c.json(body, status);
|
|
}
|
|
|
|
export async function requireAdminSession(
|
|
c: Context<{ Variables: AppVars }>,
|
|
next: Next,
|
|
) {
|
|
const token = getCookie(c, SESSION_COOKIE);
|
|
if (!token) return jsonError(c, 401, ERR.session_expired());
|
|
const tokenHash = await hashToken(token);
|
|
const row = await c.get("repos").sessions.findValid(tokenHash, new Date());
|
|
if (!row) return jsonError(c, 401, ERR.session_expired());
|
|
c.set("adminUserId", row.user_id);
|
|
c.set("adminUsername", row.username);
|
|
await next();
|
|
}
|
|
|
|
export async function requireMemberToken(
|
|
c: Context<{ Variables: AppVars }>,
|
|
next: Next,
|
|
) {
|
|
const header = c.req.header("Authorization") ?? "";
|
|
const m = /^Bearer\s+(.+)$/i.exec(header);
|
|
if (!m?.[1]) return jsonError(c, 401, ERR.session_expired());
|
|
const tokenHash = await hashToken(m[1]);
|
|
const row = await c.get("repos").members.findByTokenHash(tokenHash);
|
|
if (!row || row.status !== "active") {
|
|
return jsonError(c, 401, ERR.session_expired());
|
|
}
|
|
c.set("memberId", row.id);
|
|
await next();
|
|
}
|