@access-dlsu/leapify 0.260608.1 → 0.260608.2
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/dist/auth/auth.d.ts +5 -5
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/client/auth.d.ts +64 -60
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/index.cjs +541 -446
- package/dist/client/index.js +540 -444
- package/dist/client/types.cjs +0 -4
- package/dist/client/types.js +1 -3
- package/dist/index.cjs +2700 -2972
- package/dist/index.js +2698 -2969
- package/dist/lib/middleware/turnstile-challenge.cjs +145 -29
- package/dist/lib/middleware/turnstile-challenge.js +140 -4
- package/dist/worker.js +2758 -3049
- package/package.json +157 -156
- package/dist/chunk-NYEPGZMP.cjs +0 -171
- package/dist/chunk-NYEPGZMP.cjs.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
- package/dist/chunk-Q7SFCCGT.cjs.map +0 -1
- package/dist/chunk-WEW5LGZC.js +0 -165
- package/dist/chunk-WEW5LGZC.js.map +0 -1
- package/dist/client/index.cjs.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/types.cjs.map +0 -1
- package/dist/client/types.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/middleware/turnstile-challenge.cjs.map +0 -1
- package/dist/lib/middleware/turnstile-challenge.js.map +0 -1
- package/dist/worker.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,157 +1,158 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@access-dlsu/leapify",
|
|
3
|
-
"version": "0.260608.
|
|
4
|
-
"description": "DLSU CSO LEAP backend (run as a standalone Cloudflare Worker or install as an npm module)",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
14
|
-
},
|
|
15
|
-
"./worker": {
|
|
16
|
-
"import": "./dist/worker.js"
|
|
17
|
-
},
|
|
18
|
-
"./client": {
|
|
19
|
-
"types": "./dist/client/index.d.ts",
|
|
20
|
-
"import": "./dist/client/index.js",
|
|
21
|
-
"require": "./dist/client/index.cjs"
|
|
22
|
-
},
|
|
23
|
-
"./types": {
|
|
24
|
-
"types": "./dist/client/types.d.ts",
|
|
25
|
-
"import": "./dist/client/types.js",
|
|
26
|
-
"require": "./dist/client/types.cjs"
|
|
27
|
-
},
|
|
28
|
-
"./middleware/turnstile-challenge": {
|
|
29
|
-
"types": "./dist/lib/middleware/turnstile-challenge.d.ts",
|
|
30
|
-
"import": "./dist/lib/middleware/turnstile-challenge.js",
|
|
31
|
-
"require": "./dist/lib/middleware/turnstile-challenge.cjs"
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"dist"
|
|
36
|
-
],
|
|
37
|
-
"sideEffects": false,
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "
|
|
40
|
-
"dev": "
|
|
41
|
-
"start": "npm run build && wrangler dev",
|
|
42
|
-
"deploy": "npm run build && wrangler deploy",
|
|
43
|
-
"test": "vitest",
|
|
44
|
-
"test:run": "vitest run",
|
|
45
|
-
"cf-typegen": "wrangler types",
|
|
46
|
-
"typecheck": "tsc --noEmit",
|
|
47
|
-
"db:generate": "drizzle-kit generate",
|
|
48
|
-
"db:migrate": "drizzle-kit migrate",
|
|
49
|
-
"clean": "node scripts/clean.mjs",
|
|
50
|
-
"version:auto": "node scripts/auto-version.mjs",
|
|
51
|
-
"pack": "npm run clean && npm run version:auto && npm run build && npm pack",
|
|
52
|
-
"pub": "npm whoami && npm run clean && npm run build && npm publish --access public"
|
|
53
|
-
},
|
|
54
|
-
"cloudflare": {
|
|
55
|
-
"bindings": {
|
|
56
|
-
"ALLOWED_ORIGINS": {
|
|
57
|
-
"description": "Comma-separated allowed origins (e.g., https://yoursite.com). Leave empty to block all cross-origin requests."
|
|
58
|
-
},
|
|
59
|
-
"BETTER_AUTH_SECRET": {
|
|
60
|
-
"description": "Better Auth signing secret. Generate with: openssl rand -base64 32"
|
|
61
|
-
},
|
|
62
|
-
"BETTER_AUTH_URL": {
|
|
63
|
-
"description": "Public HTTPS base URL of this Worker (e.g. https://leap.yourdomain.com). Used for OAuth redirects."
|
|
64
|
-
},
|
|
65
|
-
"GOOGLE_CLIENT_ID": {
|
|
66
|
-
"description": "Google OAuth 2.0 Client ID from Google Cloud Console."
|
|
67
|
-
},
|
|
68
|
-
"GOOGLE_CLIENT_SECRET": {
|
|
69
|
-
"description": "Google OAuth 2.0 Client Secret from Google Cloud Console."
|
|
70
|
-
},
|
|
71
|
-
"GFORMS_SERVICE_ACCOUNT_JSON": {
|
|
72
|
-
"description": "Google Forms Service Account JSON."
|
|
73
|
-
},
|
|
74
|
-
"GFORMS_WEBHOOK_SECRET": {
|
|
75
|
-
"description": "Secret for Google Forms Webhook."
|
|
76
|
-
},
|
|
77
|
-
"CONTENTFUL_SPACE_ID": {
|
|
78
|
-
"description": "Contentful Space ID."
|
|
79
|
-
},
|
|
80
|
-
"CONTENTFUL_ACCESS_TOKEN": {
|
|
81
|
-
"description": "Contentful Access Token."
|
|
82
|
-
},
|
|
83
|
-
"CONTENTFUL_ENVIRONMENT": {
|
|
84
|
-
"description": "Contentful Environment (usually `master`)."
|
|
85
|
-
},
|
|
86
|
-
"SES_REGION": {
|
|
87
|
-
"description": "Amazon SES Region (e.g., `us-east-1`)."
|
|
88
|
-
},
|
|
89
|
-
"SES_ACCESS_KEY_ID": {
|
|
90
|
-
"description": "Amazon SES Access Key ID."
|
|
91
|
-
},
|
|
92
|
-
"SES_SECRET_ACCESS_KEY": {
|
|
93
|
-
"description": "Amazon SES Secret Access Key."
|
|
94
|
-
},
|
|
95
|
-
"SES_FROM_ADDRESS": {
|
|
96
|
-
"description": "Verified from address for Amazon SES."
|
|
97
|
-
},
|
|
98
|
-
"RESEND_API_KEY": {
|
|
99
|
-
"description": "Resend API Key for fallback email (Optional)."
|
|
100
|
-
},
|
|
101
|
-
"RESEND_FROM_ADDRESS": {
|
|
102
|
-
"description": "Resend From Address (Optional)."
|
|
103
|
-
},
|
|
104
|
-
"INTERNAL_API_SECRET": {
|
|
105
|
-
"description": "Secret key for internal API routes. Also used as HMAC signing key for PoW challenge cookies."
|
|
106
|
-
},
|
|
107
|
-
"TURNSTILE_SITE_KEY": {
|
|
108
|
-
"description": "Cloudflare Turnstile site key (public)."
|
|
109
|
-
},
|
|
110
|
-
"TURNSTILE_SECRET_KEY": {
|
|
111
|
-
"description": "Cloudflare Turnstile secret key (sensitive)."
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
"peerDependencies": {
|
|
116
|
-
"@cloudflare/workers-types": "^4.0.0",
|
|
117
|
-
"drizzle-orm": "^0.45.2",
|
|
118
|
-
"hono": "^4.0.0"
|
|
119
|
-
},
|
|
120
|
-
"peerDependenciesMeta": {
|
|
121
|
-
"@cloudflare/workers-types": {
|
|
122
|
-
"optional": true
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
"dependencies": {
|
|
126
|
-
"@hono/standard-validator": "^0.2.2",
|
|
127
|
-
"@hono/swagger-ui": "^0.6.1",
|
|
128
|
-
"@hono/zod-validator": "^0.8.0",
|
|
129
|
-
"better-auth": "^1.6.
|
|
130
|
-
"hono-openapi": "^1.3.0",
|
|
131
|
-
"zod": "^4.4.0"
|
|
132
|
-
},
|
|
133
|
-
"devDependencies": {
|
|
134
|
-
"@cloudflare/workers-types": "^4.20260606.1",
|
|
135
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
136
|
-
"better-sqlite3": "^12.10.0",
|
|
137
|
-
"drizzle-kit": "^0.31.10",
|
|
138
|
-
"hono": "^4.12.0",
|
|
139
|
-
"prettier": "^3.8.0",
|
|
140
|
-
"
|
|
141
|
-
"typescript": "^6.0.0",
|
|
142
|
-
"vitest": "^4.1.0"
|
|
143
|
-
},
|
|
144
|
-
"overrides": {
|
|
145
|
-
"esbuild": "^0.28.0"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"
|
|
1
|
+
{
|
|
2
|
+
"name": "@access-dlsu/leapify",
|
|
3
|
+
"version": "0.260608.2",
|
|
4
|
+
"description": "DLSU CSO LEAP backend (run as a standalone Cloudflare Worker or install as an npm module)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./worker": {
|
|
16
|
+
"import": "./dist/worker.js"
|
|
17
|
+
},
|
|
18
|
+
"./client": {
|
|
19
|
+
"types": "./dist/client/index.d.ts",
|
|
20
|
+
"import": "./dist/client/index.js",
|
|
21
|
+
"require": "./dist/client/index.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./types": {
|
|
24
|
+
"types": "./dist/client/types.d.ts",
|
|
25
|
+
"import": "./dist/client/types.js",
|
|
26
|
+
"require": "./dist/client/types.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./middleware/turnstile-challenge": {
|
|
29
|
+
"types": "./dist/lib/middleware/turnstile-challenge.d.ts",
|
|
30
|
+
"import": "./dist/lib/middleware/turnstile-challenge.js",
|
|
31
|
+
"require": "./dist/lib/middleware/turnstile-challenge.cjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsdown && tsc -p tsconfig.build.json --emitDeclarationOnly --declaration --declarationDir dist",
|
|
40
|
+
"dev": "tsdown --watch",
|
|
41
|
+
"start": "npm run build && wrangler dev",
|
|
42
|
+
"deploy": "npm run build && wrangler deploy",
|
|
43
|
+
"test": "vitest",
|
|
44
|
+
"test:run": "vitest run",
|
|
45
|
+
"cf-typegen": "wrangler types",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"db:generate": "drizzle-kit generate",
|
|
48
|
+
"db:migrate": "drizzle-kit migrate",
|
|
49
|
+
"clean": "node scripts/clean.mjs",
|
|
50
|
+
"version:auto": "node scripts/auto-version.mjs",
|
|
51
|
+
"pack": "npm run clean && npm run version:auto && npm run build && npm pack",
|
|
52
|
+
"pub": "npm whoami && npm run clean && npm run build && npm publish --access public"
|
|
53
|
+
},
|
|
54
|
+
"cloudflare": {
|
|
55
|
+
"bindings": {
|
|
56
|
+
"ALLOWED_ORIGINS": {
|
|
57
|
+
"description": "Comma-separated allowed origins (e.g., https://yoursite.com). Leave empty to block all cross-origin requests."
|
|
58
|
+
},
|
|
59
|
+
"BETTER_AUTH_SECRET": {
|
|
60
|
+
"description": "Better Auth signing secret. Generate with: openssl rand -base64 32"
|
|
61
|
+
},
|
|
62
|
+
"BETTER_AUTH_URL": {
|
|
63
|
+
"description": "Public HTTPS base URL of this Worker (e.g. https://leap.yourdomain.com). Used for OAuth redirects."
|
|
64
|
+
},
|
|
65
|
+
"GOOGLE_CLIENT_ID": {
|
|
66
|
+
"description": "Google OAuth 2.0 Client ID from Google Cloud Console."
|
|
67
|
+
},
|
|
68
|
+
"GOOGLE_CLIENT_SECRET": {
|
|
69
|
+
"description": "Google OAuth 2.0 Client Secret from Google Cloud Console."
|
|
70
|
+
},
|
|
71
|
+
"GFORMS_SERVICE_ACCOUNT_JSON": {
|
|
72
|
+
"description": "Google Forms Service Account JSON."
|
|
73
|
+
},
|
|
74
|
+
"GFORMS_WEBHOOK_SECRET": {
|
|
75
|
+
"description": "Secret for Google Forms Webhook."
|
|
76
|
+
},
|
|
77
|
+
"CONTENTFUL_SPACE_ID": {
|
|
78
|
+
"description": "Contentful Space ID."
|
|
79
|
+
},
|
|
80
|
+
"CONTENTFUL_ACCESS_TOKEN": {
|
|
81
|
+
"description": "Contentful Access Token."
|
|
82
|
+
},
|
|
83
|
+
"CONTENTFUL_ENVIRONMENT": {
|
|
84
|
+
"description": "Contentful Environment (usually `master`)."
|
|
85
|
+
},
|
|
86
|
+
"SES_REGION": {
|
|
87
|
+
"description": "Amazon SES Region (e.g., `us-east-1`)."
|
|
88
|
+
},
|
|
89
|
+
"SES_ACCESS_KEY_ID": {
|
|
90
|
+
"description": "Amazon SES Access Key ID."
|
|
91
|
+
},
|
|
92
|
+
"SES_SECRET_ACCESS_KEY": {
|
|
93
|
+
"description": "Amazon SES Secret Access Key."
|
|
94
|
+
},
|
|
95
|
+
"SES_FROM_ADDRESS": {
|
|
96
|
+
"description": "Verified from address for Amazon SES."
|
|
97
|
+
},
|
|
98
|
+
"RESEND_API_KEY": {
|
|
99
|
+
"description": "Resend API Key for fallback email (Optional)."
|
|
100
|
+
},
|
|
101
|
+
"RESEND_FROM_ADDRESS": {
|
|
102
|
+
"description": "Resend From Address (Optional)."
|
|
103
|
+
},
|
|
104
|
+
"INTERNAL_API_SECRET": {
|
|
105
|
+
"description": "Secret key for internal API routes. Also used as HMAC signing key for PoW challenge cookies."
|
|
106
|
+
},
|
|
107
|
+
"TURNSTILE_SITE_KEY": {
|
|
108
|
+
"description": "Cloudflare Turnstile site key (public)."
|
|
109
|
+
},
|
|
110
|
+
"TURNSTILE_SECRET_KEY": {
|
|
111
|
+
"description": "Cloudflare Turnstile secret key (sensitive)."
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"peerDependencies": {
|
|
116
|
+
"@cloudflare/workers-types": "^4.0.0",
|
|
117
|
+
"drizzle-orm": "^0.45.2",
|
|
118
|
+
"hono": "^4.0.0"
|
|
119
|
+
},
|
|
120
|
+
"peerDependenciesMeta": {
|
|
121
|
+
"@cloudflare/workers-types": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"dependencies": {
|
|
126
|
+
"@hono/standard-validator": "^0.2.2",
|
|
127
|
+
"@hono/swagger-ui": "^0.6.1",
|
|
128
|
+
"@hono/zod-validator": "^0.8.0",
|
|
129
|
+
"better-auth": "^1.6.0",
|
|
130
|
+
"hono-openapi": "^1.3.0",
|
|
131
|
+
"zod": "^4.4.0"
|
|
132
|
+
},
|
|
133
|
+
"devDependencies": {
|
|
134
|
+
"@cloudflare/workers-types": "^4.20260606.1",
|
|
135
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
136
|
+
"better-sqlite3": "^12.10.0",
|
|
137
|
+
"drizzle-kit": "^0.31.10",
|
|
138
|
+
"hono": "^4.12.0",
|
|
139
|
+
"prettier": "^3.8.0",
|
|
140
|
+
"tsdown": "^0.22.2",
|
|
141
|
+
"typescript": "^6.0.0",
|
|
142
|
+
"vitest": "^4.1.0"
|
|
143
|
+
},
|
|
144
|
+
"overrides": {
|
|
145
|
+
"esbuild": "^0.28.0",
|
|
146
|
+
"kysely": "^0.28.0"
|
|
147
|
+
},
|
|
148
|
+
"homepage": "https://github.com/access-dlsu/leapify#readme",
|
|
149
|
+
"bugs": {
|
|
150
|
+
"url": "https://github.com/access-dlsu/leapify/issues"
|
|
151
|
+
},
|
|
152
|
+
"repository": {
|
|
153
|
+
"type": "git",
|
|
154
|
+
"url": "git+https://github.com/access-dlsu/leapify.git"
|
|
155
|
+
},
|
|
156
|
+
"license": "MIT",
|
|
157
|
+
"author": "ACCESS DLSU"
|
|
157
158
|
}
|
package/dist/chunk-NYEPGZMP.cjs
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var factory = require('hono/factory');
|
|
4
|
-
|
|
5
|
-
// src/lib/middleware/turnstile-challenge.ts
|
|
6
|
-
var TURNSTILE_PATH = "/.well-known/leapify/turnstile";
|
|
7
|
-
var TURNSTILE_VERIFY_PATH = `${TURNSTILE_PATH}/verify`;
|
|
8
|
-
var TURNSTILE_COOKIE_NAME = "leapify-turnstile";
|
|
9
|
-
var VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
10
|
-
var COOKIE_MAX_AGE_SEC = 86400;
|
|
11
|
-
var EXEMPT_PATHS = [
|
|
12
|
-
"/health",
|
|
13
|
-
"/internal",
|
|
14
|
-
"/api/auth",
|
|
15
|
-
"/api/uploads/images",
|
|
16
|
-
"/api/classes",
|
|
17
|
-
"/api/faqs",
|
|
18
|
-
"/api/config",
|
|
19
|
-
"/api/themes",
|
|
20
|
-
"/api/organizations",
|
|
21
|
-
"/api/docs",
|
|
22
|
-
"/api/openapi.json",
|
|
23
|
-
TURNSTILE_VERIFY_PATH
|
|
24
|
-
];
|
|
25
|
-
function base64urlEncode(bytes) {
|
|
26
|
-
let binary = "";
|
|
27
|
-
for (const byte of bytes) {
|
|
28
|
-
binary += String.fromCharCode(byte);
|
|
29
|
-
}
|
|
30
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
31
|
-
}
|
|
32
|
-
function base64urlDecode(str) {
|
|
33
|
-
const padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
34
|
-
const binary = atob(padded);
|
|
35
|
-
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
36
|
-
for (let i = 0; i < binary.length; i++) {
|
|
37
|
-
bytes[i] = binary.charCodeAt(i);
|
|
38
|
-
}
|
|
39
|
-
return bytes;
|
|
40
|
-
}
|
|
41
|
-
async function importHmacKey(secret) {
|
|
42
|
-
return crypto.subtle.importKey(
|
|
43
|
-
"raw",
|
|
44
|
-
new TextEncoder().encode(secret),
|
|
45
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
46
|
-
false,
|
|
47
|
-
["sign", "verify"]
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
async function signCookie(secret, ip) {
|
|
51
|
-
const ts = Date.now();
|
|
52
|
-
const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(8)));
|
|
53
|
-
const payload = `${ip}:${ts}:${nonce}`;
|
|
54
|
-
const key = await importHmacKey(secret);
|
|
55
|
-
const sig = await crypto.subtle.sign(
|
|
56
|
-
"HMAC",
|
|
57
|
-
key,
|
|
58
|
-
new TextEncoder().encode(payload)
|
|
59
|
-
);
|
|
60
|
-
const sigB64 = base64urlEncode(new Uint8Array(sig));
|
|
61
|
-
return `${base64urlEncode(new TextEncoder().encode(payload))}.${sigB64}`;
|
|
62
|
-
}
|
|
63
|
-
async function validateCookie(secret, cookie, ip) {
|
|
64
|
-
try {
|
|
65
|
-
const [payloadB64, sigB64] = cookie.split(".");
|
|
66
|
-
if (!payloadB64 || !sigB64) return false;
|
|
67
|
-
const payloadBytes = base64urlDecode(payloadB64);
|
|
68
|
-
const sigBytes = base64urlDecode(sigB64);
|
|
69
|
-
const key = await importHmacKey(secret);
|
|
70
|
-
const valid = await crypto.subtle.verify(
|
|
71
|
-
"HMAC",
|
|
72
|
-
key,
|
|
73
|
-
sigBytes,
|
|
74
|
-
payloadBytes
|
|
75
|
-
);
|
|
76
|
-
if (!valid) return false;
|
|
77
|
-
const payload = new TextDecoder().decode(payloadBytes);
|
|
78
|
-
const [cookieIp, tsStr] = payload.split(":");
|
|
79
|
-
if (cookieIp !== ip) return false;
|
|
80
|
-
const ts = parseInt(tsStr, 10);
|
|
81
|
-
if (isNaN(ts) || Date.now() - ts > COOKIE_MAX_AGE_SEC * 1e3) return false;
|
|
82
|
-
return true;
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function getClientIp(c) {
|
|
88
|
-
return c.req.header("CF-Connecting-IP") ?? c.req.header("X-Real-IP") ?? c.req.header("X-Forwarded-For")?.split(",")[0]?.trim() ?? "unknown";
|
|
89
|
-
}
|
|
90
|
-
function isExempt(path) {
|
|
91
|
-
const normalized = path.toLowerCase().replace(/\/$/, "");
|
|
92
|
-
return EXEMPT_PATHS.some((p) => {
|
|
93
|
-
const ep = p.toLowerCase().replace(/\/$/, "");
|
|
94
|
-
return normalized === ep || normalized.startsWith(ep + "/");
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function setCookieHeader(c, token) {
|
|
98
|
-
const isSecure = c.req.raw.url.startsWith("https") || c.req.header("x-forwarded-proto") === "https";
|
|
99
|
-
c.header(
|
|
100
|
-
"Set-Cookie",
|
|
101
|
-
`${TURNSTILE_COOKIE_NAME}=${token}; Path=/; Max-Age=${COOKIE_MAX_AGE_SEC}; ${isSecure ? "Secure; " : ""}HttpOnly; SameSite=Lax`
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
async function handleTurnstileVerify(c) {
|
|
105
|
-
const body = await c.req.json();
|
|
106
|
-
const { token } = body;
|
|
107
|
-
if (!token) {
|
|
108
|
-
return c.json(
|
|
109
|
-
{ error: { code: "VALIDATION_ERROR", message: "Missing Turnstile token" } },
|
|
110
|
-
422
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
const secret = c.env.TURNSTILE_SECRET_KEY;
|
|
114
|
-
if (!secret) {
|
|
115
|
-
return c.json(
|
|
116
|
-
{ error: { code: "CONFIG_ERROR", message: "Turnstile not configured" } },
|
|
117
|
-
500
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
const ip = getClientIp(c);
|
|
121
|
-
const formData = new URLSearchParams();
|
|
122
|
-
formData.append("secret", secret);
|
|
123
|
-
formData.append("response", token);
|
|
124
|
-
if (ip !== "unknown") {
|
|
125
|
-
formData.append("remoteip", ip);
|
|
126
|
-
}
|
|
127
|
-
const res = await fetch(VERIFY_URL, {
|
|
128
|
-
method: "POST",
|
|
129
|
-
body: formData
|
|
130
|
-
});
|
|
131
|
-
const outcome = await res.json();
|
|
132
|
-
if (!outcome.success) {
|
|
133
|
-
return c.json(
|
|
134
|
-
{ error: { code: "TURNSTILE_FAILED", message: "Turnstile verification failed", details: outcome["error-codes"] } },
|
|
135
|
-
403
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
const cookieToken = await signCookie(secret, ip);
|
|
139
|
-
setCookieHeader(c, cookieToken);
|
|
140
|
-
return c.json({ success: true });
|
|
141
|
-
}
|
|
142
|
-
function createTurnstileMiddleware() {
|
|
143
|
-
return factory.createMiddleware(async (c, next) => {
|
|
144
|
-
if (isExempt(c.req.path)) return next();
|
|
145
|
-
if (c.req.method === "OPTIONS") return next();
|
|
146
|
-
if (c.req.header("Authorization")) return next();
|
|
147
|
-
const secret = c.env.TURNSTILE_SECRET_KEY;
|
|
148
|
-
if (!secret) return next();
|
|
149
|
-
const cookieHeader = c.req.header("Cookie") ?? "";
|
|
150
|
-
const cookieMatch = cookieHeader.match(
|
|
151
|
-
new RegExp(`${TURNSTILE_COOKIE_NAME}=([^;]+)`)
|
|
152
|
-
);
|
|
153
|
-
if (cookieMatch) {
|
|
154
|
-
const ip = getClientIp(c);
|
|
155
|
-
const valid = await validateCookie(secret, cookieMatch[1], ip);
|
|
156
|
-
if (valid) return next();
|
|
157
|
-
}
|
|
158
|
-
return c.json(
|
|
159
|
-
{ error: { code: "TURNSTILE_REQUIRED", message: "Turnstile verification required" } },
|
|
160
|
-
401
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
exports.TURNSTILE_COOKIE_NAME = TURNSTILE_COOKIE_NAME;
|
|
166
|
-
exports.TURNSTILE_PATH = TURNSTILE_PATH;
|
|
167
|
-
exports.TURNSTILE_VERIFY_PATH = TURNSTILE_VERIFY_PATH;
|
|
168
|
-
exports.createTurnstileMiddleware = createTurnstileMiddleware;
|
|
169
|
-
exports.handleTurnstileVerify = handleTurnstileVerify;
|
|
170
|
-
//# sourceMappingURL=chunk-NYEPGZMP.cjs.map
|
|
171
|
-
//# sourceMappingURL=chunk-NYEPGZMP.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/middleware/turnstile-challenge.ts"],"names":["createMiddleware"],"mappings":"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","file":"chunk-NYEPGZMP.cjs","sourcesContent":["import { createMiddleware } from 'hono/factory'\r\nimport type { Context } from 'hono'\r\nimport type { LeapifyBindings } from '../../types'\r\n\r\nexport const TURNSTILE_PATH = '/.well-known/leapify/turnstile'\r\n\r\nexport const TURNSTILE_VERIFY_PATH = `${TURNSTILE_PATH}/verify`\r\n\r\nexport const TURNSTILE_COOKIE_NAME = 'leapify-turnstile'\r\n\r\nconst VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'\r\n\r\nconst COOKIE_MAX_AGE_SEC = 86400\r\n\r\nconst EXEMPT_PATHS = [\r\n \"/health\",\r\n \"/internal\",\r\n \"/api/auth\",\r\n \"/api/uploads/images\",\r\n \"/api/classes\",\r\n \"/api/faqs\",\r\n \"/api/config\",\r\n \"/api/themes\",\r\n \"/api/organizations\",\r\n \"/api/docs\",\r\n \"/api/openapi.json\",\r\n TURNSTILE_VERIFY_PATH,\r\n];\r\n\r\nfunction base64urlEncode(bytes: Uint8Array): string {\r\n let binary = ''\r\n for (const byte of bytes) {\r\n binary += String.fromCharCode(byte)\r\n }\r\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\r\n}\r\n\r\nfunction base64urlDecode(str: string): Uint8Array<ArrayBuffer> {\r\n const padded = str.replace(/-/g, '+').replace(/_/g, '/')\r\n const binary = atob(padded)\r\n const bytes = new Uint8Array(new ArrayBuffer(binary.length))\r\n for (let i = 0; i < binary.length; i++) {\r\n bytes[i] = binary.charCodeAt(i)\r\n }\r\n return bytes\r\n}\r\n\r\nasync function importHmacKey(secret: string): Promise<CryptoKey> {\r\n return crypto.subtle.importKey(\r\n 'raw',\r\n new TextEncoder().encode(secret),\r\n { name: 'HMAC', hash: 'SHA-256' },\r\n false,\r\n ['sign', 'verify']\r\n )\r\n}\r\n\r\nasync function signCookie(secret: string, ip: string): Promise<string> {\r\n const ts = Date.now()\r\n const nonce = base64urlEncode(crypto.getRandomValues(new Uint8Array(8)))\r\n const payload = `${ip}:${ts}:${nonce}`\r\n const key = await importHmacKey(secret)\r\n const sig = await crypto.subtle.sign(\r\n 'HMAC',\r\n key,\r\n new TextEncoder().encode(payload)\r\n )\r\n const sigB64 = base64urlEncode(new Uint8Array(sig))\r\n return `${base64urlEncode(new TextEncoder().encode(payload))}.${sigB64}`\r\n}\r\n\r\nasync function validateCookie(\r\n secret: string,\r\n cookie: string,\r\n ip: string\r\n): Promise<boolean> {\r\n try {\r\n const [payloadB64, sigB64] = cookie.split('.')\r\n if (!payloadB64 || !sigB64) return false\r\n\r\n const payloadBytes = base64urlDecode(payloadB64)\r\n const sigBytes = base64urlDecode(sigB64)\r\n\r\n const key = await importHmacKey(secret)\r\n const valid = await crypto.subtle.verify(\r\n 'HMAC',\r\n key,\r\n sigBytes,\r\n payloadBytes\r\n )\r\n if (!valid) return false\r\n\r\n const payload = new TextDecoder().decode(payloadBytes)\r\n const [cookieIp, tsStr] = payload.split(':')\r\n\r\n if (cookieIp !== ip) return false\r\n\r\n const ts = parseInt(tsStr, 10)\r\n if (isNaN(ts) || Date.now() - ts > COOKIE_MAX_AGE_SEC * 1000) return false\r\n\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nfunction getClientIp(c: Context<{ Bindings: LeapifyBindings }>): string {\r\n return (\r\n c.req.header('CF-Connecting-IP') ??\r\n c.req.header('X-Real-IP') ??\r\n c.req.header('X-Forwarded-For')?.split(',')[0]?.trim() ??\r\n 'unknown'\r\n )\r\n}\r\n\r\nfunction isExempt(path: string): boolean {\r\n const normalized = path.toLowerCase().replace(/\\/$/, '')\r\n return EXEMPT_PATHS.some((p) => {\r\n const ep = p.toLowerCase().replace(/\\/$/, '')\r\n return normalized === ep || normalized.startsWith(ep + '/')\r\n })\r\n}\r\n\r\nfunction setCookieHeader(c: Context<{ Bindings: LeapifyBindings }>, token: string): void {\r\n const isSecure = c.req.raw.url.startsWith(\"https\") || c.req.header(\"x-forwarded-proto\") === \"https\";\r\n c.header(\r\n \"Set-Cookie\",\r\n `${TURNSTILE_COOKIE_NAME}=${token}; Path=/; Max-Age=${COOKIE_MAX_AGE_SEC}; ${\r\n isSecure ? \"Secure; \" : \"\"\r\n }HttpOnly; SameSite=Lax`,\r\n );\r\n}\r\n\r\n/**\r\n * POST /.well-known/leapify/turnstile/verify\r\n *\r\n * Validates a Turnstile token and issues a signed cookie on success.\r\n */\r\nexport async function handleTurnstileVerify(\r\n c: Context<{ Bindings: LeapifyBindings }>\r\n) {\r\n const body = await c.req.json<{ token?: string }>()\r\n const { token } = body\r\n\r\n if (!token) {\r\n return c.json(\r\n { error: { code: 'VALIDATION_ERROR', message: 'Missing Turnstile token' } },\r\n 422\r\n )\r\n }\r\n\r\n const secret = c.env.TURNSTILE_SECRET_KEY\r\n if (!secret) {\r\n return c.json(\r\n { error: { code: 'CONFIG_ERROR', message: 'Turnstile not configured' } },\r\n 500\r\n )\r\n }\r\n\r\n const ip = getClientIp(c)\r\n const formData = new URLSearchParams()\r\n formData.append('secret', secret)\r\n formData.append('response', token)\r\n if (ip !== 'unknown') {\r\n formData.append('remoteip', ip)\r\n }\r\n\r\n const res = await fetch(VERIFY_URL, {\r\n method: 'POST',\r\n body: formData,\r\n })\r\n const outcome = await res.json() as { success: boolean; 'error-codes'?: string[] }\r\n\r\n if (!outcome.success) {\r\n return c.json(\r\n { error: { code: 'TURNSTILE_FAILED', message: 'Turnstile verification failed', details: outcome['error-codes'] } },\r\n 403\r\n )\r\n }\r\n\r\n const cookieToken = await signCookie(secret, ip)\r\n setCookieHeader(c, cookieToken)\r\n\r\n return c.json({ success: true })\r\n}\r\n\r\n/**\r\n * Turnstile challenge middleware.\r\n *\r\n * Requires a valid Turnstile-signed cookie on all non-exempt requests.\r\n * The client must first solve a Turnstile challenge and POST the token\r\n * to the verify endpoint to obtain the cookie.\r\n *\r\n * Exempt paths: /health, /internal, /api/auth, /api/uploads/images,\r\n * and the verify endpoint itself.\r\n */\r\nexport function createTurnstileMiddleware() {\r\n return createMiddleware<{ Bindings: LeapifyBindings }>(async (c, next) => {\r\n if (isExempt(c.req.path)) return next()\r\n\r\n if (c.req.method === 'OPTIONS') return next()\r\n\r\n // Skip challenge for authenticated requests (Bearer token present)\r\n // The auth middleware will handle session validation instead.\r\n if (c.req.header('Authorization')) return next()\r\n\r\n const secret = c.env.TURNSTILE_SECRET_KEY\r\n if (!secret) return next()\r\n\r\n const cookieHeader = c.req.header('Cookie') ?? ''\r\n const cookieMatch = cookieHeader.match(\r\n new RegExp(`${TURNSTILE_COOKIE_NAME}=([^;]+)`)\r\n )\r\n if (cookieMatch) {\r\n const ip = getClientIp(c)\r\n const valid = await validateCookie(secret, cookieMatch[1], ip)\r\n if (valid) return next()\r\n }\r\n\r\n return c.json(\r\n { error: { code: 'TURNSTILE_REQUIRED', message: 'Turnstile verification required' } },\r\n 401\r\n )\r\n })\r\n}\r\n"]}
|
package/dist/chunk-PZ5AY32C.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __export = (target, all) => {
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export { __export };
|
|
8
|
-
//# sourceMappingURL=chunk-PZ5AY32C.js.map
|
|
9
|
-
//# sourceMappingURL=chunk-PZ5AY32C.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-PZ5AY32C.js"}
|
package/dist/chunk-Q7SFCCGT.cjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __export = (target, all) => {
|
|
5
|
-
for (var name in all)
|
|
6
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
exports.__export = __export;
|
|
10
|
-
//# sourceMappingURL=chunk-Q7SFCCGT.cjs.map
|
|
11
|
-
//# sourceMappingURL=chunk-Q7SFCCGT.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-Q7SFCCGT.cjs"}
|