@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.
Files changed (169) hide show
  1. package/README.md +88 -0
  2. package/dist/features/anti-abuse/index.d.ts +62 -0
  3. package/dist/features/anti-abuse/index.d.ts.map +1 -0
  4. package/dist/features/anti-abuse/index.js +139 -0
  5. package/dist/features/anti-abuse/index.js.map +1 -0
  6. package/dist/features/email/index.d.ts +68 -0
  7. package/dist/features/email/index.d.ts.map +1 -0
  8. package/dist/features/email/index.js +120 -0
  9. package/dist/features/email/index.js.map +1 -0
  10. package/dist/features/payment/fetch-utils.d.ts +17 -0
  11. package/dist/features/payment/fetch-utils.d.ts.map +1 -0
  12. package/dist/features/payment/fetch-utils.js +56 -0
  13. package/dist/features/payment/fetch-utils.js.map +1 -0
  14. package/dist/features/payment/index.d.ts +20 -0
  15. package/dist/features/payment/index.d.ts.map +1 -0
  16. package/dist/features/payment/index.js +21 -0
  17. package/dist/features/payment/index.js.map +1 -0
  18. package/dist/features/payment/providers/alipay.d.ts +30 -0
  19. package/dist/features/payment/providers/alipay.d.ts.map +1 -0
  20. package/dist/features/payment/providers/alipay.js +149 -0
  21. package/dist/features/payment/providers/alipay.js.map +1 -0
  22. package/dist/features/payment/providers/stripe.d.ts +34 -0
  23. package/dist/features/payment/providers/stripe.d.ts.map +1 -0
  24. package/dist/features/payment/providers/stripe.js +168 -0
  25. package/dist/features/payment/providers/stripe.js.map +1 -0
  26. package/dist/features/payment/providers/trc20.d.ts +24 -0
  27. package/dist/features/payment/providers/trc20.d.ts.map +1 -0
  28. package/dist/features/payment/providers/trc20.js +96 -0
  29. package/dist/features/payment/providers/trc20.js.map +1 -0
  30. package/dist/features/payment/registry.d.ts +43 -0
  31. package/dist/features/payment/registry.d.ts.map +1 -0
  32. package/dist/features/payment/registry.js +65 -0
  33. package/dist/features/payment/registry.js.map +1 -0
  34. package/dist/features/payment/types.d.ts +72 -0
  35. package/dist/features/payment/types.d.ts.map +1 -0
  36. package/dist/features/payment/types.js +8 -0
  37. package/dist/features/payment/types.js.map +1 -0
  38. package/dist/features/thompson-router/index.d.ts +101 -0
  39. package/dist/features/thompson-router/index.d.ts.map +1 -0
  40. package/dist/features/thompson-router/index.js +186 -0
  41. package/dist/features/thompson-router/index.js.map +1 -0
  42. package/dist/features/webhook/index.d.ts +76 -0
  43. package/dist/features/webhook/index.d.ts.map +1 -0
  44. package/dist/features/webhook/index.js +127 -0
  45. package/dist/features/webhook/index.js.map +1 -0
  46. package/dist/src/audit.d.ts +45 -0
  47. package/dist/src/audit.d.ts.map +1 -0
  48. package/dist/src/audit.js +40 -0
  49. package/dist/src/audit.js.map +1 -0
  50. package/dist/src/auth/jwt.d.ts +33 -0
  51. package/dist/src/auth/jwt.d.ts.map +1 -0
  52. package/dist/src/auth/jwt.js +87 -0
  53. package/dist/src/auth/jwt.js.map +1 -0
  54. package/dist/src/auth/password.d.ts +26 -0
  55. package/dist/src/auth/password.d.ts.map +1 -0
  56. package/dist/src/auth/password.js +52 -0
  57. package/dist/src/auth/password.js.map +1 -0
  58. package/dist/src/bootstrap.d.ts +74 -0
  59. package/dist/src/bootstrap.d.ts.map +1 -0
  60. package/dist/src/bootstrap.js +231 -0
  61. package/dist/src/bootstrap.js.map +1 -0
  62. package/dist/src/cache.d.ts +52 -0
  63. package/dist/src/cache.d.ts.map +1 -0
  64. package/dist/src/cache.js +76 -0
  65. package/dist/src/cache.js.map +1 -0
  66. package/dist/src/config.d.ts +83 -0
  67. package/dist/src/config.d.ts.map +1 -0
  68. package/dist/src/config.js +96 -0
  69. package/dist/src/config.js.map +1 -0
  70. package/dist/src/crypto.d.ts +33 -0
  71. package/dist/src/crypto.d.ts.map +1 -0
  72. package/dist/src/crypto.js +87 -0
  73. package/dist/src/crypto.js.map +1 -0
  74. package/dist/src/db/connection.d.ts +53 -0
  75. package/dist/src/db/connection.d.ts.map +1 -0
  76. package/dist/src/db/connection.js +104 -0
  77. package/dist/src/db/connection.js.map +1 -0
  78. package/dist/src/db/index.d.ts +6 -0
  79. package/dist/src/db/index.d.ts.map +1 -0
  80. package/dist/src/db/index.js +6 -0
  81. package/dist/src/db/index.js.map +1 -0
  82. package/dist/src/db/schema.d.ts +649 -0
  83. package/dist/src/db/schema.d.ts.map +1 -0
  84. package/dist/src/db/schema.js +76 -0
  85. package/dist/src/db/schema.js.map +1 -0
  86. package/dist/src/error.d.ts +47 -0
  87. package/dist/src/error.d.ts.map +1 -0
  88. package/dist/src/error.js +94 -0
  89. package/dist/src/error.js.map +1 -0
  90. package/dist/src/http.d.ts +83 -0
  91. package/dist/src/http.d.ts.map +1 -0
  92. package/dist/src/http.js +116 -0
  93. package/dist/src/http.js.map +1 -0
  94. package/dist/src/idempotency.d.ts +78 -0
  95. package/dist/src/idempotency.d.ts.map +1 -0
  96. package/dist/src/idempotency.js +84 -0
  97. package/dist/src/idempotency.js.map +1 -0
  98. package/dist/src/index.d.ts +31 -0
  99. package/dist/src/index.d.ts.map +1 -0
  100. package/dist/src/index.js +45 -0
  101. package/dist/src/index.js.map +1 -0
  102. package/dist/src/logger.d.ts +31 -0
  103. package/dist/src/logger.d.ts.map +1 -0
  104. package/dist/src/logger.js +45 -0
  105. package/dist/src/logger.js.map +1 -0
  106. package/dist/src/middleware/admin-auth.d.ts +38 -0
  107. package/dist/src/middleware/admin-auth.d.ts.map +1 -0
  108. package/dist/src/middleware/admin-auth.js +55 -0
  109. package/dist/src/middleware/admin-auth.js.map +1 -0
  110. package/dist/src/middleware/api-key-auth.d.ts +42 -0
  111. package/dist/src/middleware/api-key-auth.d.ts.map +1 -0
  112. package/dist/src/middleware/api-key-auth.js +104 -0
  113. package/dist/src/middleware/api-key-auth.js.map +1 -0
  114. package/dist/src/middleware/index.d.ts +3 -0
  115. package/dist/src/middleware/index.d.ts.map +1 -0
  116. package/dist/src/middleware/index.js +3 -0
  117. package/dist/src/middleware/index.js.map +1 -0
  118. package/dist/src/rate-limit.d.ts +54 -0
  119. package/dist/src/rate-limit.d.ts.map +1 -0
  120. package/dist/src/rate-limit.js +134 -0
  121. package/dist/src/rate-limit.js.map +1 -0
  122. package/dist/src/security.d.ts +78 -0
  123. package/dist/src/security.d.ts.map +1 -0
  124. package/dist/src/security.js +175 -0
  125. package/dist/src/security.js.map +1 -0
  126. package/dist/src/types.d.ts +64 -0
  127. package/dist/src/types.d.ts.map +1 -0
  128. package/dist/src/types.js +8 -0
  129. package/dist/src/types.js.map +1 -0
  130. package/features/anti-abuse/index.ts +180 -0
  131. package/features/anti-abuse/tests/index.test.ts +50 -0
  132. package/features/email/index.ts +172 -0
  133. package/features/email/tests/index.test.ts +44 -0
  134. package/features/payment/fetch-utils.ts +65 -0
  135. package/features/payment/index.ts +39 -0
  136. package/features/payment/providers/alipay.ts +171 -0
  137. package/features/payment/providers/stripe.ts +192 -0
  138. package/features/payment/providers/trc20.ts +115 -0
  139. package/features/payment/registry.ts +87 -0
  140. package/features/payment/tests/index.test.ts +506 -0
  141. package/features/payment/types.ts +93 -0
  142. package/features/telegram-miniapp/index.ts +109 -0
  143. package/features/telegram-miniapp/tests/index.test.ts +11 -0
  144. package/features/thompson-router/index.ts +243 -0
  145. package/features/thompson-router/tests/index.test.ts +93 -0
  146. package/features/webhook/index.ts +183 -0
  147. package/features/webhook/tests/index.test.ts +21 -0
  148. package/package.json +202 -0
  149. package/src/audit.ts +70 -0
  150. package/src/auth/jwt.ts +114 -0
  151. package/src/auth/password.ts +75 -0
  152. package/src/bootstrap.ts +322 -0
  153. package/src/cache.ts +78 -0
  154. package/src/config.ts +134 -0
  155. package/src/crypto.ts +106 -0
  156. package/src/db/connection.ts +127 -0
  157. package/src/db/index.ts +6 -0
  158. package/src/db/schema.ts +90 -0
  159. package/src/error.ts +125 -0
  160. package/src/http.ts +150 -0
  161. package/src/idempotency.ts +127 -0
  162. package/src/index.ts +85 -0
  163. package/src/logger.ts +63 -0
  164. package/src/middleware/admin-auth.ts +71 -0
  165. package/src/middleware/api-key-auth.ts +164 -0
  166. package/src/middleware/index.ts +2 -0
  167. package/src/rate-limit.ts +167 -0
  168. package/src/security.ts +219 -0
  169. package/src/types.ts +70 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * 安全工具模块
