@usethink/cf-core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/features/anti-abuse/index.d.ts +62 -0
- package/dist/features/anti-abuse/index.d.ts.map +1 -0
- package/dist/features/anti-abuse/index.js +139 -0
- package/dist/features/anti-abuse/index.js.map +1 -0
- package/dist/features/email/index.d.ts +68 -0
- package/dist/features/email/index.d.ts.map +1 -0
- package/dist/features/email/index.js +120 -0
- package/dist/features/email/index.js.map +1 -0
- package/dist/features/payment/fetch-utils.d.ts +17 -0
- package/dist/features/payment/fetch-utils.d.ts.map +1 -0
- package/dist/features/payment/fetch-utils.js +56 -0
- package/dist/features/payment/fetch-utils.js.map +1 -0
- package/dist/features/payment/index.d.ts +20 -0
- package/dist/features/payment/index.d.ts.map +1 -0
- package/dist/features/payment/index.js +21 -0
- package/dist/features/payment/index.js.map +1 -0
- package/dist/features/payment/providers/alipay.d.ts +30 -0
- package/dist/features/payment/providers/alipay.d.ts.map +1 -0
- package/dist/features/payment/providers/alipay.js +149 -0
- package/dist/features/payment/providers/alipay.js.map +1 -0
- package/dist/features/payment/providers/stripe.d.ts +34 -0
- package/dist/features/payment/providers/stripe.d.ts.map +1 -0
- package/dist/features/payment/providers/stripe.js +168 -0
- package/dist/features/payment/providers/stripe.js.map +1 -0
- package/dist/features/payment/providers/trc20.d.ts +24 -0
- package/dist/features/payment/providers/trc20.d.ts.map +1 -0
- package/dist/features/payment/providers/trc20.js +96 -0
- package/dist/features/payment/providers/trc20.js.map +1 -0
- package/dist/features/payment/registry.d.ts +43 -0
- package/dist/features/payment/registry.d.ts.map +1 -0
- package/dist/features/payment/registry.js +65 -0
- package/dist/features/payment/registry.js.map +1 -0
- package/dist/features/payment/types.d.ts +72 -0
- package/dist/features/payment/types.d.ts.map +1 -0
- package/dist/features/payment/types.js +8 -0
- package/dist/features/payment/types.js.map +1 -0
- package/dist/features/thompson-router/index.d.ts +101 -0
- package/dist/features/thompson-router/index.d.ts.map +1 -0
- package/dist/features/thompson-router/index.js +186 -0
- package/dist/features/thompson-router/index.js.map +1 -0
- package/dist/features/webhook/index.d.ts +76 -0
- package/dist/features/webhook/index.d.ts.map +1 -0
- package/dist/features/webhook/index.js +127 -0
- package/dist/features/webhook/index.js.map +1 -0
- package/dist/src/audit.d.ts +45 -0
- package/dist/src/audit.d.ts.map +1 -0
- package/dist/src/audit.js +40 -0
- package/dist/src/audit.js.map +1 -0
- package/dist/src/auth/jwt.d.ts +33 -0
- package/dist/src/auth/jwt.d.ts.map +1 -0
- package/dist/src/auth/jwt.js +87 -0
- package/dist/src/auth/jwt.js.map +1 -0
- package/dist/src/auth/password.d.ts +26 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +52 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/bootstrap.d.ts +74 -0
- package/dist/src/bootstrap.d.ts.map +1 -0
- package/dist/src/bootstrap.js +231 -0
- package/dist/src/bootstrap.js.map +1 -0
- package/dist/src/cache.d.ts +52 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js +76 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +96 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/crypto.d.ts +33 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +87 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/db/connection.d.ts +53 -0
- package/dist/src/db/connection.d.ts.map +1 -0
- package/dist/src/db/connection.js +104 -0
- package/dist/src/db/connection.js.map +1 -0
- package/dist/src/db/index.d.ts +6 -0
- package/dist/src/db/index.d.ts.map +1 -0
- package/dist/src/db/index.js +6 -0
- package/dist/src/db/index.js.map +1 -0
- package/dist/src/db/schema.d.ts +649 -0
- package/dist/src/db/schema.d.ts.map +1 -0
- package/dist/src/db/schema.js +76 -0
- package/dist/src/db/schema.js.map +1 -0
- package/dist/src/error.d.ts +47 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +94 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/http.d.ts +83 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +116 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/idempotency.d.ts +78 -0
- package/dist/src/idempotency.d.ts.map +1 -0
- package/dist/src/idempotency.js +84 -0
- package/dist/src/idempotency.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts +31 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +45 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/middleware/admin-auth.d.ts +38 -0
- package/dist/src/middleware/admin-auth.d.ts.map +1 -0
- package/dist/src/middleware/admin-auth.js +55 -0
- package/dist/src/middleware/admin-auth.js.map +1 -0
- package/dist/src/middleware/api-key-auth.d.ts +42 -0
- package/dist/src/middleware/api-key-auth.d.ts.map +1 -0
- package/dist/src/middleware/api-key-auth.js +104 -0
- package/dist/src/middleware/api-key-auth.js.map +1 -0
- package/dist/src/middleware/index.d.ts +3 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +3 -0
- package/dist/src/middleware/index.js.map +1 -0
- package/dist/src/rate-limit.d.ts +54 -0
- package/dist/src/rate-limit.d.ts.map +1 -0
- package/dist/src/rate-limit.js +134 -0
- package/dist/src/rate-limit.js.map +1 -0
- package/dist/src/security.d.ts +78 -0
- package/dist/src/security.d.ts.map +1 -0
- package/dist/src/security.js +175 -0
- package/dist/src/security.js.map +1 -0
- package/dist/src/types.d.ts +64 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +8 -0
- package/dist/src/types.js.map +1 -0
- package/features/anti-abuse/index.ts +180 -0
- package/features/anti-abuse/tests/index.test.ts +50 -0
- package/features/email/index.ts +172 -0
- package/features/email/tests/index.test.ts +44 -0
- package/features/payment/fetch-utils.ts +65 -0
- package/features/payment/index.ts +39 -0
- package/features/payment/providers/alipay.ts +171 -0
- package/features/payment/providers/stripe.ts +192 -0
- package/features/payment/providers/trc20.ts +115 -0
- package/features/payment/registry.ts +87 -0
- package/features/payment/tests/index.test.ts +506 -0
- package/features/payment/types.ts +93 -0
- package/features/telegram-miniapp/index.ts +109 -0
- package/features/telegram-miniapp/tests/index.test.ts +11 -0
- package/features/thompson-router/index.ts +243 -0
- package/features/thompson-router/tests/index.test.ts +93 -0
- package/features/webhook/index.ts +183 -0
- package/features/webhook/tests/index.test.ts +21 -0
- package/package.json +202 -0
- package/src/audit.ts +70 -0
- package/src/auth/jwt.ts +114 -0
- package/src/auth/password.ts +75 -0
- package/src/bootstrap.ts +322 -0
- package/src/cache.ts +78 -0
- package/src/config.ts +134 -0
- package/src/crypto.ts +106 -0
- package/src/db/connection.ts +127 -0
- package/src/db/index.ts +6 -0
- package/src/db/schema.ts +90 -0
- package/src/error.ts +125 -0
- package/src/http.ts +150 -0
- package/src/idempotency.ts +127 -0
- package/src/index.ts +85 -0
- package/src/logger.ts +63 -0
- package/src/middleware/admin-auth.ts +71 -0
- package/src/middleware/api-key-auth.ts +164 -0
- package/src/middleware/index.ts +2 -0
- package/src/rate-limit.ts +167 -0
- package/src/security.ts +219 -0
- package/src/types.ts +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGxH,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAG7C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAGjG,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGhG,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAGxF,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGhE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGjF,OAAO,EAAE,YAAY,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGpI,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAGxH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/D,YAAY,EACV,YAAY,EACZ,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @usethink/cf-core — Cloudflare Workers 共享内核
|
|
3
|
+
*
|
|
4
|
+
* 统一导出所有模块,支持两种导入方式:
|
|
5
|
+
*
|
|
6
|
+
* 1. 从根导入(适合小项目):
|
|
7
|
+
* import { ok, fail, sha256, verifyTurnstile } from "@usethink/cf-core";
|
|
8
|
+
*
|
|
9
|
+
* 2. 按子路径导入(推荐,tree-shakeable):
|
|
10
|
+
* import { ok, fail } from "@usethink/cf-core/http";
|
|
11
|
+
* import { sha256 } from "@usethink/cf-core/security";
|
|
12
|
+
*/
|
|
13
|
+
// ── HTTP 工具 ──
|
|
14
|
+
export { ok, fail, failRateLimit, getOrigin, safeJsonBody, maskContact, normalizeCode, csvEscape, toCsv } from "./http";
|
|
15
|
+
// ── 安全工具 ──
|
|
16
|
+
export { sha256, constantTimeEqual, getIpHash, getClientIp, getBearerToken, verifyTurnstile, buildSecurityHeaders, } from "./security";
|
|
17
|
+
// ── 缓存 ──
|
|
18
|
+
export { createCache, cache } from "./cache";
|
|
19
|
+
// ── 限流 ──
|
|
20
|
+
export { MemoryRateLimiter, KvRateLimiter, DbRateLimiter } from "./rate-limit";
|
|
21
|
+
// ── 幂等性 ──
|
|
22
|
+
export { checkIdempotency, saveIdempotentResponse, getIdempotentResponse } from "./idempotency";
|
|
23
|
+
// ── 审计日志 ──
|
|
24
|
+
export { writeAdminAudit } from "./audit";
|
|
25
|
+
// ── 系统配置 ──
|
|
26
|
+
export { SystemConfig } from "./config";
|
|
27
|
+
// ── 错误处理 ──
|
|
28
|
+
export { classifyError, retryWithBackoff, ErrorType } from "./error";
|
|
29
|
+
// ── 结构化日志 ──
|
|
30
|
+
export { logger } from "./logger";
|
|
31
|
+
// ── 加解密 ──
|
|
32
|
+
export { encrypt, decrypt, isEncryptionAvailable, generateUUID } from "./crypto";
|
|
33
|
+
// ── 数据库 ──
|
|
34
|
+
export { initDatabase, initDatabaseWithHealthCheck, getOrCreateClient, createDrizzle } from "./db/connection";
|
|
35
|
+
// ── 公共 Schema ──
|
|
36
|
+
export { systemConfig, adminAuditLogs, rateLimitWindows, idempotencyKeys, apiKeys, } from "./db/schema";
|
|
37
|
+
// ── 认证 ──
|
|
38
|
+
export { signJwt, verifyJwt, extractJwt } from "./auth/jwt";
|
|
39
|
+
export { hashPassword, verifyPassword } from "./auth/password";
|
|
40
|
+
// ── 中间件 ──
|
|
41
|
+
export { createAdminAuth } from "./middleware/admin-auth";
|
|
42
|
+
export { createApiKeyAuth, extractApiKey } from "./middleware/api-key-auth";
|
|
43
|
+
// ── Bootstrap ──
|
|
44
|
+
export { bootstrap } from "./bootstrap";
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,gBAAgB;AAChB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAExH,aAAa;AACb,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,cAAc,EACd,eAAe,EACf,oBAAoB,GAErB,MAAM,YAAY,CAAC;AAEpB,WAAW;AACX,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE7C,WAAW;AACX,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAoB,MAAM,cAAc,CAAC;AAEjG,YAAY;AACZ,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEhG,aAAa;AACb,OAAO,EAAE,eAAe,EAAmB,MAAM,SAAS,CAAC;AAE3D,aAAa;AACb,OAAO,EAAE,YAAY,EAA4B,MAAM,UAAU,CAAC;AAElE,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAqB,MAAM,SAAS,CAAC;AAExF,cAAc;AACd,OAAO,EAAE,MAAM,EAAgC,MAAM,UAAU,CAAC;AAEhE,YAAY;AACZ,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEjF,YAAY;AACZ,OAAO,EAAE,YAAY,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAEpI,kBAAkB;AAClB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,WAAW;AACX,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE/D,YAAY;AACZ,OAAO,EAAE,eAAe,EAAyB,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAA8C,MAAM,2BAA2B,CAAC;AAExH,kBAAkB;AAClB,OAAO,EAAE,SAAS,EAAyB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结构化日志模块
|
|
3
|
+
*
|
|
4
|
+
* 提供 JSON 格式的结构化日志输出,便于 Workers 日志分析和 Cloudflare 日志查询。
|
|
5
|
+
*
|
|
6
|
+
* 来源:vcode src/lib/logger.ts
|
|
7
|
+
*/
|
|
8
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
9
|
+
export interface LogEntry {
|
|
10
|
+
level: LogLevel;
|
|
11
|
+
message: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare const logger: {
|
|
16
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
17
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
18
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
19
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
20
|
+
/** 业务日志 — 资源操作(凭证/订单/卡密等) */
|
|
21
|
+
resourceAction(action: string, resourceId: string, meta?: Record<string, unknown>): void;
|
|
22
|
+
/** 业务日志 — 渠道/驱动操作 */
|
|
23
|
+
channelAction(action: string, channel: string, meta?: Record<string, unknown>): void;
|
|
24
|
+
/** 业务日志 — 定时任务 */
|
|
25
|
+
cronJob(jobName: string, meta?: Record<string, unknown>): void;
|
|
26
|
+
/** 安全日志 — 认证/授权事件 */
|
|
27
|
+
security(action: string, meta?: Record<string, unknown>): void;
|
|
28
|
+
/** 审计日志 — 管理操作 */
|
|
29
|
+
audit(action: string, adminId: string, targetId?: string, meta?: Record<string, unknown>): void;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAMD,eAAO,MAAM,MAAM;mBACF,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kBAIvC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kBAItC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;mBAIrC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIrD,6BAA6B;2BACN,MAAM,cAAc,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjF,qBAAqB;0BACC,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI7E,kBAAkB;qBACD,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvD,qBAAqB;qBACJ,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvD,kBAAkB;kBACJ,MAAM,WAAW,MAAM,aAAa,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAGzF,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结构化日志模块
|
|
3
|
+
*
|
|
4
|
+
* 提供 JSON 格式的结构化日志输出,便于 Workers 日志分析和 Cloudflare 日志查询。
|
|
5
|
+
*
|
|
6
|
+
* 来源:vcode src/lib/logger.ts
|
|
7
|
+
*/
|
|
8
|
+
function createEntry(level, message, meta) {
|
|
9
|
+
return { level, message, timestamp: new Date().toISOString(), ...meta };
|
|
10
|
+
}
|
|
11
|
+
export const logger = {
|
|
12
|
+
debug(message, meta) {
|
|
13
|
+
console.log(JSON.stringify(createEntry("debug", message, meta)));
|
|
14
|
+
},
|
|
15
|
+
info(message, meta) {
|
|
16
|
+
console.log(JSON.stringify(createEntry("info", message, meta)));
|
|
17
|
+
},
|
|
18
|
+
warn(message, meta) {
|
|
19
|
+
console.warn(JSON.stringify(createEntry("warn", message, meta)));
|
|
20
|
+
},
|
|
21
|
+
error(message, meta) {
|
|
22
|
+
console.error(JSON.stringify(createEntry("error", message, meta)));
|
|
23
|
+
},
|
|
24
|
+
/** 业务日志 — 资源操作(凭证/订单/卡密等) */
|
|
25
|
+
resourceAction(action, resourceId, meta) {
|
|
26
|
+
this.info(`resource.${action}`, { resourceId, ...meta });
|
|
27
|
+
},
|
|
28
|
+
/** 业务日志 — 渠道/驱动操作 */
|
|
29
|
+
channelAction(action, channel, meta) {
|
|
30
|
+
this.info(`channel.${action}`, { channel, ...meta });
|
|
31
|
+
},
|
|
32
|
+
/** 业务日志 — 定时任务 */
|
|
33
|
+
cronJob(jobName, meta) {
|
|
34
|
+
this.info(`cron.${jobName}`, meta);
|
|
35
|
+
},
|
|
36
|
+
/** 安全日志 — 认证/授权事件 */
|
|
37
|
+
security(action, meta) {
|
|
38
|
+
this.warn(`security.${action}`, meta);
|
|
39
|
+
},
|
|
40
|
+
/** 审计日志 — 管理操作 */
|
|
41
|
+
audit(action, adminId, targetId, meta) {
|
|
42
|
+
this.info(`audit.${action}`, { adminId, targetId, ...meta });
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,SAAS,WAAW,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;IACnF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,6BAA6B;IAC7B,cAAc,CAAC,MAAc,EAAE,UAAkB,EAAE,IAA8B;QAC/E,IAAI,CAAC,IAAI,CAAC,YAAY,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,qBAAqB;IACrB,aAAa,CAAC,MAAc,EAAE,OAAe,EAAE,IAA8B;QAC3E,IAAI,CAAC,IAAI,CAAC,WAAW,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,kBAAkB;IAClB,OAAO,CAAC,OAAe,EAAE,IAA8B;QACrD,IAAI,CAAC,IAAI,CAAC,QAAQ,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,qBAAqB;IACrB,QAAQ,CAAC,MAAc,EAAE,IAA8B;QACrD,IAAI,CAAC,IAAI,CAAC,YAAY,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,kBAAkB;IAClB,KAAK,CAAC,MAAc,EAAE,OAAe,EAAE,QAAiB,EAAE,IAA8B;QACtF,IAAI,CAAC,IAAI,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员认证中间件
|
|
3
|
+
*
|
|
4
|
+
* 支持两种认证方式:
|
|
5
|
+
* 1. Bearer Token(eshop/vcode 方式):Authorization: Bearer <token>
|
|
6
|
+
* 2. 自定义 Header(xtools 方式):X-Admin-Token: <token>
|
|
7
|
+
*
|
|
8
|
+
* 安全措施:
|
|
9
|
+
* - 时序安全比较(timingSafeEqual)防时序攻击
|
|
10
|
+
* - 默认 Token 仅限本地地址使用
|
|
11
|
+
* - 未配置 ADMIN_TOKEN 时返回 503
|
|
12
|
+
*
|
|
13
|
+
* 来源:eshop requireAdmin + xtools adminAuthMiddleware 合并
|
|
14
|
+
*/
|
|
15
|
+
import type { Context, Next } from "hono";
|
|
16
|
+
export interface AdminAuthOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Token 提取方式:
|
|
19
|
+
* - "bearer" — 从 Authorization: Bearer 提取(eshop/vcode 默认)
|
|
20
|
+
* - "header" — 从 X-Admin-Token 提取(xtools 默认)
|
|
21
|
+
* - "both" — 两种方式都尝试(兼容模式)
|
|
22
|
+
*/
|
|
23
|
+
mode?: "bearer" | "header" | "both";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 创建管理员认证中间件
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // eshop/vcode 风格
|
|
30
|
+
* app.route("/admin", new Hono().use("*", createAdminAuth()).route("/", adminRoutes));
|
|
31
|
+
*
|
|
32
|
+
* // xtools 风格
|
|
33
|
+
* app.use("/api/admin/*", createAdminAuth({ mode: "header" }));
|
|
34
|
+
*/
|
|
35
|
+
export declare function createAdminAuth(options?: AdminAuthOptions): (c: Context<any>, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
36
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
37
|
+
}, 500 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">) | undefined>;
|
|
38
|
+
//# sourceMappingURL=admin-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-auth.d.ts","sourceRoot":"","sources":["../../../src/middleware/admin-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;CACrC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,IAI9C,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI;;+XA2B1C"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员认证中间件
|
|
3
|
+
*
|
|
4
|
+
* 支持两种认证方式:
|
|
5
|
+
* 1. Bearer Token(eshop/vcode 方式):Authorization: Bearer <token>
|
|
6
|
+
* 2. 自定义 Header(xtools 方式):X-Admin-Token: <token>
|
|
7
|
+
*
|
|
8
|
+
* 安全措施:
|
|
9
|
+
* - 时序安全比较(timingSafeEqual)防时序攻击
|
|
10
|
+
* - 默认 Token 仅限本地地址使用
|
|
11
|
+
* - 未配置 ADMIN_TOKEN 时返回 503
|
|
12
|
+
*
|
|
13
|
+
* 来源:eshop requireAdmin + xtools adminAuthMiddleware 合并
|
|
14
|
+
*/
|
|
15
|
+
import { fail } from "../http";
|
|
16
|
+
import { constantTimeEqual, getBearerToken } from "../security";
|
|
17
|
+
/**
|
|
18
|
+
* 创建管理员认证中间件
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // eshop/vcode 风格
|
|
22
|
+
* app.route("/admin", new Hono().use("*", createAdminAuth()).route("/", adminRoutes));
|
|
23
|
+
*
|
|
24
|
+
* // xtools 风格
|
|
25
|
+
* app.use("/api/admin/*", createAdminAuth({ mode: "header" }));
|
|
26
|
+
*/
|
|
27
|
+
export function createAdminAuth(options = {}) {
|
|
28
|
+
const { mode = "both" } = options;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
return async (c, next) => {
|
|
31
|
+
const expected = c.env.ADMIN_TOKEN;
|
|
32
|
+
if (!expected)
|
|
33
|
+
return fail(c, "管理员令牌未配置", 503);
|
|
34
|
+
// 安全检查:默认 Token 仅限本地
|
|
35
|
+
const hostname = new URL(c.req.url).hostname;
|
|
36
|
+
if (expected === "dev-only-change-me" &&
|
|
37
|
+
!["127.0.0.1", "localhost", "::1"].includes(hostname)) {
|
|
38
|
+
return fail(c, "生产环境必须配置 ADMIN_TOKEN", 503);
|
|
39
|
+
}
|
|
40
|
+
// 提取 Token
|
|
41
|
+
let actual = "";
|
|
42
|
+
if (mode === "bearer" || mode === "both") {
|
|
43
|
+
actual = getBearerToken(c);
|
|
44
|
+
}
|
|
45
|
+
if (!actual && (mode === "header" || mode === "both")) {
|
|
46
|
+
actual = c.req.header("X-Admin-Token") || "";
|
|
47
|
+
}
|
|
48
|
+
if (!actual)
|
|
49
|
+
return fail(c, "未授权", 401);
|
|
50
|
+
if (!constantTimeEqual(expected, actual))
|
|
51
|
+
return fail(c, "未授权", 401);
|
|
52
|
+
await next();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=admin-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-auth.js","sourceRoot":"","sources":["../../../src/middleware/admin-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYhE;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,UAA4B,EAAE;IAC5D,MAAM,EAAE,IAAI,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAElC,8DAA8D;IAC9D,OAAO,KAAK,EAAE,CAAe,EAAE,IAAU,EAAE,EAAE;QAC3C,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;QAE/C,qBAAqB;QACrB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QAC7C,IACE,QAAQ,KAAK,oBAAoB;YACjC,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EACrD,CAAC;YACD,OAAO,IAAI,CAAC,CAAC,EAAE,sBAAsB,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;QAED,WAAW;QACX,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACzC,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACtD,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAErE,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key 认证中间件
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - SHA-256 哈希存储(不存明文)
|
|
6
|
+
* - 月度配额 + 原子递增(消除 TOCTOU 竞态)
|
|
7
|
+
* - 支持 Bearer 和自定义 Header 提取
|
|
8
|
+
* - 分级管理(free/basic/pro/enterprise)
|
|
9
|
+
*
|
|
10
|
+
* 来源:xtools + vcode api-auth.ts 合并
|
|
11
|
+
*/
|
|
12
|
+
import type { Context, Next } from "hono";
|
|
13
|
+
export interface ApiKeyContext {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
tier: string;
|
|
17
|
+
userId: string;
|
|
18
|
+
monthlyQuota: number;
|
|
19
|
+
monthlyUsage: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ApiKeyAuthOptions {
|
|
22
|
+
/** Key 前缀(如 "xtools_"、"vcode_"),默认不限制 */
|
|
23
|
+
prefix?: string;
|
|
24
|
+
/** 是否必须认证,默认 true */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
/** 自定义变量名(注入到 Hono Variables),默认 "apiKeyContext" */
|
|
27
|
+
variableName?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 从请求中提取 API Key
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractApiKey(c: Context, prefix?: string): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* 创建 API Key 认证中间件
|
|
35
|
+
*/
|
|
36
|
+
export declare function createApiKeyAuth(options?: ApiKeyAuthOptions): (c: Context<{
|
|
37
|
+
Bindings: Record<string, unknown>;
|
|
38
|
+
Variables: Record<string, unknown>;
|
|
39
|
+
}>, next: Next) => Promise<(Response & import("hono").TypedResponse<{
|
|
40
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
41
|
+
}, 500 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">) | undefined>;
|
|
42
|
+
//# sourceMappingURL=api-key-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth.d.ts","sourceRoot":"","sources":["../../../src/middleware/api-key-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM1C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AA8BD,MAAM,WAAW,iBAAiB;IAChC,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBxE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,IAI5D,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EACrF,MAAM,IAAI;;+XA+Db"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key 认证中间件
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - SHA-256 哈希存储(不存明文)
|
|
6
|
+
* - 月度配额 + 原子递增(消除 TOCTOU 竞态)
|
|
7
|
+
* - 支持 Bearer 和自定义 Header 提取
|
|
8
|
+
* - 分级管理(free/basic/pro/enterprise)
|
|
9
|
+
*
|
|
10
|
+
* 来源:xtools + vcode api-auth.ts 合并
|
|
11
|
+
*/
|
|
12
|
+
import { fail } from "../http";
|
|
13
|
+
import { sha256 } from "../security";
|
|
14
|
+
import { apiKeys } from "../db/schema";
|
|
15
|
+
import { eq, and, sql } from "drizzle-orm";
|
|
16
|
+
/**
|
|
17
|
+
* 从请求中提取 API Key
|
|
18
|
+
*/
|
|
19
|
+
export function extractApiKey(c, prefix) {
|
|
20
|
+
// 1. Authorization: Bearer <key>
|
|
21
|
+
const auth = c.req.header("Authorization");
|
|
22
|
+
if (auth) {
|
|
23
|
+
const parts = auth.split(" ");
|
|
24
|
+
if (parts.length === 2) {
|
|
25
|
+
const [scheme, credentials] = parts;
|
|
26
|
+
if ((scheme.toLowerCase() === "bearer" || scheme.toLowerCase() === "token") &&
|
|
27
|
+
(!prefix || credentials.startsWith(prefix))) {
|
|
28
|
+
return credentials;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// 2. X-API-Key header
|
|
33
|
+
const apiKey = c.req.header("X-API-Key");
|
|
34
|
+
if (apiKey && (!prefix || apiKey.startsWith(prefix))) {
|
|
35
|
+
return apiKey;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 创建 API Key 认证中间件
|
|
41
|
+
*/
|
|
42
|
+
export function createApiKeyAuth(options = {}) {
|
|
43
|
+
const { prefix, required = true, variableName = "apiKeyContext" } = options;
|
|
44
|
+
return async (c, next) => {
|
|
45
|
+
const key = extractApiKey(c, prefix);
|
|
46
|
+
if (!key) {
|
|
47
|
+
if (required)
|
|
48
|
+
return fail(c, "缺少 API Key", 401);
|
|
49
|
+
await next();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const db = c.get("db");
|
|
53
|
+
if (!db)
|
|
54
|
+
return fail(c, "数据库不可用", 503);
|
|
55
|
+
const keyHash = await sha256(key);
|
|
56
|
+
const rows = await db
|
|
57
|
+
.select()
|
|
58
|
+
.from(apiKeys)
|
|
59
|
+
.where(eq(apiKeys.keyHash, keyHash))
|
|
60
|
+
.limit(1);
|
|
61
|
+
if (rows.length === 0)
|
|
62
|
+
return fail(c, "API Key 无效", 401);
|
|
63
|
+
const record = rows[0];
|
|
64
|
+
if (!record.enabled)
|
|
65
|
+
return fail(c, "API Key 已禁用", 401);
|
|
66
|
+
if (record.expiresAt && new Date(record.expiresAt) < new Date()) {
|
|
67
|
+
return fail(c, "API Key 已过期", 401);
|
|
68
|
+
}
|
|
69
|
+
// 原子配额检查 + 递增
|
|
70
|
+
if (record.monthlyQuota > 0) {
|
|
71
|
+
const updated = await db
|
|
72
|
+
.update(apiKeys)
|
|
73
|
+
.set({
|
|
74
|
+
monthlyUsage: sql `monthly_usage + 1`,
|
|
75
|
+
lastUsedAt: new Date().toISOString(),
|
|
76
|
+
})
|
|
77
|
+
.where(and(eq(apiKeys.id, record.id), sql `monthly_usage < monthly_quota`))
|
|
78
|
+
.returning();
|
|
79
|
+
if (updated.length === 0)
|
|
80
|
+
return fail(c, "已超过月度配额", 429);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await db
|
|
84
|
+
.update(apiKeys)
|
|
85
|
+
.set({
|
|
86
|
+
monthlyUsage: sql `monthly_usage + 1`,
|
|
87
|
+
lastUsedAt: new Date().toISOString(),
|
|
88
|
+
})
|
|
89
|
+
.where(eq(apiKeys.id, record.id));
|
|
90
|
+
}
|
|
91
|
+
const context = {
|
|
92
|
+
id: record.id,
|
|
93
|
+
name: record.name,
|
|
94
|
+
tier: record.tier || "free",
|
|
95
|
+
userId: record.userId,
|
|
96
|
+
monthlyQuota: record.monthlyQuota,
|
|
97
|
+
monthlyUsage: record.monthlyUsage,
|
|
98
|
+
};
|
|
99
|
+
c.set(variableName, context);
|
|
100
|
+
c.set("userId", record.userId || record.id);
|
|
101
|
+
await next();
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=api-key-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth.js","sourceRoot":"","sources":["../../../src/middleware/api-key-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAyB,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAA8C,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一限流模块 — 支持 DB / KV / 内存 三种存储后端
|
|
3
|
+
*
|
|
4
|
+
* 三项目的限流实现各有特点:
|
|
5
|
+
* - eshop: DB 版(原子 upsert,最可靠,适合无 KV 的项目)
|
|
6
|
+
* - xtools: KV 版(滑动窗口,跨实例共享)
|
|
7
|
+
* - vcode: 内存版(最轻量,但重启丢失)
|
|
8
|
+
*
|
|
9
|
+
* 本模块统一接口,项目按自己的基础设施选择后端。
|
|
10
|
+
*/
|
|
11
|
+
import type { RateLimitResult } from "./types";
|
|
12
|
+
export interface RateLimiter {
|
|
13
|
+
check(key: string, limit: number, windowMs: number): Promise<RateLimitResult>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 内存固定窗口限流器
|
|
17
|
+
*
|
|
18
|
+
* Workers 实例级别,重启后重置。
|
|
19
|
+
* 适合请求量不大、对精确性要求不高的场景。
|
|
20
|
+
*/
|
|
21
|
+
export declare class MemoryRateLimiter implements RateLimiter {
|
|
22
|
+
private store;
|
|
23
|
+
check(key: string, limit: number, windowMs: number): Promise<RateLimitResult>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* KV 滑动窗口限流器
|
|
27
|
+
*
|
|
28
|
+
* 使用 Cloudflare KV 存储,跨 Workers 实例共享状态。
|
|
29
|
+
* 滑动窗口算法,避免固定窗口边界突发。
|
|
30
|
+
*
|
|
31
|
+
* 来源:xtools src/lib/rate-limiter.ts
|
|
32
|
+
*/
|
|
33
|
+
export declare class KvRateLimiter implements RateLimiter {
|
|
34
|
+
private kv;
|
|
35
|
+
private prefix;
|
|
36
|
+
constructor(kv: KVNamespace, prefix?: string);
|
|
37
|
+
check(key: string, limit: number, windowMs: number): Promise<RateLimitResult>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 数据库固定窗口限流器
|
|
41
|
+
*
|
|
42
|
+
* 使用 rate_limit_windows 表,通过原子 upsert(INSERT ON CONFLICT UPDATE)实现。
|
|
43
|
+
* UPDATE 自带 WHERE request_count < :limit 条件,确保计数器永远不会超过阈值,
|
|
44
|
+
* 从根本上消除高并发场景下的竞态条件。
|
|
45
|
+
*
|
|
46
|
+
* 来源:eshop src/lib/rate-limit.ts(竞态修复版)
|
|
47
|
+
*/
|
|
48
|
+
export declare class DbRateLimiter implements RateLimiter {
|
|
49
|
+
private db;
|
|
50
|
+
private tableName;
|
|
51
|
+
constructor(db: DbRateLimiter["db"]);
|
|
52
|
+
check(key: string, limit: number, windowMs: number): Promise<RateLimitResult>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAM/C,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC/E;AAMD;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,WAAW;IACnD,OAAO,CAAC,KAAK,CAAyD;IAEhE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAiBpF;AAMD;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,EAAE,EAAE,WAAW,EAAE,MAAM,SAAS;IAKtC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CA2BpF;AAMD;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,OAAO,CAAC,EAAE,CAER;IACF,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC;IAK7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAsCpF"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一限流模块 — 支持 DB / KV / 内存 三种存储后端
|
|
3
|
+
*
|
|
4
|
+
* 三项目的限流实现各有特点:
|
|
5
|
+
* - eshop: DB 版(原子 upsert,最可靠,适合无 KV 的项目)
|
|
6
|
+
* - xtools: KV 版(滑动窗口,跨实例共享)
|
|
7
|
+
* - vcode: 内存版(最轻量,但重启丢失)
|
|
8
|
+
*
|
|
9
|
+
* 本模块统一接口,项目按自己的基础设施选择后端。
|
|
10
|
+
*/
|
|
11
|
+
import { sql } from "drizzle-orm";
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
|
+
// 内存版(最轻量 — 适合 vcode 类项目)
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
/**
|
|
16
|
+
* 内存固定窗口限流器
|
|
17
|
+
*
|
|
18
|
+
* Workers 实例级别,重启后重置。
|
|
19
|
+
* 适合请求量不大、对精确性要求不高的场景。
|
|
20
|
+
*/
|
|
21
|
+
export class MemoryRateLimiter {
|
|
22
|
+
store = new Map();
|
|
23
|
+
async check(key, limit, windowMs) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const record = this.store.get(key);
|
|
26
|
+
if (!record || now > record.resetAt) {
|
|
27
|
+
this.store.set(key, { count: 1, resetAt: now + windowMs });
|
|
28
|
+
return { ok: true, remaining: limit - 1 };
|
|
29
|
+
}
|
|
30
|
+
if (record.count >= limit) {
|
|
31
|
+
const resetMs = Math.max(0, record.resetAt - now);
|
|
32
|
+
return { ok: false, message: "请求过于频繁,请稍后再试", status: 429, resetMs };
|
|
33
|
+
}
|
|
34
|
+
record.count++;
|
|
35
|
+
return { ok: true, remaining: limit - record.count };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
39
|
+
// KV 版(跨实例共享 — 适合 xtools 类项目)
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
41
|
+
/**
|
|
42
|
+
* KV 滑动窗口限流器
|
|
43
|
+
*
|
|
44
|
+
* 使用 Cloudflare KV 存储,跨 Workers 实例共享状态。
|
|
45
|
+
* 滑动窗口算法,避免固定窗口边界突发。
|
|
46
|
+
*
|
|
47
|
+
* 来源:xtools src/lib/rate-limiter.ts
|
|
48
|
+
*/
|
|
49
|
+
export class KvRateLimiter {
|
|
50
|
+
kv;
|
|
51
|
+
prefix;
|
|
52
|
+
constructor(kv, prefix = "rate") {
|
|
53
|
+
this.kv = kv;
|
|
54
|
+
this.prefix = prefix;
|
|
55
|
+
}
|
|
56
|
+
async check(key, limit, windowMs) {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const windowStart = now - windowMs;
|
|
59
|
+
const kvKey = `${this.prefix}:${key}`;
|
|
60
|
+
try {
|
|
61
|
+
const data = await this.kv.get(kvKey, "json");
|
|
62
|
+
const timestamps = Array.isArray(data) ? data : [];
|
|
63
|
+
const valid = timestamps.filter((ts) => ts > windowStart);
|
|
64
|
+
if (valid.length >= limit) {
|
|
65
|
+
const oldest = Math.min(...valid);
|
|
66
|
+
const resetMs = Math.max(0, oldest + windowMs - now);
|
|
67
|
+
return { ok: false, message: "请求过于频繁,请稍后再试", status: 429, remaining: 0, resetMs };
|
|
68
|
+
}
|
|
69
|
+
valid.push(now);
|
|
70
|
+
const ttlSeconds = Math.ceil(windowMs / 1000) + 10;
|
|
71
|
+
await this.kv.put(kvKey, JSON.stringify(valid), { expirationTtl: ttlSeconds });
|
|
72
|
+
const remaining = Math.max(0, limit - valid.length);
|
|
73
|
+
return { ok: true, remaining };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.warn("[KvRateLimiter] KV error, failing open:", err instanceof Error ? err.message : String(err));
|
|
77
|
+
return { ok: true, remaining: limit };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
82
|
+
// DB 版(最可靠 — 适合 eshop 类项目)
|
|
83
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
84
|
+
/**
|
|
85
|
+
* 数据库固定窗口限流器
|
|
86
|
+
*
|
|
87
|
+
* 使用 rate_limit_windows 表,通过原子 upsert(INSERT ON CONFLICT UPDATE)实现。
|
|
88
|
+
* UPDATE 自带 WHERE request_count < :limit 条件,确保计数器永远不会超过阈值,
|
|
89
|
+
* 从根本上消除高并发场景下的竞态条件。
|
|
90
|
+
*
|
|
91
|
+
* 来源:eshop src/lib/rate-limit.ts(竞态修复版)
|
|
92
|
+
*/
|
|
93
|
+
export class DbRateLimiter {
|
|
94
|
+
db;
|
|
95
|
+
tableName;
|
|
96
|
+
constructor(db) {
|
|
97
|
+
this.db = db;
|
|
98
|
+
this.tableName = "rate_limit_windows";
|
|
99
|
+
}
|
|
100
|
+
async check(key, limit, windowMs) {
|
|
101
|
+
const now = Math.floor(Date.now() / 1000);
|
|
102
|
+
const windowSeconds = Math.floor(windowMs / 1000);
|
|
103
|
+
const windowStart = Math.floor(now / windowSeconds) * windowSeconds;
|
|
104
|
+
try {
|
|
105
|
+
// 使用 raw SQL + WHERE 条件保证计数器不会超过 limit:
|
|
106
|
+
// - 首次插入: request_count = 1
|
|
107
|
+
// - 后续更新: request_count += 1,但 WHERE request_count < :limit 阻止超额
|
|
108
|
+
const result = await this.db.execute(sql `INSERT INTO ${sql.identifier(this.tableName)} (action, ip_hash, window_start, request_count)
|
|
109
|
+
VALUES (${key}, '', ${windowStart}, 1)
|
|
110
|
+
ON CONFLICT(action, ip_hash, window_start) DO UPDATE SET
|
|
111
|
+
request_count = request_count + 1
|
|
112
|
+
WHERE rate_limit_windows.request_count < ${limit}
|
|
113
|
+
RETURNING request_count`);
|
|
114
|
+
const currentCount = result.rows?.[0]?.request_count ?? 0;
|
|
115
|
+
// 如果 WHERE 条件不满足(count >= limit),UPDATE 被跳过,返回旧值
|
|
116
|
+
if (currentCount > limit) {
|
|
117
|
+
const resetInSeconds = windowStart + windowSeconds - now;
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
message: "请求过于频繁,请稍后再试",
|
|
121
|
+
status: 429,
|
|
122
|
+
remaining: 0,
|
|
123
|
+
resetMs: Math.max(0, resetInSeconds * 1000),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { ok: true, remaining: Math.max(0, limit - currentCount) };
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// 限流失败时 fail-open 以保持服务可用
|
|
130
|
+
return { ok: true, remaining: limit };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/rate-limit.ts"],"names":[],"mappings":"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"}
|