@shware/security 2.5.3 → 2.6.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/dist/auth/index.cjs +520 -561
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +104 -106
- package/dist/auth/index.d.cts.map +1 -0
- package/dist/auth/index.d.mts +109 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +513 -536
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/types.cjs +0 -19
- package/dist/auth/types.d.cts +44 -44
- package/dist/auth/types.d.cts.map +1 -0
- package/dist/auth/types.d.mts +50 -0
- package/dist/auth/types.d.mts.map +1 -0
- package/dist/auth/types.mjs +1 -1
- package/dist/core/index.cjs +34 -59
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +23 -21
- package/dist/core/index.d.cts.map +1 -0
- package/dist/core/index.d.mts +34 -0
- package/dist/core/index.d.mts.map +1 -0
- package/dist/core/index.mjs +32 -33
- package/dist/core/index.mjs.map +1 -1
- package/dist/email/schema.cjs +13 -38
- package/dist/email/schema.cjs.map +1 -1
- package/dist/email/schema.d.cts +11 -12
- package/dist/email/schema.d.cts.map +1 -0
- package/dist/email/schema.d.mts +12 -0
- package/dist/email/schema.d.mts.map +1 -0
- package/dist/email/schema.mjs +10 -11
- package/dist/email/schema.mjs.map +1 -1
- package/dist/index.cjs +17 -54
- package/dist/index.d.cts +10 -15
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +4 -17
- package/dist/oauth2/client.cjs +109 -145
- package/dist/oauth2/client.cjs.map +1 -1
- package/dist/oauth2/client.d.cts +55 -56
- package/dist/oauth2/client.d.cts.map +1 -0
- package/dist/oauth2/client.d.mts +58 -0
- package/dist/oauth2/client.d.mts.map +1 -0
- package/dist/oauth2/client.mjs +104 -116
- package/dist/oauth2/client.mjs.map +1 -1
- package/dist/oauth2/error.cjs +19 -44
- package/dist/oauth2/error.cjs.map +1 -1
- package/dist/oauth2/error.d.cts +8 -6
- package/dist/oauth2/error.d.cts.map +1 -0
- package/dist/oauth2/{error.d.ts → error.d.mts} +8 -6
- package/dist/oauth2/error.d.mts.map +1 -0
- package/dist/oauth2/error.mjs +18 -19
- package/dist/oauth2/error.mjs.map +1 -1
- package/dist/oauth2/provider/apple.cjs +75 -88
- package/dist/oauth2/provider/apple.cjs.map +1 -1
- package/dist/oauth2/provider/apple.d.cts +31 -30
- package/dist/oauth2/provider/apple.d.cts.map +1 -0
- package/dist/oauth2/provider/apple.d.mts +42 -0
- package/dist/oauth2/provider/apple.d.mts.map +1 -0
- package/dist/oauth2/provider/apple.mjs +71 -60
- package/dist/oauth2/provider/apple.mjs.map +1 -1
- package/dist/oauth2/provider/common.cjs +86 -109
- package/dist/oauth2/provider/common.cjs.map +1 -1
- package/dist/oauth2/provider/common.d.cts +19 -18
- package/dist/oauth2/provider/common.d.cts.map +1 -0
- package/dist/oauth2/provider/common.d.mts +30 -0
- package/dist/oauth2/provider/common.d.mts.map +1 -0
- package/dist/oauth2/provider/common.mjs +80 -79
- package/dist/oauth2/provider/common.mjs.map +1 -1
- package/dist/oauth2/provider/facebook.cjs +93 -109
- package/dist/oauth2/provider/facebook.cjs.map +1 -1
- package/dist/oauth2/provider/facebook.d.cts +29 -28
- package/dist/oauth2/provider/facebook.d.cts.map +1 -0
- package/dist/oauth2/provider/facebook.d.mts +45 -0
- package/dist/oauth2/provider/facebook.d.mts.map +1 -0
- package/dist/oauth2/provider/facebook.mjs +89 -81
- package/dist/oauth2/provider/facebook.mjs.map +1 -1
- package/dist/oauth2/provider/github.cjs +64 -91
- package/dist/oauth2/provider/github.cjs.map +1 -1
- package/dist/oauth2/provider/github.d.cts +52 -51
- package/dist/oauth2/provider/github.d.cts.map +1 -0
- package/dist/oauth2/provider/github.d.mts +60 -0
- package/dist/oauth2/provider/github.d.mts.map +1 -0
- package/dist/oauth2/provider/github.mjs +60 -63
- package/dist/oauth2/provider/github.mjs.map +1 -1
- package/dist/oauth2/provider/google.cjs +105 -122
- package/dist/oauth2/provider/google.cjs.map +1 -1
- package/dist/oauth2/provider/google.d.cts +34 -33
- package/dist/oauth2/provider/google.d.cts.map +1 -0
- package/dist/oauth2/provider/google.d.mts +44 -0
- package/dist/oauth2/provider/google.d.mts.map +1 -0
- package/dist/oauth2/provider/google.mjs +101 -94
- package/dist/oauth2/provider/google.mjs.map +1 -1
- package/dist/oauth2/provider/index.cjs +13 -46
- package/dist/oauth2/provider/index.d.cts +7 -7
- package/dist/oauth2/provider/index.d.mts +7 -0
- package/dist/oauth2/provider/index.mjs +1 -10
- package/dist/oauth2/provider/wechat.cjs +77 -99
- package/dist/oauth2/provider/wechat.cjs.map +1 -1
- package/dist/oauth2/provider/wechat.d.cts +28 -27
- package/dist/oauth2/provider/wechat.d.cts.map +1 -0
- package/dist/oauth2/provider/wechat.d.mts +39 -0
- package/dist/oauth2/provider/wechat.d.mts.map +1 -0
- package/dist/oauth2/provider/wechat.mjs +74 -72
- package/dist/oauth2/provider/wechat.mjs.map +1 -1
- package/dist/oauth2/provider/x.cjs +102 -125
- package/dist/oauth2/provider/x.cjs.map +1 -1
- package/dist/oauth2/provider/x.d.cts +51 -50
- package/dist/oauth2/provider/x.d.cts.map +1 -0
- package/dist/oauth2/provider/x.d.mts +69 -0
- package/dist/oauth2/provider/x.d.mts.map +1 -0
- package/dist/oauth2/provider/x.mjs +99 -103
- package/dist/oauth2/provider/x.mjs.map +1 -1
- package/dist/oauth2/types.cjs +13 -37
- package/dist/oauth2/types.cjs.map +1 -1
- package/dist/oauth2/types.d.cts +180 -178
- package/dist/oauth2/types.d.cts.map +1 -0
- package/dist/oauth2/types.d.mts +213 -0
- package/dist/oauth2/types.d.mts.map +1 -0
- package/dist/oauth2/types.mjs +13 -13
- package/dist/oauth2/types.mjs.map +1 -1
- package/dist/session/common.cjs +17 -43
- package/dist/session/common.cjs.map +1 -1
- package/dist/session/common.d.cts +4 -4
- package/dist/session/common.d.cts.map +1 -0
- package/dist/session/{common.d.ts → common.d.mts} +4 -4
- package/dist/session/common.d.mts.map +1 -0
- package/dist/session/common.mjs +14 -16
- package/dist/session/common.mjs.map +1 -1
- package/dist/session/db.cjs +88 -123
- package/dist/session/db.cjs.map +1 -1
- package/dist/session/db.d.cts +36 -36
- package/dist/session/db.d.cts.map +1 -0
- package/dist/session/db.d.mts +44 -0
- package/dist/session/db.d.mts.map +1 -0
- package/dist/session/db.mjs +84 -95
- package/dist/session/db.mjs.map +1 -1
- package/dist/session/map-session.cjs +71 -103
- package/dist/session/map-session.cjs.map +1 -1
- package/dist/session/map-session.d.cts +24 -24
- package/dist/session/map-session.d.cts.map +1 -0
- package/dist/session/map-session.d.mts +27 -0
- package/dist/session/map-session.d.mts.map +1 -0
- package/dist/session/map-session.mjs +70 -78
- package/dist/session/map-session.mjs.map +1 -1
- package/dist/session/redis.cjs +270 -319
- package/dist/session/redis.cjs.map +1 -1
- package/dist/session/redis.d.cts +51 -51
- package/dist/session/redis.d.cts.map +1 -0
- package/dist/session/redis.d.mts +58 -0
- package/dist/session/redis.d.mts.map +1 -0
- package/dist/session/redis.mjs +266 -291
- package/dist/session/redis.mjs.map +1 -1
- package/dist/session/types.cjs +0 -19
- package/dist/session/types.d.cts +34 -33
- package/dist/session/types.d.cts.map +1 -0
- package/dist/session/types.d.mts +45 -0
- package/dist/session/types.d.mts.map +1 -0
- package/dist/session/types.mjs +1 -1
- package/dist/utils/http.cjs +31 -55
- package/dist/utils/http.cjs.map +1 -1
- package/dist/utils/http.d.cts +7 -10
- package/dist/utils/http.d.cts.map +1 -0
- package/dist/utils/{http.d.ts → http.d.mts} +7 -10
- package/dist/utils/http.d.mts.map +1 -0
- package/dist/utils/http.mjs +24 -24
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/oauth2.cjs +6 -30
- package/dist/utils/oauth2.cjs.map +1 -1
- package/dist/utils/oauth2.d.cts +3 -1
- package/dist/utils/oauth2.d.cts.map +1 -0
- package/dist/utils/oauth2.d.mts +5 -0
- package/dist/utils/oauth2.d.mts.map +1 -0
- package/dist/utils/oauth2.mjs +5 -5
- package/dist/utils/oauth2.mjs.map +1 -1
- package/dist/utils/timing.cjs +49 -70
- package/dist/utils/timing.cjs.map +1 -1
- package/dist/utils/timing.d.cts +10 -8
- package/dist/utils/timing.d.cts.map +1 -0
- package/dist/utils/timing.d.mts +15 -0
- package/dist/utils/timing.d.mts.map +1 -0
- package/dist/utils/timing.mjs +48 -45
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/valid.cjs +50 -71
- package/dist/utils/valid.cjs.map +1 -1
- package/dist/utils/valid.d.cts +17 -16
- package/dist/utils/valid.d.cts.map +1 -0
- package/dist/utils/valid.d.mts +28 -0
- package/dist/utils/valid.d.mts.map +1 -0
- package/dist/utils/valid.mjs +47 -44
- package/dist/utils/valid.mjs.map +1 -1
- package/package.json +22 -12
- package/dist/auth/index.d.ts +0 -111
- package/dist/auth/types.cjs.map +0 -1
- package/dist/auth/types.d.ts +0 -50
- package/dist/auth/types.mjs.map +0 -1
- package/dist/core/index.d.ts +0 -32
- package/dist/email/schema.d.ts +0 -13
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -15
- package/dist/index.mjs.map +0 -1
- package/dist/oauth2/client.d.ts +0 -59
- package/dist/oauth2/provider/apple.d.ts +0 -41
- package/dist/oauth2/provider/common.d.ts +0 -29
- package/dist/oauth2/provider/facebook.d.ts +0 -44
- package/dist/oauth2/provider/github.d.ts +0 -59
- package/dist/oauth2/provider/google.d.ts +0 -43
- package/dist/oauth2/provider/index.cjs.map +0 -1
- package/dist/oauth2/provider/index.d.ts +0 -7
- package/dist/oauth2/provider/index.mjs.map +0 -1
- package/dist/oauth2/provider/wechat.d.ts +0 -38
- package/dist/oauth2/provider/x.d.ts +0 -68
- package/dist/oauth2/types.d.ts +0 -211
- package/dist/session/__tests__/redis.test.cjs +0 -247
- package/dist/session/__tests__/redis.test.cjs.map +0 -1
- package/dist/session/__tests__/redis.test.d.cts +0 -2
- package/dist/session/__tests__/redis.test.d.ts +0 -2
- package/dist/session/__tests__/redis.test.mjs +0 -252
- package/dist/session/__tests__/redis.test.mjs.map +0 -1
- package/dist/session/db.d.ts +0 -44
- package/dist/session/map-session.d.ts +0 -27
- package/dist/session/redis.d.ts +0 -58
- package/dist/session/types.cjs.map +0 -1
- package/dist/session/types.d.ts +0 -44
- package/dist/session/types.mjs.map +0 -1
- package/dist/utils/oauth2.d.ts +0 -3
- package/dist/utils/timing.d.ts +0 -13
- package/dist/utils/valid.d.ts +0 -27
package/dist/auth/index.cjs
CHANGED
|
@@ -1,565 +1,524 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
var import_provider = require("../oauth2/provider/index.cjs");
|
|
33
|
-
var import_common = require("../session/common.cjs");
|
|
34
|
-
var import_http = require("../utils/http.cjs");
|
|
35
|
-
var import_timing = require("../utils/timing.cjs");
|
|
36
|
-
var import_valid = require("../utils/valid.cjs");
|
|
37
|
-
var PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;
|
|
38
|
-
var PATH = {
|
|
39
|
-
CSRF: "/csrf",
|
|
40
|
-
LOGOUT: "/logout",
|
|
41
|
-
LOGGED: "/logged",
|
|
42
|
-
OAUTH2_STATE: "/oauth2/state/:registrationId",
|
|
43
|
-
OAUTH2_NONCE: "/oauth2/nonce/:registrationId",
|
|
44
|
-
OAUTH2_AUTHORIZATION: "/oauth2/authorization/:registrationId",
|
|
45
|
-
LOGIN_OAUTH2_CODE: "/login/oauth2/code/:registrationId",
|
|
46
|
-
LOGIN_OAUTH2_NATIVE: "/login/oauth2/native/:registrationId",
|
|
47
|
-
LOGIN_OAUTH2_ONETAP: "/login/oauth2/onetap/google",
|
|
48
|
-
LOGIN_EMAIL: "/login/email",
|
|
49
|
-
LOGIN_PHONE: "/login/phone",
|
|
50
|
-
SEND_EMAIL_VERIFICATION_CODE: "/auth/sendEmailVerificationCode",
|
|
51
|
-
SEND_PHONE_VERIFICATION_CODE: "/auth/sendPhoneVerificationCode",
|
|
52
|
-
// Desktop / native app sign-in via web loopback (RFC 8252 + OAuth 2.1 Auth Code Flow):
|
|
53
|
-
// 1. user already authenticated with cookie session → POST DESKTOP_AUTHORIZE → { code }
|
|
54
|
-
// 2. web redirects to desktop's loopback callback with the code
|
|
55
|
-
// 3. desktop renderer POSTs DESKTOP_EXCHANGE with the code → server returns
|
|
56
|
-
// Set-Cookie carrying a fresh session, single-use code is invalidated
|
|
57
|
-
// The two-step flow keeps the actual session id off the redirect URL — only a
|
|
58
|
-
// 5-minute single-use code transits via the loopback.
|
|
59
|
-
DESKTOP_AUTHORIZE: "/auth/desktop/authorize",
|
|
60
|
-
DESKTOP_EXCHANGE: "/auth/desktop/exchange",
|
|
61
|
-
CLEANUP_EXPIRED_SESSIONS: "/sessions/expired/cleanup"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../core/index.cjs");
|
|
3
|
+
const require_email_schema = require("../email/schema.cjs");
|
|
4
|
+
const require_oauth2_client = require("../oauth2/client.cjs");
|
|
5
|
+
const require_oauth2_provider_google = require("../oauth2/provider/google.cjs");
|
|
6
|
+
require("../oauth2/provider/index.cjs");
|
|
7
|
+
const require_session_common = require("../session/common.cjs");
|
|
8
|
+
const require_utils_http = require("../utils/http.cjs");
|
|
9
|
+
const require_utils_timing = require("../utils/timing.cjs");
|
|
10
|
+
const require_utils_valid = require("../utils/valid.cjs");
|
|
11
|
+
let node_crypto = require("node:crypto");
|
|
12
|
+
let _shware_utils = require("@shware/utils");
|
|
13
|
+
//#region src/auth/index.ts
|
|
14
|
+
const PKCE_FORMAT = /^[A-Za-z0-9_-]{43,128}$/;
|
|
15
|
+
const PATH = {
|
|
16
|
+
CSRF: "/csrf",
|
|
17
|
+
LOGOUT: "/logout",
|
|
18
|
+
LOGGED: "/logged",
|
|
19
|
+
OAUTH2_STATE: "/oauth2/state/:registrationId",
|
|
20
|
+
OAUTH2_NONCE: "/oauth2/nonce/:registrationId",
|
|
21
|
+
OAUTH2_AUTHORIZATION: "/oauth2/authorization/:registrationId",
|
|
22
|
+
LOGIN_OAUTH2_CODE: "/login/oauth2/code/:registrationId",
|
|
23
|
+
LOGIN_OAUTH2_NATIVE: "/login/oauth2/native/:registrationId",
|
|
24
|
+
LOGIN_OAUTH2_ONETAP: "/login/oauth2/onetap/google",
|
|
25
|
+
LOGIN_EMAIL: "/login/email",
|
|
26
|
+
LOGIN_PHONE: "/login/phone",
|
|
27
|
+
SEND_EMAIL_VERIFICATION_CODE: "/auth/sendEmailVerificationCode",
|
|
28
|
+
SEND_PHONE_VERIFICATION_CODE: "/auth/sendPhoneVerificationCode",
|
|
29
|
+
DESKTOP_AUTHORIZE: "/auth/desktop/authorize",
|
|
30
|
+
DESKTOP_EXCHANGE: "/auth/desktop/exchange",
|
|
31
|
+
CLEANUP_EXPIRED_SESSIONS: "/sessions/expired/cleanup"
|
|
62
32
|
};
|
|
63
33
|
var Auth = class {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
return { name: String(name) };
|
|
550
|
-
};
|
|
551
|
-
listSessions = async (principal) => {
|
|
552
|
-
const sessions = await this.repository.findByPrincipalName(principal.name);
|
|
553
|
-
return Array.from(sessions.values());
|
|
554
|
-
};
|
|
555
|
-
cleanupExpiredSessions = async (cleanupCount) => {
|
|
556
|
-
await this.repository.cleanupExpiredSessions(cleanupCount);
|
|
557
|
-
return Response.json({});
|
|
558
|
-
};
|
|
34
|
+
timing;
|
|
35
|
+
cookieName;
|
|
36
|
+
cookieOptions;
|
|
37
|
+
kv;
|
|
38
|
+
repository;
|
|
39
|
+
turnstileSecretKey;
|
|
40
|
+
oauth2Client;
|
|
41
|
+
ATTR_OAUTH2_AUTHORIZATION_REQUEST = "oauth2AuthorizationRequest";
|
|
42
|
+
constructor({ sessionRepository, kvRepository, turnstileSecretKey, oauth2, cookie, timing }) {
|
|
43
|
+
this.timing = timing ?? false;
|
|
44
|
+
this.kv = kvRepository;
|
|
45
|
+
this.repository = sessionRepository;
|
|
46
|
+
this.turnstileSecretKey = turnstileSecretKey;
|
|
47
|
+
const { name, ...cookieOptions } = cookie ?? {};
|
|
48
|
+
this.cookieName = name ?? "SESSION";
|
|
49
|
+
this.cookieOptions = {
|
|
50
|
+
path: "/",
|
|
51
|
+
sameSite: "none",
|
|
52
|
+
secure: true,
|
|
53
|
+
httpOnly: true,
|
|
54
|
+
...cookieOptions
|
|
55
|
+
};
|
|
56
|
+
this.oauth2Client = oauth2?.client ? new require_oauth2_client.OAuth2Client(oauth2.client) : null;
|
|
57
|
+
}
|
|
58
|
+
getSessionId(request) {
|
|
59
|
+
let sessionId;
|
|
60
|
+
const header = request.headers.get("Authorization");
|
|
61
|
+
if (header?.startsWith("Bearer ")) sessionId = header.replace("Bearer ", "");
|
|
62
|
+
if (!sessionId) sessionId = require_utils_http.getCookie(request, this.cookieName);
|
|
63
|
+
return sessionId;
|
|
64
|
+
}
|
|
65
|
+
csrf = async (_request) => {
|
|
66
|
+
const token = (0, node_crypto.randomUUID)();
|
|
67
|
+
const response = Response.json({
|
|
68
|
+
token,
|
|
69
|
+
parameterName: "_csrf",
|
|
70
|
+
headerName: "X-XSRF-TOKEN"
|
|
71
|
+
});
|
|
72
|
+
require_utils_http.setCookie(response, "XSRF-TOKEN", token, {
|
|
73
|
+
path: "/",
|
|
74
|
+
secure: true,
|
|
75
|
+
httpOnly: false,
|
|
76
|
+
sameSite: "none",
|
|
77
|
+
domain: this.cookieOptions.domain
|
|
78
|
+
});
|
|
79
|
+
return response;
|
|
80
|
+
};
|
|
81
|
+
logout = async (request) => {
|
|
82
|
+
const sessionId = this.getSessionId(request);
|
|
83
|
+
if (sessionId) await this.repository.deleteById(sessionId);
|
|
84
|
+
const response = Response.json({});
|
|
85
|
+
require_utils_http.deleteCookie(response, this.cookieName);
|
|
86
|
+
return response;
|
|
87
|
+
};
|
|
88
|
+
logged = async (request, onLogged) => {
|
|
89
|
+
const sessionId = this.getSessionId(request);
|
|
90
|
+
if (!sessionId) return Response.json({ data: false });
|
|
91
|
+
const session = await this.repository.findById(sessionId);
|
|
92
|
+
if (!session) return Response.json({ data: false });
|
|
93
|
+
const name = session.getAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME);
|
|
94
|
+
session.setLastAccessedTime(Date.now());
|
|
95
|
+
await this.repository.save(session);
|
|
96
|
+
if (name !== null) await onLogged?.({ name: String(name) });
|
|
97
|
+
const response = Response.json({ data: name !== null });
|
|
98
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
99
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
100
|
+
...this.cookieOptions,
|
|
101
|
+
maxAge
|
|
102
|
+
});
|
|
103
|
+
return response;
|
|
104
|
+
};
|
|
105
|
+
createPkceParameters() {
|
|
106
|
+
const code_verifier = (0, node_crypto.randomUUID)();
|
|
107
|
+
return {
|
|
108
|
+
code_verifier,
|
|
109
|
+
code_challenge: (0, node_crypto.createHash)("sha256").update(code_verifier).digest().toString("base64url"),
|
|
110
|
+
code_challenge_method: "S256"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
getOauth2StateKey(state) {
|
|
114
|
+
return `oauth2:state:${state}`;
|
|
115
|
+
}
|
|
116
|
+
getOauth2NonceKey(nonce) {
|
|
117
|
+
return `oauth2:nonce:${nonce}`;
|
|
118
|
+
}
|
|
119
|
+
getEmailVerificationCodeKey(email) {
|
|
120
|
+
return `email:verification:${email}`;
|
|
121
|
+
}
|
|
122
|
+
getPhoneVerificationCodeKey(phone) {
|
|
123
|
+
return `phone:verification:${phone}`;
|
|
124
|
+
}
|
|
125
|
+
getDesktopCodeKey(code) {
|
|
126
|
+
return `desktop:code:${code}`;
|
|
127
|
+
}
|
|
128
|
+
oauth2State = async (request) => {
|
|
129
|
+
const { registrationId } = require_utils_http.param(request, PATH.OAUTH2_STATE);
|
|
130
|
+
const state = (0, node_crypto.randomUUID)();
|
|
131
|
+
await this.kv.setItem(this.getOauth2StateKey(state), registrationId, 600);
|
|
132
|
+
return Response.json({ state });
|
|
133
|
+
};
|
|
134
|
+
oauth2Nonce = async (request) => {
|
|
135
|
+
const { registrationId } = require_utils_http.param(request, PATH.OAUTH2_NONCE);
|
|
136
|
+
const nonce = (0, node_crypto.randomUUID)();
|
|
137
|
+
await this.kv.setItem(this.getOauth2NonceKey(nonce), registrationId, 600);
|
|
138
|
+
return Response.json({ nonce });
|
|
139
|
+
};
|
|
140
|
+
oauth2Authorization = async (request) => {
|
|
141
|
+
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
142
|
+
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
143
|
+
const { registrationId } = require_utils_http.param(request, PATH.OAUTH2_AUTHORIZATION);
|
|
144
|
+
const state = (0, node_crypto.randomUUID)();
|
|
145
|
+
const pkce = this.createPkceParameters();
|
|
146
|
+
const uri = await this.oauth2Client.createAuthorizationUri({
|
|
147
|
+
registrationId,
|
|
148
|
+
state,
|
|
149
|
+
pkce
|
|
150
|
+
});
|
|
151
|
+
const sessionId = this.getSessionId(request);
|
|
152
|
+
const session = sessionId ? await this.repository.findById(sessionId) ?? this.repository.createSession() : this.repository.createSession();
|
|
153
|
+
mark("fetch_session");
|
|
154
|
+
const authorizationRequest = {
|
|
155
|
+
state,
|
|
156
|
+
registrationId,
|
|
157
|
+
authorizationRequestUri: uri.href,
|
|
158
|
+
additionalParameters: pkce
|
|
159
|
+
};
|
|
160
|
+
const value = JSON.stringify(authorizationRequest);
|
|
161
|
+
session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
|
|
162
|
+
await this.repository.save(session);
|
|
163
|
+
mark("save_session");
|
|
164
|
+
const response = new Response(null, {
|
|
165
|
+
status: 302,
|
|
166
|
+
headers: { location: uri.href }
|
|
167
|
+
});
|
|
168
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
169
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
170
|
+
...this.cookieOptions,
|
|
171
|
+
maxAge
|
|
172
|
+
});
|
|
173
|
+
setTiming(response);
|
|
174
|
+
return response;
|
|
175
|
+
};
|
|
176
|
+
redirect = (error, description) => {
|
|
177
|
+
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
178
|
+
const uri = new URL(this.oauth2Client.errorUri);
|
|
179
|
+
uri.searchParams.set("error", error);
|
|
180
|
+
if (description) uri.searchParams.set("error_description", description);
|
|
181
|
+
return Response.redirect(uri.href, 302);
|
|
182
|
+
};
|
|
183
|
+
loginOAuth2Code = async (request, handler) => {
|
|
184
|
+
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
185
|
+
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
186
|
+
const sessionId = this.getSessionId(request);
|
|
187
|
+
const session = sessionId ? await this.repository.findById(sessionId) : null;
|
|
188
|
+
if (!session) return this.redirect("invalid_request", "session not found");
|
|
189
|
+
mark("load_session");
|
|
190
|
+
const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
191
|
+
if (!json) return this.redirect("invalid_request", "authorization request not found");
|
|
192
|
+
const cached = JSON.parse(json);
|
|
193
|
+
const { registrationId } = require_utils_http.param(request, PATH.LOGIN_OAUTH2_CODE);
|
|
194
|
+
let data;
|
|
195
|
+
if (request.method === "POST" && request.headers.get("content-type")?.toLowerCase() === "application/x-www-form-urlencoded") {
|
|
196
|
+
const params = new URLSearchParams(await request.text());
|
|
197
|
+
data = Object.fromEntries(params.entries());
|
|
198
|
+
} else data = require_utils_http.query(request);
|
|
199
|
+
const parsed = require_oauth2_client.oauth2RedirectQuerySchema.safeParse(data);
|
|
200
|
+
if (!parsed.success) return this.redirect("invalid_request", "invalid redirect query");
|
|
201
|
+
if (!parsed.data.code || !parsed.data.state) return this.redirect("invalid_request", "invalid redirect query");
|
|
202
|
+
if (parsed.data.state !== cached.state) return this.redirect("invalid_request", "redirect state mismatch");
|
|
203
|
+
if (registrationId !== cached.registrationId) return this.redirect("invalid_request", "redirect registration mismatch");
|
|
204
|
+
mark("validate_session");
|
|
205
|
+
const { code } = parsed.data;
|
|
206
|
+
const token = await this.oauth2Client.exchangeAuthorizationCode({
|
|
207
|
+
registrationId,
|
|
208
|
+
code,
|
|
209
|
+
pkce: cached.additionalParameters
|
|
210
|
+
});
|
|
211
|
+
const userInfo = await this.oauth2Client.getUserInfo({
|
|
212
|
+
registrationId,
|
|
213
|
+
token
|
|
214
|
+
});
|
|
215
|
+
mark("exchange_code");
|
|
216
|
+
const principal = await handler(request, registrationId, userInfo, token);
|
|
217
|
+
mark("save_principal");
|
|
218
|
+
session.setLastAccessedTime(Date.now());
|
|
219
|
+
session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
220
|
+
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
221
|
+
await this.repository.save(session);
|
|
222
|
+
mark("update_session");
|
|
223
|
+
const uri = new URL(this.oauth2Client.successUri);
|
|
224
|
+
uri.searchParams.set("status", "success");
|
|
225
|
+
uri.searchParams.set("registrationId", registrationId);
|
|
226
|
+
const response = new Response(null, {
|
|
227
|
+
status: 302,
|
|
228
|
+
headers: { location: uri.href }
|
|
229
|
+
});
|
|
230
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
231
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
232
|
+
...this.cookieOptions,
|
|
233
|
+
maxAge
|
|
234
|
+
});
|
|
235
|
+
setTiming(response);
|
|
236
|
+
return response;
|
|
237
|
+
};
|
|
238
|
+
loginOAuth2Native = async (request, handler) => {
|
|
239
|
+
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
240
|
+
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
241
|
+
const { registrationId } = require_utils_http.param(request, PATH.LOGIN_OAUTH2_NATIVE);
|
|
242
|
+
const credentials = await request.json();
|
|
243
|
+
const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({
|
|
244
|
+
registrationId,
|
|
245
|
+
credentials
|
|
246
|
+
});
|
|
247
|
+
mark("login_oauth2_native");
|
|
248
|
+
const { nonce } = userInfo.data;
|
|
249
|
+
if (nonce) {
|
|
250
|
+
if (await this.kv.getItem(this.getOauth2NonceKey(nonce)) !== registrationId) return Response.json({
|
|
251
|
+
error: "invalid_request",
|
|
252
|
+
error_description: "nonce mismatch"
|
|
253
|
+
}, { status: 400 });
|
|
254
|
+
await this.kv.removeItem(this.getOauth2NonceKey(nonce));
|
|
255
|
+
mark("check_nonce");
|
|
256
|
+
}
|
|
257
|
+
const principal = await handler(request, registrationId, userInfo, token);
|
|
258
|
+
mark("save_principal");
|
|
259
|
+
const session = this.repository.createSession();
|
|
260
|
+
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
261
|
+
await this.repository.save(session);
|
|
262
|
+
const response = new Response(JSON.stringify(principal), {
|
|
263
|
+
status: 200,
|
|
264
|
+
headers: { "Content-Type": "application/json" }
|
|
265
|
+
});
|
|
266
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
267
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
268
|
+
...this.cookieOptions,
|
|
269
|
+
maxAge
|
|
270
|
+
});
|
|
271
|
+
setTiming(response);
|
|
272
|
+
return response;
|
|
273
|
+
};
|
|
274
|
+
loginOAuth2Onetap = async (request, handler, registrationId = "google") => {
|
|
275
|
+
(0, _shware_utils.invariant)(this.oauth2Client, "oauth2Client is not initialized");
|
|
276
|
+
const { mark, setTiming } = require_utils_timing.timing({ enabled: this.timing });
|
|
277
|
+
let body;
|
|
278
|
+
if (request.headers.get("Content-Type")?.includes("application/x-www-form-urlencoded")) {
|
|
279
|
+
const text = await request.text();
|
|
280
|
+
body = Object.fromEntries(new URLSearchParams(text));
|
|
281
|
+
} else if (request.headers.get("Content-Type")?.includes("application/json")) body = await request.json();
|
|
282
|
+
else return Response.json({
|
|
283
|
+
error: "invalid_request",
|
|
284
|
+
error_description: "invalid content type"
|
|
285
|
+
}, { status: 400 });
|
|
286
|
+
const parsed = require_oauth2_client.googleOneTapSchema.parse(body);
|
|
287
|
+
let principal;
|
|
288
|
+
if (parsed.credential) {
|
|
289
|
+
const { token, userInfo } = await require_oauth2_provider_google.google.getTokenInfo(parsed.credential);
|
|
290
|
+
principal = await handler(request, registrationId, userInfo, token);
|
|
291
|
+
} else if (parsed.code) {
|
|
292
|
+
const token = await this.oauth2Client.exchangeAuthorizationCode({
|
|
293
|
+
registrationId,
|
|
294
|
+
code: parsed.code
|
|
295
|
+
});
|
|
296
|
+
principal = await handler(request, registrationId, await this.oauth2Client.getUserInfo({
|
|
297
|
+
registrationId,
|
|
298
|
+
token
|
|
299
|
+
}), token);
|
|
300
|
+
} else throw new Error("invalid request");
|
|
301
|
+
mark("save_principal");
|
|
302
|
+
const session = this.repository.createSession();
|
|
303
|
+
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
304
|
+
await this.repository.save(session);
|
|
305
|
+
const response = new Response(JSON.stringify(principal), {
|
|
306
|
+
status: 200,
|
|
307
|
+
headers: { "Content-Type": "application/json" }
|
|
308
|
+
});
|
|
309
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
310
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
311
|
+
...this.cookieOptions,
|
|
312
|
+
maxAge
|
|
313
|
+
});
|
|
314
|
+
setTiming(response);
|
|
315
|
+
return response;
|
|
316
|
+
};
|
|
317
|
+
sendEmailVerificationCode = async (request, sender) => {
|
|
318
|
+
(0, _shware_utils.invariant)(this.turnstileSecretKey, "turnstileSecretKey is not configured");
|
|
319
|
+
const { data, error } = require_utils_valid.valid(require_email_schema.sendEmailVerificationCodeSchema, await request.json());
|
|
320
|
+
if (error) return Response.json(error, { status: error.error.code });
|
|
321
|
+
const { email, turnstileToken } = data;
|
|
322
|
+
const result = await require_utils_valid.verifyTurnstileToken(this.turnstileSecretKey, turnstileToken);
|
|
323
|
+
if (result.error) return Response.json(result.error, { status: result.error.error.code });
|
|
324
|
+
try {
|
|
325
|
+
const verificationCode = (0, node_crypto.randomInt)(1e6).toString().padStart(6, "0");
|
|
326
|
+
await sender({
|
|
327
|
+
email,
|
|
328
|
+
verificationCode
|
|
329
|
+
});
|
|
330
|
+
await this.kv.setItem(this.getEmailVerificationCodeKey(email), verificationCode, 600);
|
|
331
|
+
return Response.json({});
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.error("Failed to send email verification code", e);
|
|
334
|
+
return Response.json(require_utils_valid.invalidArgument("EMAIL_DELIVERY_FAILED"), { status: 400 });
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
loginEmail = async (request, handler) => {
|
|
338
|
+
const { data, error } = require_utils_valid.valid(require_email_schema.loginEmailSchema, await request.json());
|
|
339
|
+
if (error) return Response.json(error, { status: error.error.code });
|
|
340
|
+
const { email, verificationCode } = data;
|
|
341
|
+
const key = this.getEmailVerificationCodeKey(email);
|
|
342
|
+
const stored = await this.kv.getItem(key);
|
|
343
|
+
if (typeof stored !== "string" || stored.length !== verificationCode.length || !(0, node_crypto.timingSafeEqual)(Buffer.from(stored), Buffer.from(verificationCode))) return Response.json(require_utils_valid.invalidArgument("INVALID_VERIFICATION_CODE"), { status: 400 });
|
|
344
|
+
await this.kv.removeItem(key);
|
|
345
|
+
const principal = await handler(request, "EMAIL", {
|
|
346
|
+
claims: {
|
|
347
|
+
sub: email,
|
|
348
|
+
email,
|
|
349
|
+
email_verified: true
|
|
350
|
+
},
|
|
351
|
+
data: {}
|
|
352
|
+
});
|
|
353
|
+
const session = this.repository.createSession();
|
|
354
|
+
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
355
|
+
await this.repository.save(session);
|
|
356
|
+
const response = new Response(JSON.stringify(principal), {
|
|
357
|
+
status: 200,
|
|
358
|
+
headers: { "Content-Type": "application/json" }
|
|
359
|
+
});
|
|
360
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
361
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
362
|
+
...this.cookieOptions,
|
|
363
|
+
maxAge
|
|
364
|
+
});
|
|
365
|
+
return response;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Step 1 of the desktop / native app sign-in handshake. The caller already
|
|
369
|
+
* completed normal sign-in on the web (so a cookie session exists); this
|
|
370
|
+
* issues a 5-minute single-use code that the web page hands off to the
|
|
371
|
+
* desktop client via a loopback redirect.
|
|
372
|
+
*
|
|
373
|
+
* PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`
|
|
374
|
+
* before opening the browser, derives `code_challenge = base64url(sha256(verifier))`,
|
|
375
|
+
* and threads the challenge through the URL → web page → here. We pin
|
|
376
|
+
* the challenge to the code in KV; `desktopExchange` enforces that the
|
|
377
|
+
* caller knows the matching verifier. This blocks an attacker who only
|
|
378
|
+
* intercepts the auth code (browser history, malicious extension, packet
|
|
379
|
+
* capture on loopback) from exchanging it.
|
|
380
|
+
*
|
|
381
|
+
* Requires an authenticated cookie session — anonymous callers get 401.
|
|
382
|
+
* Throttling / abuse protection on this endpoint is the API layer's job
|
|
383
|
+
* (rate-limit per principal); the SDK doesn't gate it because the
|
|
384
|
+
* upstream sign-in flow already passed CAPTCHA / OAuth / verification.
|
|
385
|
+
*/
|
|
386
|
+
desktopAuthorize = async (request) => {
|
|
387
|
+
const principal = await this.getPrincipal(request);
|
|
388
|
+
if (!principal) return Response.json({ error: {
|
|
389
|
+
code: 401,
|
|
390
|
+
status: "UNAUTHENTICATED",
|
|
391
|
+
message: "sign in first",
|
|
392
|
+
details: []
|
|
393
|
+
} }, { status: 401 });
|
|
394
|
+
let body;
|
|
395
|
+
try {
|
|
396
|
+
body = await request.json();
|
|
397
|
+
} catch {
|
|
398
|
+
return Response.json(require_utils_valid.invalidArgument("INVALID_ARGUMENT"), { status: 400 });
|
|
399
|
+
}
|
|
400
|
+
const codeChallenge = typeof body.code_challenge === "string" ? body.code_challenge : null;
|
|
401
|
+
if (!codeChallenge || body.code_challenge_method !== "S256" || !PKCE_FORMAT.test(codeChallenge)) return Response.json(require_utils_valid.invalidArgument("INVALID_ARGUMENT"), { status: 400 });
|
|
402
|
+
const code = (0, node_crypto.randomUUID)();
|
|
403
|
+
const record = {
|
|
404
|
+
name: principal.name,
|
|
405
|
+
cc: codeChallenge
|
|
406
|
+
};
|
|
407
|
+
await this.kv.setItem(this.getDesktopCodeKey(code), JSON.stringify(record), 300);
|
|
408
|
+
return Response.json({ code });
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Step 2 of the desktop / native app sign-in handshake. The desktop
|
|
412
|
+
* client hands back the auth code it received via the loopback callback,
|
|
413
|
+
* and we mint a fresh session for it — independent of the web cookie
|
|
414
|
+
* session that originally `authorize`'d the code, so logging out on
|
|
415
|
+
* either side doesn't cascade to the other.
|
|
416
|
+
*
|
|
417
|
+
* Cookie attributes: defaults to `SameSite=None; Secure` so the cookie
|
|
418
|
+
* survives cross-site fetches from the desktop renderer's `file://` /
|
|
419
|
+
* `app://` origin. The configured base `cookieOptions` provide
|
|
420
|
+
* everything else (`domain`, `path`, `httpOnly`, `maxAge`); the override
|
|
421
|
+
* is targeted at the SameSite/Secure pair only. In a non-HTTPS dev
|
|
422
|
+
* backend this combination is rejected by Chromium — see the desktop
|
|
423
|
+
* sign-in PRD for workarounds.
|
|
424
|
+
*
|
|
425
|
+
* No CSRF check needed — the request carries no cookie, the auth code
|
|
426
|
+
* itself is the credential, and KV's single-use removal blocks replay.
|
|
427
|
+
*/
|
|
428
|
+
desktopExchange = async (request) => {
|
|
429
|
+
let body;
|
|
430
|
+
try {
|
|
431
|
+
body = await request.json();
|
|
432
|
+
} catch {
|
|
433
|
+
return Response.json(require_utils_valid.invalidArgument("INVALID_ARGUMENT"), { status: 400 });
|
|
434
|
+
}
|
|
435
|
+
const code = typeof body.code === "string" ? body.code : null;
|
|
436
|
+
const verifier = typeof body.code_verifier === "string" ? body.code_verifier : null;
|
|
437
|
+
if (!code || !verifier || !PKCE_FORMAT.test(verifier)) return Response.json(require_utils_valid.invalidArgument("INVALID_ARGUMENT"), { status: 400 });
|
|
438
|
+
const key = this.getDesktopCodeKey(code);
|
|
439
|
+
const raw = await this.kv.getItem(key);
|
|
440
|
+
if (typeof raw !== "string") return Response.json(require_utils_valid.invalidArgument("INVALID_DESKTOP_CODE"), { status: 400 });
|
|
441
|
+
let record;
|
|
442
|
+
try {
|
|
443
|
+
record = JSON.parse(raw);
|
|
444
|
+
} catch {
|
|
445
|
+
await this.kv.removeItem(key);
|
|
446
|
+
return Response.json(require_utils_valid.invalidArgument("INVALID_DESKTOP_CODE"), { status: 400 });
|
|
447
|
+
}
|
|
448
|
+
const computed = (0, node_crypto.createHash)("sha256").update(verifier).digest();
|
|
449
|
+
const stored = Buffer.from(record.cc, "base64url");
|
|
450
|
+
if (stored.length !== computed.length || !(0, node_crypto.timingSafeEqual)(computed, stored)) return Response.json(require_utils_valid.invalidArgument("INVALID_DESKTOP_CODE"), { status: 400 });
|
|
451
|
+
await this.kv.removeItem(key);
|
|
452
|
+
const session = this.repository.createSession();
|
|
453
|
+
session.setAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME, record.name);
|
|
454
|
+
session.setAttribute("client_type", "desktop");
|
|
455
|
+
await this.repository.save(session);
|
|
456
|
+
const response = Response.json({ ok: true });
|
|
457
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
458
|
+
require_utils_http.setCookie(response, this.cookieName, session.getId(), {
|
|
459
|
+
...this.cookieOptions,
|
|
460
|
+
sameSite: "none",
|
|
461
|
+
secure: true,
|
|
462
|
+
maxAge
|
|
463
|
+
});
|
|
464
|
+
return response;
|
|
465
|
+
};
|
|
466
|
+
sendPhoneVerificationCode = async (_request, _sender) => {
|
|
467
|
+
return Response.json({ error: "not_implemented" }, { status: 501 });
|
|
468
|
+
};
|
|
469
|
+
loginPhone = async (_request, _handler) => {
|
|
470
|
+
return Response.json({ error: "not_implemented" }, { status: 501 });
|
|
471
|
+
};
|
|
472
|
+
kick = async (principal) => {
|
|
473
|
+
const sessions = await this.repository.findByPrincipalName(principal.name);
|
|
474
|
+
for (const sessionId of sessions.keys()) await this.repository.deleteById(sessionId);
|
|
475
|
+
};
|
|
476
|
+
isAuthenticated = async (request) => {
|
|
477
|
+
const sessionId = this.getSessionId(request);
|
|
478
|
+
if (!sessionId) return false;
|
|
479
|
+
const session = await this.repository.findById(sessionId);
|
|
480
|
+
if (!session) return false;
|
|
481
|
+
if (!session.getAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME)) return false;
|
|
482
|
+
return true;
|
|
483
|
+
};
|
|
484
|
+
getSession = async (request, create) => {
|
|
485
|
+
const sessionId = this.getSessionId(request);
|
|
486
|
+
if (!sessionId) if (create) {
|
|
487
|
+
const session = this.repository.createSession();
|
|
488
|
+
await this.repository.save(session);
|
|
489
|
+
return session;
|
|
490
|
+
} else return null;
|
|
491
|
+
const session = await this.repository.findById(sessionId);
|
|
492
|
+
if (!session) if (create) {
|
|
493
|
+
const session = this.repository.createSession();
|
|
494
|
+
await this.repository.save(session);
|
|
495
|
+
return session;
|
|
496
|
+
} else return null;
|
|
497
|
+
return session;
|
|
498
|
+
};
|
|
499
|
+
deleteSession = async (sessionId) => {
|
|
500
|
+
await this.repository.deleteById(sessionId);
|
|
501
|
+
};
|
|
502
|
+
getPrincipal = async (request) => {
|
|
503
|
+
const sessionId = this.getSessionId(request);
|
|
504
|
+
if (!sessionId) return null;
|
|
505
|
+
const session = await this.repository.findById(sessionId);
|
|
506
|
+
if (!session) return null;
|
|
507
|
+
const name = session.getAttribute(require_session_common.PRINCIPAL_NAME_INDEX_NAME);
|
|
508
|
+
if (!name) return null;
|
|
509
|
+
return { name: String(name) };
|
|
510
|
+
};
|
|
511
|
+
listSessions = async (principal) => {
|
|
512
|
+
const sessions = await this.repository.findByPrincipalName(principal.name);
|
|
513
|
+
return Array.from(sessions.values());
|
|
514
|
+
};
|
|
515
|
+
cleanupExpiredSessions = async (cleanupCount) => {
|
|
516
|
+
await this.repository.cleanupExpiredSessions(cleanupCount);
|
|
517
|
+
return Response.json({});
|
|
518
|
+
};
|
|
559
519
|
};
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
});
|
|
520
|
+
//#endregion
|
|
521
|
+
exports.Auth = Auth;
|
|
522
|
+
exports.PATH = PATH;
|
|
523
|
+
|
|
565
524
|
//# sourceMappingURL=index.cjs.map
|