@ereo/auth 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/auth.d.ts +220 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1028 -0
- package/dist/providers/index.d.ts +191 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +495 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/providers/index.ts
|
|
3
|
+
function credentials(config) {
|
|
4
|
+
return {
|
|
5
|
+
id: config.id || "credentials",
|
|
6
|
+
name: config.name || "Credentials",
|
|
7
|
+
type: "credentials",
|
|
8
|
+
authorize: config.authorize
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function github(config) {
|
|
12
|
+
const baseUrl = config.enterprise?.baseUrl || "https://github.com";
|
|
13
|
+
const apiBaseUrl = config.enterprise?.baseUrl ? `${config.enterprise.baseUrl}/api/v3` : "https://api.github.com";
|
|
14
|
+
const authorizationUrl = config.authorizationUrl || `${baseUrl}/login/oauth/authorize`;
|
|
15
|
+
const tokenUrl = config.tokenUrl || `${baseUrl}/login/oauth/access_token`;
|
|
16
|
+
const userInfoUrl = config.userInfoUrl || `${apiBaseUrl}/user`;
|
|
17
|
+
const scope = config.scope || ["read:user", "user:email"];
|
|
18
|
+
return {
|
|
19
|
+
id: "github",
|
|
20
|
+
name: "GitHub",
|
|
21
|
+
type: "oauth",
|
|
22
|
+
async authorize(credentials2) {
|
|
23
|
+
if (credentials2.user) {
|
|
24
|
+
return credentials2.user;
|
|
25
|
+
}
|
|
26
|
+
const code = credentials2.code;
|
|
27
|
+
if (!code)
|
|
28
|
+
return null;
|
|
29
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Accept: "application/json",
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
client_id: config.clientId,
|
|
37
|
+
client_secret: config.clientSecret,
|
|
38
|
+
code,
|
|
39
|
+
redirect_uri: config.redirectUri
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
const tokenData = await tokenResponse.json();
|
|
43
|
+
if (!tokenData.access_token)
|
|
44
|
+
return null;
|
|
45
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
48
|
+
"User-Agent": "EreoJS-Auth",
|
|
49
|
+
Accept: "application/json"
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const user = await userResponse.json();
|
|
53
|
+
let email = user.email;
|
|
54
|
+
if (!email) {
|
|
55
|
+
const emailsResponse = await fetch(`${apiBaseUrl}/user/emails`, {
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
58
|
+
"User-Agent": "EreoJS-Auth",
|
|
59
|
+
Accept: "application/json"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const emails = await emailsResponse.json();
|
|
63
|
+
const primaryEmail = emails.find((e) => e.primary && e.verified);
|
|
64
|
+
email = primaryEmail?.email || emails[0]?.email || null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: String(user.id),
|
|
68
|
+
email: email || undefined,
|
|
69
|
+
name: user.name || user.login,
|
|
70
|
+
avatar: user.avatar_url,
|
|
71
|
+
username: user.login
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
75
|
+
const params = new URLSearchParams({
|
|
76
|
+
client_id: config.clientId,
|
|
77
|
+
redirect_uri: redirectUri,
|
|
78
|
+
scope: scope.join(" "),
|
|
79
|
+
state
|
|
80
|
+
});
|
|
81
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
82
|
+
},
|
|
83
|
+
async handleCallback(params) {
|
|
84
|
+
const { code, redirectUri } = params;
|
|
85
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
Accept: "application/json",
|
|
89
|
+
"Content-Type": "application/json"
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
client_id: config.clientId,
|
|
93
|
+
client_secret: config.clientSecret,
|
|
94
|
+
code,
|
|
95
|
+
redirect_uri: redirectUri
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
const tokenData = await tokenResponse.json();
|
|
99
|
+
if (!tokenData.access_token)
|
|
100
|
+
return null;
|
|
101
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
104
|
+
"User-Agent": "EreoJS-Auth",
|
|
105
|
+
Accept: "application/json"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const user = await userResponse.json();
|
|
109
|
+
let email = user.email;
|
|
110
|
+
if (!email) {
|
|
111
|
+
const emailsResponse = await fetch(`${apiBaseUrl}/user/emails`, {
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `token ${tokenData.access_token}`,
|
|
114
|
+
"User-Agent": "EreoJS-Auth",
|
|
115
|
+
Accept: "application/json"
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const emails = await emailsResponse.json();
|
|
119
|
+
const primaryEmail = emails.find((e) => e.primary && e.verified);
|
|
120
|
+
email = primaryEmail?.email || emails[0]?.email || null;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
id: String(user.id),
|
|
124
|
+
email: email || undefined,
|
|
125
|
+
name: user.name || user.login,
|
|
126
|
+
avatar: user.avatar_url,
|
|
127
|
+
username: user.login
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function google(config) {
|
|
133
|
+
const authorizationUrl = config.authorizationUrl || "https://accounts.google.com/o/oauth2/v2/auth";
|
|
134
|
+
const tokenUrl = config.tokenUrl || "https://oauth2.googleapis.com/token";
|
|
135
|
+
const userInfoUrl = config.userInfoUrl || "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
136
|
+
const scope = config.scope || ["openid", "email", "profile"];
|
|
137
|
+
return {
|
|
138
|
+
id: "google",
|
|
139
|
+
name: "Google",
|
|
140
|
+
type: "oauth",
|
|
141
|
+
async authorize(credentials2) {
|
|
142
|
+
if (credentials2.user) {
|
|
143
|
+
return credentials2.user;
|
|
144
|
+
}
|
|
145
|
+
const code = credentials2.code;
|
|
146
|
+
if (!code)
|
|
147
|
+
return null;
|
|
148
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
152
|
+
},
|
|
153
|
+
body: new URLSearchParams({
|
|
154
|
+
code,
|
|
155
|
+
client_id: config.clientId,
|
|
156
|
+
client_secret: config.clientSecret,
|
|
157
|
+
redirect_uri: config.redirectUri || "postmessage",
|
|
158
|
+
grant_type: "authorization_code"
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
const tokenData = await tokenResponse.json();
|
|
162
|
+
if (!tokenData.access_token)
|
|
163
|
+
return null;
|
|
164
|
+
const userResponse = await fetch(`${userInfoUrl}?access_token=${tokenData.access_token}`);
|
|
165
|
+
const user = await userResponse.json();
|
|
166
|
+
if (config.hostedDomain && user.hd !== config.hostedDomain) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
id: user.id,
|
|
171
|
+
email: user.email,
|
|
172
|
+
name: user.name,
|
|
173
|
+
picture: user.picture,
|
|
174
|
+
emailVerified: user.verified_email
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
178
|
+
const params = new URLSearchParams({
|
|
179
|
+
client_id: config.clientId,
|
|
180
|
+
redirect_uri: redirectUri,
|
|
181
|
+
response_type: "code",
|
|
182
|
+
scope: scope.join(" "),
|
|
183
|
+
state,
|
|
184
|
+
access_type: "offline",
|
|
185
|
+
prompt: "consent"
|
|
186
|
+
});
|
|
187
|
+
if (config.hostedDomain) {
|
|
188
|
+
params.set("hd", config.hostedDomain);
|
|
189
|
+
}
|
|
190
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
191
|
+
},
|
|
192
|
+
async handleCallback(params) {
|
|
193
|
+
const { code, redirectUri } = params;
|
|
194
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
198
|
+
},
|
|
199
|
+
body: new URLSearchParams({
|
|
200
|
+
code,
|
|
201
|
+
client_id: config.clientId,
|
|
202
|
+
client_secret: config.clientSecret,
|
|
203
|
+
redirect_uri: redirectUri,
|
|
204
|
+
grant_type: "authorization_code"
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
const tokenData = await tokenResponse.json();
|
|
208
|
+
if (!tokenData.access_token)
|
|
209
|
+
return null;
|
|
210
|
+
const userResponse = await fetch(`${userInfoUrl}?access_token=${tokenData.access_token}`);
|
|
211
|
+
const user = await userResponse.json();
|
|
212
|
+
if (config.hostedDomain && user.hd !== config.hostedDomain) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
id: user.id,
|
|
217
|
+
email: user.email,
|
|
218
|
+
name: user.name,
|
|
219
|
+
picture: user.picture,
|
|
220
|
+
emailVerified: user.verified_email
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function discord(config) {
|
|
226
|
+
const authorizationUrl = config.authorizationUrl || "https://discord.com/api/oauth2/authorize";
|
|
227
|
+
const tokenUrl = config.tokenUrl || "https://discord.com/api/oauth2/token";
|
|
228
|
+
const userInfoUrl = config.userInfoUrl || "https://discord.com/api/users/@me";
|
|
229
|
+
const scope = config.scope || ["identify", "email"];
|
|
230
|
+
return {
|
|
231
|
+
id: "discord",
|
|
232
|
+
name: "Discord",
|
|
233
|
+
type: "oauth",
|
|
234
|
+
async authorize(credentials2) {
|
|
235
|
+
if (credentials2.user) {
|
|
236
|
+
return credentials2.user;
|
|
237
|
+
}
|
|
238
|
+
const code = credentials2.code;
|
|
239
|
+
if (!code)
|
|
240
|
+
return null;
|
|
241
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
245
|
+
},
|
|
246
|
+
body: new URLSearchParams({
|
|
247
|
+
client_id: config.clientId,
|
|
248
|
+
client_secret: config.clientSecret,
|
|
249
|
+
code,
|
|
250
|
+
grant_type: "authorization_code",
|
|
251
|
+
redirect_uri: config.redirectUri || ""
|
|
252
|
+
})
|
|
253
|
+
});
|
|
254
|
+
const tokenData = await tokenResponse.json();
|
|
255
|
+
if (!tokenData.access_token)
|
|
256
|
+
return null;
|
|
257
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
258
|
+
headers: {
|
|
259
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const user = await userResponse.json();
|
|
263
|
+
if (config.guildId) {
|
|
264
|
+
const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", {
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const guilds = await guildsResponse.json();
|
|
270
|
+
const isMember = guilds.some((g) => g.id === config.guildId);
|
|
271
|
+
if (!isMember)
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const avatarUrl = user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null;
|
|
275
|
+
return {
|
|
276
|
+
id: user.id,
|
|
277
|
+
email: user.email,
|
|
278
|
+
name: user.username,
|
|
279
|
+
avatar: avatarUrl || undefined,
|
|
280
|
+
discriminator: user.discriminator
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
284
|
+
const params = new URLSearchParams({
|
|
285
|
+
client_id: config.clientId,
|
|
286
|
+
redirect_uri: redirectUri,
|
|
287
|
+
response_type: "code",
|
|
288
|
+
scope: scope.join(" "),
|
|
289
|
+
state
|
|
290
|
+
});
|
|
291
|
+
return `${authorizationUrl}?${params.toString()}`;
|
|
292
|
+
},
|
|
293
|
+
async handleCallback(params) {
|
|
294
|
+
const { code, redirectUri } = params;
|
|
295
|
+
const tokenResponse = await fetch(tokenUrl, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
299
|
+
},
|
|
300
|
+
body: new URLSearchParams({
|
|
301
|
+
client_id: config.clientId,
|
|
302
|
+
client_secret: config.clientSecret,
|
|
303
|
+
code,
|
|
304
|
+
grant_type: "authorization_code",
|
|
305
|
+
redirect_uri: redirectUri
|
|
306
|
+
})
|
|
307
|
+
});
|
|
308
|
+
const tokenData = await tokenResponse.json();
|
|
309
|
+
if (!tokenData.access_token)
|
|
310
|
+
return null;
|
|
311
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
312
|
+
headers: {
|
|
313
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
const user = await userResponse.json();
|
|
317
|
+
if (config.guildId) {
|
|
318
|
+
const guildsResponse = await fetch("https://discord.com/api/users/@me/guilds", {
|
|
319
|
+
headers: {
|
|
320
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
const guilds = await guildsResponse.json();
|
|
324
|
+
const isMember = guilds.some((g) => g.id === config.guildId);
|
|
325
|
+
if (!isMember)
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const avatarUrl = user.avatar ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png` : null;
|
|
329
|
+
return {
|
|
330
|
+
id: user.id,
|
|
331
|
+
email: user.email,
|
|
332
|
+
name: user.username,
|
|
333
|
+
avatar: avatarUrl || undefined,
|
|
334
|
+
discriminator: user.discriminator
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function oauth(config) {
|
|
340
|
+
const scope = config.scope || [];
|
|
341
|
+
return {
|
|
342
|
+
id: config.id,
|
|
343
|
+
name: config.name,
|
|
344
|
+
type: "oauth",
|
|
345
|
+
async authorize(credentials2) {
|
|
346
|
+
if (credentials2.user) {
|
|
347
|
+
return credentials2.user;
|
|
348
|
+
}
|
|
349
|
+
const code = credentials2.code;
|
|
350
|
+
if (!code || !config.tokenUrl)
|
|
351
|
+
return null;
|
|
352
|
+
const tokenResponse = await fetch(config.tokenUrl, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: {
|
|
355
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
356
|
+
},
|
|
357
|
+
body: new URLSearchParams({
|
|
358
|
+
client_id: config.clientId,
|
|
359
|
+
client_secret: config.clientSecret,
|
|
360
|
+
code,
|
|
361
|
+
grant_type: "authorization_code",
|
|
362
|
+
redirect_uri: config.redirectUri || ""
|
|
363
|
+
})
|
|
364
|
+
});
|
|
365
|
+
const tokenData = await tokenResponse.json();
|
|
366
|
+
if (!tokenData.access_token)
|
|
367
|
+
return null;
|
|
368
|
+
const userInfoUrl = config.userInfoUrl || config.profileUrl;
|
|
369
|
+
if (!userInfoUrl) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
373
|
+
headers: {
|
|
374
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
const profile = await userResponse.json();
|
|
378
|
+
if (config.profile) {
|
|
379
|
+
return config.profile(profile, tokenData);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
id: profile.id || profile.sub || profile.user_id,
|
|
383
|
+
email: profile.email,
|
|
384
|
+
name: profile.name || profile.username
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
getAuthorizationUrl(state, redirectUri) {
|
|
388
|
+
if (!config.authorizationUrl) {
|
|
389
|
+
throw new Error(`Authorization URL not configured for provider: ${config.id}`);
|
|
390
|
+
}
|
|
391
|
+
const params = new URLSearchParams({
|
|
392
|
+
client_id: config.clientId,
|
|
393
|
+
redirect_uri: redirectUri,
|
|
394
|
+
response_type: "code",
|
|
395
|
+
state
|
|
396
|
+
});
|
|
397
|
+
if (scope.length > 0) {
|
|
398
|
+
params.set("scope", scope.join(" "));
|
|
399
|
+
}
|
|
400
|
+
return `${config.authorizationUrl}?${params.toString()}`;
|
|
401
|
+
},
|
|
402
|
+
async handleCallback(params) {
|
|
403
|
+
const { code, redirectUri } = params;
|
|
404
|
+
if (!config.tokenUrl) {
|
|
405
|
+
throw new Error(`Token URL not configured for provider: ${config.id}`);
|
|
406
|
+
}
|
|
407
|
+
const tokenResponse = await fetch(config.tokenUrl, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: {
|
|
410
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
411
|
+
},
|
|
412
|
+
body: new URLSearchParams({
|
|
413
|
+
client_id: config.clientId,
|
|
414
|
+
client_secret: config.clientSecret,
|
|
415
|
+
code,
|
|
416
|
+
grant_type: "authorization_code",
|
|
417
|
+
redirect_uri: redirectUri
|
|
418
|
+
})
|
|
419
|
+
});
|
|
420
|
+
const tokenData = await tokenResponse.json();
|
|
421
|
+
if (!tokenData.access_token)
|
|
422
|
+
return null;
|
|
423
|
+
const userInfoUrl = config.userInfoUrl || config.profileUrl;
|
|
424
|
+
if (!userInfoUrl) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const userResponse = await fetch(userInfoUrl, {
|
|
428
|
+
headers: {
|
|
429
|
+
Authorization: `Bearer ${tokenData.access_token}`
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
const profile = await userResponse.json();
|
|
433
|
+
if (config.profile) {
|
|
434
|
+
return config.profile(profile, tokenData);
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
id: profile.id || profile.sub || profile.user_id,
|
|
438
|
+
email: profile.email,
|
|
439
|
+
name: profile.name || profile.username
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function mock(config) {
|
|
445
|
+
return {
|
|
446
|
+
id: "mock",
|
|
447
|
+
name: "Mock",
|
|
448
|
+
type: "credentials",
|
|
449
|
+
async authorize() {
|
|
450
|
+
if (config?.delay) {
|
|
451
|
+
await new Promise((resolve) => setTimeout(resolve, config.delay));
|
|
452
|
+
}
|
|
453
|
+
if (config?.user) {
|
|
454
|
+
return config.user;
|
|
455
|
+
}
|
|
456
|
+
if (config?.session) {
|
|
457
|
+
return {
|
|
458
|
+
id: config.session.userId,
|
|
459
|
+
email: config.session.email,
|
|
460
|
+
name: config.session.name,
|
|
461
|
+
roles: config.session.roles,
|
|
462
|
+
...config.session.claims
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
id: "mock-user-123",
|
|
467
|
+
email: "mock@example.com",
|
|
468
|
+
name: "Mock User",
|
|
469
|
+
roles: ["user"]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function apiKey(config) {
|
|
475
|
+
return {
|
|
476
|
+
id: "api-key",
|
|
477
|
+
name: "API Key",
|
|
478
|
+
type: "credentials",
|
|
479
|
+
async authorize(credentials2) {
|
|
480
|
+
const key = credentials2.apiKey;
|
|
481
|
+
if (!key)
|
|
482
|
+
return null;
|
|
483
|
+
return config.validate(key);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/auth.ts
|
|
489
|
+
async function getSigningKey(secret) {
|
|
490
|
+
const encoder = new TextEncoder;
|
|
491
|
+
const keyData = encoder.encode(secret);
|
|
492
|
+
return crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
|
|
493
|
+
}
|
|
494
|
+
function base64UrlEncode(data) {
|
|
495
|
+
let binary = "";
|
|
496
|
+
for (let i = 0;i < data.length; i++) {
|
|
497
|
+
binary += String.fromCharCode(data[i]);
|
|
498
|
+
}
|
|
499
|
+
const base64 = btoa(binary);
|
|
500
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
501
|
+
}
|
|
502
|
+
function base64UrlDecode(str) {
|
|
503
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
504
|
+
while (base64.length % 4) {
|
|
505
|
+
base64 += "=";
|
|
506
|
+
}
|
|
507
|
+
const binary = atob(base64);
|
|
508
|
+
const bytes = new Uint8Array(binary.length);
|
|
509
|
+
for (let i = 0;i < binary.length; i++) {
|
|
510
|
+
bytes[i] = binary.charCodeAt(i);
|
|
511
|
+
}
|
|
512
|
+
return bytes;
|
|
513
|
+
}
|
|
514
|
+
async function signJWT(payload, secret) {
|
|
515
|
+
const encoder = new TextEncoder;
|
|
516
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
517
|
+
const headerBase64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));
|
|
518
|
+
const payloadBase64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));
|
|
519
|
+
const signingInput = `${headerBase64}.${payloadBase64}`;
|
|
520
|
+
const key = await getSigningKey(secret);
|
|
521
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(signingInput));
|
|
522
|
+
const signatureBase64 = base64UrlEncode(new Uint8Array(signature));
|
|
523
|
+
return `${signingInput}.${signatureBase64}`;
|
|
524
|
+
}
|
|
525
|
+
async function verifyJWT(token, secret) {
|
|
526
|
+
try {
|
|
527
|
+
const parts = token.split(".");
|
|
528
|
+
if (parts.length !== 3) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const [headerBase64, payloadBase64, signatureBase64] = parts;
|
|
532
|
+
const encoder = new TextEncoder;
|
|
533
|
+
const signingInput = `${headerBase64}.${payloadBase64}`;
|
|
534
|
+
const signature = base64UrlDecode(signatureBase64);
|
|
535
|
+
const key = await getSigningKey(secret);
|
|
536
|
+
const isValid = await crypto.subtle.verify("HMAC", key, signature.buffer, encoder.encode(signingInput));
|
|
537
|
+
if (!isValid) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const payloadJson = new TextDecoder().decode(base64UrlDecode(payloadBase64));
|
|
541
|
+
const payload = JSON.parse(payloadJson);
|
|
542
|
+
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
return payload;
|
|
546
|
+
} catch {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
class SessionStore {
|
|
552
|
+
maxAge;
|
|
553
|
+
sessions = new Map;
|
|
554
|
+
cleanupInterval = null;
|
|
555
|
+
constructor(maxAge) {
|
|
556
|
+
this.maxAge = maxAge;
|
|
557
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60 * 1000);
|
|
558
|
+
}
|
|
559
|
+
generateId() {
|
|
560
|
+
const array = new Uint8Array(32);
|
|
561
|
+
crypto.getRandomValues(array);
|
|
562
|
+
return base64UrlEncode(array);
|
|
563
|
+
}
|
|
564
|
+
set(sessionId, session) {
|
|
565
|
+
this.sessions.set(sessionId, {
|
|
566
|
+
session,
|
|
567
|
+
createdAt: Date.now(),
|
|
568
|
+
lastAccessed: Date.now()
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
get(sessionId) {
|
|
572
|
+
const stored = this.sessions.get(sessionId);
|
|
573
|
+
if (!stored) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
if (Date.now() - stored.createdAt > this.maxAge * 1000) {
|
|
577
|
+
this.sessions.delete(sessionId);
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
stored.lastAccessed = Date.now();
|
|
581
|
+
return stored.session;
|
|
582
|
+
}
|
|
583
|
+
delete(sessionId) {
|
|
584
|
+
return this.sessions.delete(sessionId);
|
|
585
|
+
}
|
|
586
|
+
update(sessionId, session) {
|
|
587
|
+
const stored = this.sessions.get(sessionId);
|
|
588
|
+
if (!stored) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
stored.session = session;
|
|
592
|
+
stored.lastAccessed = Date.now();
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
cleanup() {
|
|
596
|
+
const now = Date.now();
|
|
597
|
+
const entries = Array.from(this.sessions.entries());
|
|
598
|
+
for (const [id, stored] of entries) {
|
|
599
|
+
if (now - stored.createdAt > this.maxAge * 1000) {
|
|
600
|
+
this.sessions.delete(id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
destroy() {
|
|
605
|
+
if (this.cleanupInterval) {
|
|
606
|
+
clearInterval(this.cleanupInterval);
|
|
607
|
+
this.cleanupInterval = null;
|
|
608
|
+
}
|
|
609
|
+
this.sessions.clear();
|
|
610
|
+
}
|
|
611
|
+
get size() {
|
|
612
|
+
return this.sessions.size;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function parseCookie(cookieString, name) {
|
|
616
|
+
const cookies = cookieString.split(";");
|
|
617
|
+
for (const cookie of cookies) {
|
|
618
|
+
const [key, value] = cookie.trim().split("=");
|
|
619
|
+
if (key === name && value) {
|
|
620
|
+
return decodeURIComponent(value);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
function buildCookieHeader(name, value, options) {
|
|
626
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
627
|
+
if (options.maxAge !== undefined) {
|
|
628
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
629
|
+
}
|
|
630
|
+
if (options.expires) {
|
|
631
|
+
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
632
|
+
}
|
|
633
|
+
if (options.secure) {
|
|
634
|
+
parts.push("Secure");
|
|
635
|
+
}
|
|
636
|
+
if (options.httpOnly !== false) {
|
|
637
|
+
parts.push("HttpOnly");
|
|
638
|
+
}
|
|
639
|
+
if (options.sameSite) {
|
|
640
|
+
parts.push(`SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`);
|
|
641
|
+
}
|
|
642
|
+
if (options.domain) {
|
|
643
|
+
parts.push(`Domain=${options.domain}`);
|
|
644
|
+
}
|
|
645
|
+
parts.push(`Path=${options.path || "/"}`);
|
|
646
|
+
return parts.join("; ");
|
|
647
|
+
}
|
|
648
|
+
function buildClearCookieHeader(name, options) {
|
|
649
|
+
return buildCookieHeader(name, "", {
|
|
650
|
+
maxAge: 0,
|
|
651
|
+
expires: new Date(0),
|
|
652
|
+
...options
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
function createAuthPlugin(config) {
|
|
656
|
+
const secretValue = config.session?.secret || config.secret;
|
|
657
|
+
if (!secretValue) {
|
|
658
|
+
throw new Error("[auth] Session secret is required");
|
|
659
|
+
}
|
|
660
|
+
const secret = secretValue;
|
|
661
|
+
const sessionMaxAge = config.session?.maxAge ?? config.sessionDuration ?? 7 * 24 * 60 * 60;
|
|
662
|
+
const sessionStrategy = config.session?.strategy ?? "jwt";
|
|
663
|
+
const sessionUpdateAge = config.session?.updateAge ?? sessionMaxAge / 2;
|
|
664
|
+
const cookieName = config.cookie?.name || "ereo.session";
|
|
665
|
+
const cookieOptions = {
|
|
666
|
+
secure: config.cookie?.secure ?? false,
|
|
667
|
+
httpOnly: config.cookie?.httpOnly ?? true,
|
|
668
|
+
sameSite: config.cookie?.sameSite ?? "lax",
|
|
669
|
+
domain: config.cookie?.domain,
|
|
670
|
+
path: config.cookie?.path ?? "/"
|
|
671
|
+
};
|
|
672
|
+
const sessionStore = new SessionStore(sessionMaxAge);
|
|
673
|
+
const debug = config.debug ?? false;
|
|
674
|
+
const log = (...args) => {
|
|
675
|
+
if (debug) {
|
|
676
|
+
console.log("[auth]", ...args);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
async function createSession(user, providerId) {
|
|
680
|
+
const now = new Date;
|
|
681
|
+
const expiresAt = new Date(now.getTime() + sessionMaxAge * 1000);
|
|
682
|
+
const sessionId = sessionStore.generateId();
|
|
683
|
+
let session = {
|
|
684
|
+
userId: user.id,
|
|
685
|
+
email: user.email,
|
|
686
|
+
name: user.name,
|
|
687
|
+
roles: user.roles ?? [],
|
|
688
|
+
claims: {},
|
|
689
|
+
expiresAt,
|
|
690
|
+
sessionId,
|
|
691
|
+
issuedAt: now,
|
|
692
|
+
provider: providerId
|
|
693
|
+
};
|
|
694
|
+
for (const [key, value] of Object.entries(user)) {
|
|
695
|
+
if (!["id", "email", "name", "roles"].includes(key)) {
|
|
696
|
+
session.claims[key] = value;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (config.callbacks?.onSessionCreated) {
|
|
700
|
+
session = await config.callbacks.onSessionCreated(session);
|
|
701
|
+
}
|
|
702
|
+
if (sessionStrategy !== "jwt") {
|
|
703
|
+
sessionStore.set(sessionId, session);
|
|
704
|
+
}
|
|
705
|
+
return session;
|
|
706
|
+
}
|
|
707
|
+
async function createToken(session) {
|
|
708
|
+
const now = Math.floor(Date.now() / 1000);
|
|
709
|
+
let payload = {
|
|
710
|
+
sub: session.userId,
|
|
711
|
+
iat: Math.floor((session.issuedAt?.getTime() ?? Date.now()) / 1000),
|
|
712
|
+
exp: Math.floor((session.expiresAt?.getTime() ?? Date.now() + sessionMaxAge * 1000) / 1000),
|
|
713
|
+
sid: session.sessionId,
|
|
714
|
+
email: session.email,
|
|
715
|
+
name: session.name,
|
|
716
|
+
roles: session.roles,
|
|
717
|
+
provider: session.provider,
|
|
718
|
+
...session.claims
|
|
719
|
+
};
|
|
720
|
+
if (config.callbacks?.jwt) {
|
|
721
|
+
payload = await config.callbacks.jwt({ token: payload, session });
|
|
722
|
+
}
|
|
723
|
+
return signJWT(payload, secret);
|
|
724
|
+
}
|
|
725
|
+
async function sessionFromToken(token) {
|
|
726
|
+
const payload = await verifyJWT(token, secret);
|
|
727
|
+
if (!payload) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
if (sessionStrategy === "hybrid" && payload.sid) {
|
|
731
|
+
const storedSession = sessionStore.get(payload.sid);
|
|
732
|
+
if (!storedSession) {
|
|
733
|
+
log("Session not found in store:", payload.sid);
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
return storedSession;
|
|
737
|
+
}
|
|
738
|
+
const { sub, iat, exp, sid, email, name, roles, provider, ...claims } = payload;
|
|
739
|
+
let session = {
|
|
740
|
+
userId: sub,
|
|
741
|
+
email,
|
|
742
|
+
name,
|
|
743
|
+
roles,
|
|
744
|
+
claims,
|
|
745
|
+
expiresAt: new Date(exp * 1000),
|
|
746
|
+
sessionId: sid,
|
|
747
|
+
issuedAt: new Date(iat * 1000),
|
|
748
|
+
provider
|
|
749
|
+
};
|
|
750
|
+
if (config.callbacks?.session) {
|
|
751
|
+
session = await config.callbacks.session({ token: payload, session });
|
|
752
|
+
}
|
|
753
|
+
return session;
|
|
754
|
+
}
|
|
755
|
+
async function extractSession(request) {
|
|
756
|
+
const authHeader = request.headers.get("authorization");
|
|
757
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
758
|
+
const token = authHeader.slice(7);
|
|
759
|
+
const session = await sessionFromToken(token);
|
|
760
|
+
if (session) {
|
|
761
|
+
log("Session extracted from Authorization header");
|
|
762
|
+
return session;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const cookie = request.headers.get("cookie");
|
|
766
|
+
if (cookie) {
|
|
767
|
+
const sessionCookie = parseCookie(cookie, cookieName);
|
|
768
|
+
if (sessionCookie) {
|
|
769
|
+
if (sessionStrategy === "cookie") {
|
|
770
|
+
const session = sessionStore.get(sessionCookie);
|
|
771
|
+
if (session) {
|
|
772
|
+
log("Session extracted from cookie (session store)");
|
|
773
|
+
return session;
|
|
774
|
+
}
|
|
775
|
+
} else {
|
|
776
|
+
const session = await sessionFromToken(sessionCookie);
|
|
777
|
+
if (session) {
|
|
778
|
+
if (config.callbacks?.onSessionValidate) {
|
|
779
|
+
const isValid = await config.callbacks.onSessionValidate(session);
|
|
780
|
+
if (!isValid) {
|
|
781
|
+
log("Session validation failed");
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (session.expiresAt && new Date(session.expiresAt) < new Date) {
|
|
786
|
+
log("Session expired");
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
log("Session extracted from cookie (JWT)");
|
|
790
|
+
return session;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
name: "@ereo/auth",
|
|
799
|
+
async setup(context) {
|
|
800
|
+
log(`Initialized with ${config.providers?.length || 0} providers`);
|
|
801
|
+
console.log(`[auth] Initialized with ${config.providers?.length || 0} providers`);
|
|
802
|
+
if (config.providers) {
|
|
803
|
+
for (const provider of config.providers) {
|
|
804
|
+
log(`Provider registered: ${provider.id} (${provider.type})`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
configureServer(server) {
|
|
809
|
+
const authMiddleware = async (request, ctx, next) => {
|
|
810
|
+
const session = await extractSession(request);
|
|
811
|
+
let pendingCookieHeader = null;
|
|
812
|
+
const authContext = {
|
|
813
|
+
session,
|
|
814
|
+
signIn: async (providerId, credentials2) => {
|
|
815
|
+
const provider = config.providers?.find((p) => p.id === providerId);
|
|
816
|
+
if (!provider) {
|
|
817
|
+
throw new Error(`Auth provider not found: ${providerId}`);
|
|
818
|
+
}
|
|
819
|
+
const user = await provider.authorize(credentials2);
|
|
820
|
+
if (!user) {
|
|
821
|
+
throw new Error("Authentication failed");
|
|
822
|
+
}
|
|
823
|
+
if (config.callbacks?.onSignIn) {
|
|
824
|
+
await config.callbacks.onSignIn(user);
|
|
825
|
+
}
|
|
826
|
+
const newSession = await createSession(user, providerId);
|
|
827
|
+
authContext.session = newSession;
|
|
828
|
+
const token = await createToken(newSession);
|
|
829
|
+
pendingCookieHeader = buildCookieHeader(cookieName, sessionStrategy === "cookie" ? newSession.sessionId : token, {
|
|
830
|
+
...cookieOptions,
|
|
831
|
+
maxAge: sessionMaxAge
|
|
832
|
+
});
|
|
833
|
+
return newSession;
|
|
834
|
+
},
|
|
835
|
+
signOut: async () => {
|
|
836
|
+
if (authContext.session) {
|
|
837
|
+
if (config.callbacks?.onSignOut) {
|
|
838
|
+
await config.callbacks.onSignOut(authContext.session);
|
|
839
|
+
}
|
|
840
|
+
if (authContext.session.sessionId) {
|
|
841
|
+
sessionStore.delete(authContext.session.sessionId);
|
|
842
|
+
}
|
|
843
|
+
pendingCookieHeader = buildClearCookieHeader(cookieName, {
|
|
844
|
+
domain: cookieOptions.domain,
|
|
845
|
+
path: cookieOptions.path
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
authContext.session = null;
|
|
849
|
+
},
|
|
850
|
+
isAuthenticated: () => authContext.session !== null,
|
|
851
|
+
hasRole: (role) => authContext.session?.roles?.includes(role) ?? false,
|
|
852
|
+
hasAnyRole: (roles) => roles.some((role) => authContext.session?.roles?.includes(role)),
|
|
853
|
+
hasAllRoles: (roles) => roles.every((role) => authContext.session?.roles?.includes(role)),
|
|
854
|
+
getUser: () => {
|
|
855
|
+
if (!authContext.session)
|
|
856
|
+
return null;
|
|
857
|
+
return {
|
|
858
|
+
id: authContext.session.userId,
|
|
859
|
+
email: authContext.session.email,
|
|
860
|
+
name: authContext.session.name,
|
|
861
|
+
roles: authContext.session.roles,
|
|
862
|
+
...authContext.session.claims
|
|
863
|
+
};
|
|
864
|
+
},
|
|
865
|
+
getToken: async () => {
|
|
866
|
+
if (!authContext.session)
|
|
867
|
+
return null;
|
|
868
|
+
return createToken(authContext.session);
|
|
869
|
+
},
|
|
870
|
+
refreshSession: async () => {
|
|
871
|
+
if (!authContext.session)
|
|
872
|
+
return null;
|
|
873
|
+
const issuedAt = authContext.session.issuedAt?.getTime() ?? 0;
|
|
874
|
+
const now = Date.now();
|
|
875
|
+
if (now - issuedAt > sessionUpdateAge * 1000) {
|
|
876
|
+
const newExpiresAt = new Date(now + sessionMaxAge * 1000);
|
|
877
|
+
const newIssuedAt = new Date(now);
|
|
878
|
+
authContext.session = {
|
|
879
|
+
...authContext.session,
|
|
880
|
+
expiresAt: newExpiresAt,
|
|
881
|
+
issuedAt: newIssuedAt
|
|
882
|
+
};
|
|
883
|
+
if (authContext.session.sessionId) {
|
|
884
|
+
sessionStore.update(authContext.session.sessionId, authContext.session);
|
|
885
|
+
}
|
|
886
|
+
const token = await createToken(authContext.session);
|
|
887
|
+
pendingCookieHeader = buildCookieHeader(cookieName, sessionStrategy === "cookie" ? authContext.session.sessionId : token, {
|
|
888
|
+
...cookieOptions,
|
|
889
|
+
maxAge: sessionMaxAge
|
|
890
|
+
});
|
|
891
|
+
log("Session refreshed");
|
|
892
|
+
}
|
|
893
|
+
return authContext.session;
|
|
894
|
+
},
|
|
895
|
+
getCookieHeader: () => pendingCookieHeader
|
|
896
|
+
};
|
|
897
|
+
ctx.set("auth", authContext);
|
|
898
|
+
const response = await next();
|
|
899
|
+
if (pendingCookieHeader) {
|
|
900
|
+
const headers = new Headers(response.headers);
|
|
901
|
+
headers.append("Set-Cookie", pendingCookieHeader);
|
|
902
|
+
return new Response(response.body, {
|
|
903
|
+
status: response.status,
|
|
904
|
+
statusText: response.statusText,
|
|
905
|
+
headers
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
return response;
|
|
909
|
+
};
|
|
910
|
+
server.middlewares.push(authMiddleware);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function requireAuth(options) {
|
|
915
|
+
return {
|
|
916
|
+
auth: {
|
|
917
|
+
required: true,
|
|
918
|
+
roles: options?.roles,
|
|
919
|
+
permissions: options?.permissions,
|
|
920
|
+
redirect: options?.redirect,
|
|
921
|
+
unauthorized: options?.unauthorizedResponse,
|
|
922
|
+
check: async ({ context }) => {
|
|
923
|
+
const auth = context.get("auth");
|
|
924
|
+
if (!auth?.isAuthenticated()) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (options?.roles && !auth.hasAnyRole(options.roles)) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function optionalAuth() {
|
|
936
|
+
return {
|
|
937
|
+
auth: {
|
|
938
|
+
required: false
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function requireRoles(roles, options) {
|
|
943
|
+
return {
|
|
944
|
+
auth: {
|
|
945
|
+
required: true,
|
|
946
|
+
roles,
|
|
947
|
+
redirect: options?.redirect,
|
|
948
|
+
check: async ({ context }) => {
|
|
949
|
+
const auth = context.get("auth");
|
|
950
|
+
if (!auth?.isAuthenticated()) {
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
if (options?.requireAll) {
|
|
954
|
+
return auth.hasAllRoles(roles);
|
|
955
|
+
}
|
|
956
|
+
return auth.hasAnyRole(roles);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function useAuth(context) {
|
|
962
|
+
const auth = context.get("auth");
|
|
963
|
+
if (!auth) {
|
|
964
|
+
throw new Error("Auth context not found. Make sure createAuthPlugin is registered.");
|
|
965
|
+
}
|
|
966
|
+
return auth;
|
|
967
|
+
}
|
|
968
|
+
function getSession(context) {
|
|
969
|
+
const auth = context.get("auth");
|
|
970
|
+
return auth?.session ?? null;
|
|
971
|
+
}
|
|
972
|
+
function getUser(context) {
|
|
973
|
+
const auth = context.get("auth");
|
|
974
|
+
return auth?.getUser() ?? null;
|
|
975
|
+
}
|
|
976
|
+
function withAuth(handler, options) {
|
|
977
|
+
return async (args) => {
|
|
978
|
+
const auth = useAuth(args.context);
|
|
979
|
+
if (!auth.isAuthenticated()) {
|
|
980
|
+
throw new Response("Unauthorized", { status: 401 });
|
|
981
|
+
}
|
|
982
|
+
if (options?.roles && !auth.hasAnyRole(options.roles)) {
|
|
983
|
+
throw new Response("Forbidden", { status: 403 });
|
|
984
|
+
}
|
|
985
|
+
return handler({ ...args, auth });
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function getOAuthUrl(context, providerId, redirectUri) {
|
|
989
|
+
const config = context.get("authConfig");
|
|
990
|
+
const provider = config?.providers?.find((p) => p.id === providerId);
|
|
991
|
+
if (!provider || provider.type !== "oauth" || !provider.getAuthorizationUrl) {
|
|
992
|
+
throw new Error(`OAuth provider not found or not configured: ${providerId}`);
|
|
993
|
+
}
|
|
994
|
+
const state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
995
|
+
return provider.getAuthorizationUrl(state, redirectUri);
|
|
996
|
+
}
|
|
997
|
+
async function handleOAuthCallback(context, providerId, params) {
|
|
998
|
+
const auth = useAuth(context);
|
|
999
|
+
const config = context.get("authConfig");
|
|
1000
|
+
const provider = config?.providers?.find((p) => p.id === providerId);
|
|
1001
|
+
if (!provider || provider.type !== "oauth" || !provider.handleCallback) {
|
|
1002
|
+
throw new Error(`OAuth provider not found or not configured: ${providerId}`);
|
|
1003
|
+
}
|
|
1004
|
+
const user = await provider.handleCallback(params);
|
|
1005
|
+
if (!user) {
|
|
1006
|
+
throw new Error("OAuth authentication failed");
|
|
1007
|
+
}
|
|
1008
|
+
return auth.signIn(providerId, { user });
|
|
1009
|
+
}
|
|
1010
|
+
export {
|
|
1011
|
+
withAuth,
|
|
1012
|
+
useAuth,
|
|
1013
|
+
requireRoles,
|
|
1014
|
+
requireAuth,
|
|
1015
|
+
optionalAuth,
|
|
1016
|
+
oauth,
|
|
1017
|
+
mock,
|
|
1018
|
+
handleOAuthCallback,
|
|
1019
|
+
google,
|
|
1020
|
+
github,
|
|
1021
|
+
getUser,
|
|
1022
|
+
getSession,
|
|
1023
|
+
getOAuthUrl,
|
|
1024
|
+
discord,
|
|
1025
|
+
credentials,
|
|
1026
|
+
createAuthPlugin,
|
|
1027
|
+
apiKey
|
|
1028
|
+
};
|