3
+ *
4
+ * 提供 SHA-256 哈希、IP 哈希(加盐)、时序安全比较、Turnstile 人机验证、安全响应头。
5
+ * 合并自 eshop/xtools/vcode 三项目的 security.ts,取各版本之长。
6
+ *
7
+ * 设计要点:
8
+ * - 纯函数/泛型 Context,不绑定特定项目的 AppEnv
9
+ * - 所有加密操作使用 Web Crypto API(Workers 原生)
10
+ * - IP 仅信任 cf-connecting-ip(CF 边缘注入,客户端无法伪造)
11
+ */
12
+
13
+ import type { Context } from "hono";
14
+ import type { TurnstileResult } from "./types";
15
+
16
+ const encoder = new TextEncoder();
17
+
18
+ // ═══════════════════════════════════════════════════════════════════════════════
19
+ // 基础密码学工具
20
+ // ═══════════════════════════════════════════════════════════════════════════════
21
+
22
+ /**
23
+ * 对字符串进行 SHA-256 哈希,返回 64 位十六进制字符串。
24
+ */
25
+ export async function sha256(input: string): Promise<string> {
26
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(input));
27
+ return [...new Uint8Array(digest)]
28
+ .map((b) => b.toString(16).padStart(2, "0"))
29
+ .join("");
30
+ }
31
+
32
+ /**
33
+ * 恒定时间字符串比较 — 防止时序攻击。
34
+ *
35
+ * 使用 crypto.subtle.timingSafeEqual 比较两个字符串的 UTF-8 字节序列。
36
+ * 长度不匹配时回退到手写比较(仍为恒定时间),避免长度泄露信息。
37
+ *
38
+ * 来源:eshop(crypto.subtle 版)+ xtools(手写 XOR 版)合并
39
+ */
40
+ export function constantTimeEqual(a: string, b: string): boolean {
41
+ const aBuf = encoder.encode(a);
42
+ const bBuf = encoder.encode(b);
43
+ // 手写 XOR 比较(恒定时间,兼容 Node.js 和 Workers)
44
+ if (aBuf.byteLength !== bBuf.byteLength) return false;
45
+ let diff = 0;
46
+ for (let i = 0; i < aBuf.byteLength; i++) {
47
+ diff |= aBuf[i] ^ bBuf[i];
48
+ }
49
+ return diff === 0;
50
+ }
51
+
52
+ // ═══════════════════════════════════════════════════════════════════════════════
53
+ // IP 哈希
54
+ // ═══════════════════════════════════════════════════════════════════════════════
55
+
56
+ /**
57
+ * 获取客户端 IP 的加盐 SHA-256 哈希。
58
+ *
59
+ * - 仅信任 cf-connecting-ip(CF 边缘注入)
60
+ * - 非 CF 环境降级使用 x-forwarded-for + "dev:" 前缀
61
+ * - 加盐防止反向查找
62
+ *
63
+ * @param c - Hono Context
64
+ * @param salt - 盐值,默认从 env.RATE_LIMIT_SALT 读取
65
+ */
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ export async function getIpHash(
68
+ c: Context<any>,
69
+ salt?: string,
70
+ ): Promise<string> {
71
+ const cfIp = c.req.header("cf-connecting-ip");
72
+ let ip: string;
73
+ if (cfIp) {
74
+ ip = cfIp;
75
+ } else {
76
+ ip = "dev:" + (c.req.header("x-forwarded-for") || "0.0.0.0");
77
+ }
78
+ const actualSalt = salt ?? c.env.RATE_LIMIT_SALT ?? "cf-core-salt";
79
+ return sha256(`${actualSalt}:${ip}`);
80
+ }
81
+
82
+ /**
83
+ * 获取原始客户端 IP(用于日志,不做隐私存储时可用)
84
+ */
85
+ export function getClientIp(c: Context): string {
86
+ const cfIp = c.req.header("cf-connecting-ip");
87
+ if (cfIp) return cfIp;
88
+ const xff = c.req.header("x-forwarded-for");
89
+ return xff ? xff.split(",")[0].trim() : "unknown";
90
+ }
91
+
92
+ // ═══════════════════════════════════════════════════════════════════════════════
93
+ // Bearer Token 提取
94
+ // ═══════════════════════════════════════════════════════════════════════════════
95
+
96
+ /**
97
+ * 从 Authorization 请求头提取 Bearer Token。
98
+ * 格式:Authorization: Bearer <token>
99
+ */
100
+ export function getBearerToken(c: Context): string {
101
+ const auth = c.req.header("authorization") || "";
102
+ const match = auth.match(/^Bearer\s+(.+)$/i);
103
+ return match?.[1]?.trim() || "";
104
+ }
105
+
106
+ // ═══════════════════════════════════════════════════════════════════════════════
107
+ // Turnstile 人机验证
108
+ // ═══════════════════════════════════════════════════════════════════════════════
109
+
110
+ /**
111
+ * Cloudflare Turnstile 验证。
112
+ *
113
+ * - 未配置 TURNSTILE_SECRET_KEY 时直接放行(分阶段部署)
114
+ * - 无 token 时静默通过(smoke 测试/管理端调用)
115
+ * - 验证失败返回 { ok: false, message }
116
+ *
117
+ * 来源:eshop(FormData 版)+ vcode(urlencoded 版)合并为 FormData 版(更规范)
118
+ */
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ export async function verifyTurnstile(
121
+ c: Context<any>,
122
+ token?: string,
123
+ ): Promise<TurnstileResult> {
124
+ const secret = c.env.TURNSTILE_SECRET_KEY;
125
+ if (!secret) return { ok: true };
126
+ if (!token) return { ok: true };
127
+
128
+ const form = new FormData();
129
+ form.append("secret", secret);
130
+ form.append("response", token);
131
+ const ip = c.req.header("cf-connecting-ip");
132
+ if (ip) form.append("remoteip", ip);
133
+
134
+ try {
135
+ const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
136
+ method: "POST",
137
+ body: form,
138
+ });
139
+ const data = await response.json<{ success?: boolean; "error-codes"?: string[] }>();
140
+ if (!data.success) {
141
+ console.warn("[turnstile] verification failed", {
142
+ errorCodes: data["error-codes"] || [],
143
+ });
144
+ return { ok: false, message: "人机验证失败" };
145
+ }
146
+ return { ok: true };
147
+ } catch (err) {
148
+ console.error("[turnstile] fetch error:", err);
149
+ return { ok: true };
150
+ }
151
+ }
152
+
153
+ // ═══════════════════════════════════════════════════════════════════════════════
154
+ // 安全响应头
155
+ // ═══════════════════════════════════════════════════════════════════════════════
156
+
157
+ export interface SecurityHeadersOptions {
158
+ /** CSP 额外 script-src(如 CDN 域名) */
159
+ extraScriptSrc?: string[];
160
+ /** CSP 额外 style-src */
161
+ extraStyleSrc?: string[];
162
+ /** CSP 额外 connect-src */
163
+ extraConnectSrc?: string[];
164
+ /** CSP 额外 font-src */
165
+ extraFontSrc?: string[];
166
+ /** 是否允许 unsafe-eval(admin 页面的 Vue UMD 需要) */
167
+ allowUnsafeEval?: boolean;
168
+ /** 是否允许 Telegram WebApp SDK */
169
+ allowTelegram?: boolean;
170
+ }
171
+
172
+ /**
173
+ * 生成安全响应头(CSP + 安全头)
174
+ *
175
+ * 合并三项目不同的 CSP 策略为统一配置接口。
176
+ * 返回 Headers 对象,可直接合并到响应中。
177
+ */
178
+ export function buildSecurityHeaders(options: SecurityHeadersOptions = {}): Headers {
179
+ const {
180
+ extraScriptSrc = [],
181
+ extraStyleSrc = [],
182
+ extraConnectSrc = [],
183
+ extraFontSrc = [],
184
+ allowUnsafeEval = false,
185
+ allowTelegram = false,
186
+ } = options;
187
+
188
+ const scriptSrc = [
189
+ "'self'",
190
+ "'unsafe-inline'",
191
+ "https://unpkg.com",
192
+ "https://static.cloudflareinsights.com",
193
+ "https://challenges.cloudflare.com",
194
+ ...(allowUnsafeEval ? ["'unsafe-eval'"] : []),
195
+ ...(allowTelegram ? ["https://telegram.org"] : []),
196
+ ...extraScriptSrc,
197
+ ];
198
+
199
+ const csp = [
200
+ "default-src 'self'",
201
+ `style-src 'self' 'unsafe-inline' https://unpkg.com ${extraStyleSrc.join(" ")}`.trim(),
202
+ `script-src ${scriptSrc.join(" ")}`,
203
+ "img-src 'self' data: https:",
204
+ `connect-src 'self' https://unpkg.com https://challenges.cloudflare.com https://static.cloudflareinsights.com ${extraConnectSrc.join(" ")}`.trim(),
205
+ "frame-src 'self' https://challenges.cloudflare.com",
206
+ "object-src 'none'",
207
+ "base-uri 'self'",
208
+ ...(extraFontSrc.length > 0 ? [`font-src 'self' ${extraFontSrc.join(" ")}`] : []),
209
+ ].join("; ");
210
+
211
+ return new Headers({
212
+ "Content-Security-Policy": csp,
213
+ "X-Content-Type-Options": "nosniff",
214
+ "X-Frame-Options": "DENY",
215
+ "Referrer-Policy": "strict-origin-when-cross-origin",
216
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()",
217
+ "Cross-Origin-Opener-Policy": "same-origin",
218
+ });
219
+ }
package/src/types.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @usethink/cf-core — 公共类型定义
3
+ *
4
+ * 所有项目共享的基础类型约束。
5
+ * 各项目通过 extends 扩展自己的 AppEnv,保持与 cf-core 兼容。
6
+ */
7
+
8
+ /**
9
+ * 所有 Cloudflare Workers 项目必须满足的最小 Bindings 约束。
10
+ * 各项目在此基础上添加自己的环境变量。
11
+ */
12
+ export interface CoreBindings {
13
+ TURSO_URL?: string;
14
+ TURSO_TOKEN?: string;
15
+ ADMIN_TOKEN?: string;
16
+ TURNSTILE_SECRET_KEY?: string;
17
+ RATE_LIMIT_SALT?: string;
18
+ APP_ORIGIN?: string;
19
+ }
20
+
21
+ /**
22
+ * Hono Variables 的最小约束。
23
+ * 各项目的 Variables 至少包含 db 字段,类型由项目自行指定。
24
+ */
25
+ export interface CoreVariables {
26
+ db: unknown;
27
+ }
28
+
29
+ /**
30
+ * Hono 上下文的最小环境约束。
31
+ * cf-core 中所有需要 Context 的函数都以此为泛型上界。
32
+ */
33
+ export interface CoreEnv {
34
+ Bindings: CoreBindings;
35
+ Variables: CoreVariables;
36
+ }
37
+
38
+ /**
39
+ * ok/fail 统一响应格式。
40
+ */
41
+ export interface OkResponse {
42
+ ok: true;
43
+ [key: string]: unknown;
44
+ }
45
+
46
+ export interface FailResponse {
47
+ ok: false;
48
+ error: string;
49
+ details?: unknown;
50
+ }
51
+
52
+ /**
53
+ * Turnstile 验证结果。
54
+ */
55
+ export interface TurnstileResult {
56
+ ok: boolean;
57
+ message?: string;
58
+ }
59
+
60
+ /**
61
+ * 限流检查结果。
62
+ */
63
+ export interface RateLimitResult {
64
+ ok: boolean;
65
+ message?: string;
66
+ status?: number;
67
+ ipHash?: string;
68
+ remaining?: number;
69
+ resetMs?: number;
70
+ }