@draftlab/auth 0.15.0 → 0.16.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/esm/allow.js +26 -0
- package/dist/esm/client.js +254 -0
- package/dist/esm/core.js +597 -0
- package/dist/esm/css.d.js +0 -0
- package/dist/esm/error.js +88 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/keys.js +126 -0
- package/dist/esm/mutex.js +53 -0
- package/dist/esm/pkce.js +87 -0
- package/dist/esm/provider/apple.js +15 -0
- package/dist/esm/provider/code.js +62 -0
- package/dist/esm/provider/discord.js +15 -0
- package/dist/esm/provider/facebook.js +15 -0
- package/dist/esm/provider/github.js +15 -0
- package/dist/esm/provider/gitlab.js +15 -0
- package/dist/esm/provider/google.js +16 -0
- package/dist/esm/provider/linkedin.js +15 -0
- package/dist/esm/provider/magiclink.js +83 -0
- package/dist/esm/provider/microsoft.js +15 -0
- package/dist/esm/provider/oauth2.js +130 -0
- package/dist/esm/provider/password.js +331 -0
- package/dist/esm/provider/provider.js +18 -0
- package/dist/esm/provider/reddit.js +15 -0
- package/dist/esm/provider/slack.js +15 -0
- package/dist/esm/provider/spotify.js +15 -0
- package/dist/esm/provider/twitch.js +15 -0
- package/dist/esm/provider/vercel.js +17 -0
- package/dist/esm/random.js +40 -0
- package/dist/esm/revocation.js +27 -0
- package/dist/esm/storage/memory.js +110 -0
- package/dist/esm/storage/storage.js +56 -0
- package/dist/esm/storage/turso.js +93 -0
- package/dist/esm/storage/unstorage.js +78 -0
- package/dist/esm/subject.js +7 -0
- package/dist/esm/themes/theme.js +115 -0
- package/dist/esm/toolkit/client.js +119 -0
- package/dist/esm/toolkit/index.js +25 -0
- package/dist/esm/toolkit/providers/facebook.js +11 -0
- package/dist/esm/toolkit/providers/github.js +11 -0
- package/dist/esm/toolkit/providers/google.js +11 -0
- package/dist/esm/toolkit/providers/strategy.js +0 -0
- package/dist/esm/toolkit/storage.js +81 -0
- package/dist/esm/toolkit/utils.js +18 -0
- package/dist/esm/types.js +0 -0
- package/dist/esm/ui/base.js +478 -0
- package/dist/esm/ui/code.js +186 -0
- package/dist/esm/ui/form.js +46 -0
- package/dist/esm/ui/icon.js +242 -0
- package/dist/esm/ui/magiclink.js +158 -0
- package/dist/esm/ui/password.js +435 -0
- package/dist/esm/ui/select.js +102 -0
- package/dist/esm/util.js +59 -0
- package/dist/{allow.d.mts → types/allow.d.ts} +9 -11
- package/dist/types/allow.d.ts.map +1 -0
- package/dist/types/client.d.ts +462 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/core.d.ts +113 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/{error.d.mts → types/error.d.ts} +95 -97
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/{keys.d.mts → types/keys.d.ts} +20 -24
- package/dist/types/keys.d.ts.map +1 -0
- package/dist/types/mutex.d.ts +42 -0
- package/dist/types/mutex.d.ts.map +1 -0
- package/dist/{pkce.d.mts → types/pkce.d.ts} +10 -11
- package/dist/types/pkce.d.ts.map +1 -0
- package/dist/types/provider/apple.d.ts +197 -0
- package/dist/types/provider/apple.d.ts.map +1 -0
- package/dist/types/provider/code.d.ts +288 -0
- package/dist/types/provider/code.d.ts.map +1 -0
- package/dist/types/provider/discord.d.ts +206 -0
- package/dist/types/provider/discord.d.ts.map +1 -0
- package/dist/types/provider/facebook.d.ts +200 -0
- package/dist/types/provider/facebook.d.ts.map +1 -0
- package/dist/types/provider/github.d.ts +220 -0
- package/dist/types/provider/github.d.ts.map +1 -0
- package/dist/types/provider/gitlab.d.ts +180 -0
- package/dist/types/provider/gitlab.d.ts.map +1 -0
- package/dist/types/provider/google.d.ts +158 -0
- package/dist/types/provider/google.d.ts.map +1 -0
- package/dist/types/provider/linkedin.d.ts +190 -0
- package/dist/types/provider/linkedin.d.ts.map +1 -0
- package/dist/types/provider/magiclink.d.ts +141 -0
- package/dist/types/provider/magiclink.d.ts.map +1 -0
- package/dist/types/provider/microsoft.d.ts +247 -0
- package/dist/types/provider/microsoft.d.ts.map +1 -0
- package/dist/types/provider/oauth2.d.ts +229 -0
- package/dist/types/provider/oauth2.d.ts.map +1 -0
- package/dist/types/provider/password.d.ts +408 -0
- package/dist/types/provider/password.d.ts.map +1 -0
- package/dist/types/provider/provider.d.ts +226 -0
- package/dist/types/provider/provider.d.ts.map +1 -0
- package/dist/types/provider/reddit.d.ts +159 -0
- package/dist/types/provider/reddit.d.ts.map +1 -0
- package/dist/types/provider/slack.d.ts +171 -0
- package/dist/types/provider/slack.d.ts.map +1 -0
- package/dist/types/provider/spotify.d.ts +168 -0
- package/dist/types/provider/spotify.d.ts.map +1 -0
- package/dist/types/provider/twitch.d.ts +163 -0
- package/dist/types/provider/twitch.d.ts.map +1 -0
- package/dist/types/provider/vercel.d.ts +294 -0
- package/dist/types/provider/vercel.d.ts.map +1 -0
- package/dist/{random.d.mts → types/random.d.ts} +4 -6
- package/dist/types/random.d.ts.map +1 -0
- package/dist/types/revocation.d.ts +76 -0
- package/dist/types/revocation.d.ts.map +1 -0
- package/dist/{storage/memory.d.mts → types/storage/memory.d.ts} +17 -21
- package/dist/types/storage/memory.d.ts.map +1 -0
- package/dist/types/storage/storage.d.ts +177 -0
- package/dist/types/storage/storage.d.ts.map +1 -0
- package/dist/{storage/turso.d.mts → types/storage/turso.d.ts} +4 -8
- package/dist/types/storage/turso.d.ts.map +1 -0
- package/dist/{storage/unstorage.d.mts → types/storage/unstorage.d.ts} +12 -11
- package/dist/types/storage/unstorage.d.ts.map +1 -0
- package/dist/types/subject.d.ts +115 -0
- package/dist/types/subject.d.ts.map +1 -0
- package/dist/types/themes/theme.d.ts +207 -0
- package/dist/types/themes/theme.d.ts.map +1 -0
- package/dist/types/toolkit/client.d.ts +235 -0
- package/dist/types/toolkit/client.d.ts.map +1 -0
- package/dist/types/toolkit/index.d.ts +45 -0
- package/dist/types/toolkit/index.d.ts.map +1 -0
- package/dist/types/toolkit/providers/facebook.d.ts +8 -0
- package/dist/types/toolkit/providers/facebook.d.ts.map +1 -0
- package/dist/types/toolkit/providers/github.d.ts +8 -0
- package/dist/types/toolkit/providers/github.d.ts.map +1 -0
- package/dist/types/toolkit/providers/google.d.ts +8 -0
- package/dist/types/toolkit/providers/google.d.ts.map +1 -0
- package/dist/types/toolkit/providers/strategy.d.ts +38 -0
- package/dist/types/toolkit/providers/strategy.d.ts.map +1 -0
- package/dist/{toolkit/storage.d.mts → types/toolkit/storage.d.ts} +37 -39
- package/dist/types/toolkit/storage.d.ts.map +1 -0
- package/dist/{toolkit/utils.d.mts → types/toolkit/utils.d.ts} +2 -4
- package/dist/types/toolkit/utils.d.ts.map +1 -0
- package/dist/types/types.d.ts +92 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/ui/base.d.ts +18 -0
- package/dist/types/ui/base.d.ts.map +1 -0
- package/dist/types/ui/code.d.ts +43 -0
- package/dist/types/ui/code.d.ts.map +1 -0
- package/dist/types/ui/form.d.ts +24 -0
- package/dist/types/ui/form.d.ts.map +1 -0
- package/dist/types/ui/icon.d.ts +60 -0
- package/dist/types/ui/icon.d.ts.map +1 -0
- package/dist/types/ui/magiclink.d.ts +41 -0
- package/dist/types/ui/magiclink.d.ts.map +1 -0
- package/dist/types/ui/password.d.ts +43 -0
- package/dist/types/ui/password.d.ts.map +1 -0
- package/dist/types/ui/select.d.ts +33 -0
- package/dist/types/ui/select.d.ts.map +1 -0
- package/dist/{util.d.mts → types/util.d.ts} +11 -13
- package/dist/types/util.d.ts.map +1 -0
- package/package.json +10 -16
- package/dist/adapters/node.d.mts +0 -18
- package/dist/adapters/node.mjs +0 -69
- package/dist/allow.mjs +0 -63
- package/dist/client.d.mts +0 -456
- package/dist/client.mjs +0 -283
- package/dist/core.d.mts +0 -110
- package/dist/core.mjs +0 -595
- package/dist/error.mjs +0 -237
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -3
- package/dist/keys.mjs +0 -146
- package/dist/mutex.d.mts +0 -44
- package/dist/mutex.mjs +0 -110
- package/dist/pkce.mjs +0 -157
- package/dist/provider/apple.d.mts +0 -111
- package/dist/provider/apple.mjs +0 -164
- package/dist/provider/code.d.mts +0 -228
- package/dist/provider/code.mjs +0 -246
- package/dist/provider/discord.d.mts +0 -146
- package/dist/provider/discord.mjs +0 -156
- package/dist/provider/facebook.d.mts +0 -142
- package/dist/provider/facebook.mjs +0 -150
- package/dist/provider/github.d.mts +0 -140
- package/dist/provider/github.mjs +0 -169
- package/dist/provider/gitlab.d.mts +0 -106
- package/dist/provider/gitlab.mjs +0 -147
- package/dist/provider/google.d.mts +0 -112
- package/dist/provider/google.mjs +0 -109
- package/dist/provider/linkedin.d.mts +0 -132
- package/dist/provider/linkedin.mjs +0 -142
- package/dist/provider/magiclink.d.mts +0 -89
- package/dist/provider/magiclink.mjs +0 -143
- package/dist/provider/microsoft.d.mts +0 -178
- package/dist/provider/microsoft.mjs +0 -177
- package/dist/provider/oauth2.d.mts +0 -176
- package/dist/provider/oauth2.mjs +0 -222
- package/dist/provider/passkey.d.mts +0 -104
- package/dist/provider/passkey.mjs +0 -320
- package/dist/provider/password.d.mts +0 -412
- package/dist/provider/password.mjs +0 -363
- package/dist/provider/provider.d.mts +0 -227
- package/dist/provider/provider.mjs +0 -44
- package/dist/provider/reddit.d.mts +0 -107
- package/dist/provider/reddit.mjs +0 -127
- package/dist/provider/slack.d.mts +0 -114
- package/dist/provider/slack.mjs +0 -138
- package/dist/provider/spotify.d.mts +0 -113
- package/dist/provider/spotify.mjs +0 -135
- package/dist/provider/totp.d.mts +0 -112
- package/dist/provider/totp.mjs +0 -191
- package/dist/provider/twitch.d.mts +0 -108
- package/dist/provider/twitch.mjs +0 -131
- package/dist/provider/vercel.d.mts +0 -177
- package/dist/provider/vercel.mjs +0 -230
- package/dist/random.mjs +0 -86
- package/dist/revocation.d.mts +0 -55
- package/dist/revocation.mjs +0 -63
- package/dist/router/context.d.mts +0 -21
- package/dist/router/context.mjs +0 -193
- package/dist/router/cookies.d.mts +0 -8
- package/dist/router/cookies.mjs +0 -13
- package/dist/router/index.d.mts +0 -21
- package/dist/router/index.mjs +0 -107
- package/dist/router/matcher.d.mts +0 -15
- package/dist/router/matcher.mjs +0 -76
- package/dist/router/middleware/cors.d.mts +0 -15
- package/dist/router/middleware/cors.mjs +0 -114
- package/dist/router/safe-request.d.mts +0 -52
- package/dist/router/safe-request.mjs +0 -160
- package/dist/router/types.d.mts +0 -67
- package/dist/router/types.mjs +0 -1
- package/dist/router/variables.d.mts +0 -12
- package/dist/router/variables.mjs +0 -20
- package/dist/storage/memory.mjs +0 -125
- package/dist/storage/storage.d.mts +0 -179
- package/dist/storage/storage.mjs +0 -104
- package/dist/storage/turso.mjs +0 -117
- package/dist/storage/unstorage.mjs +0 -103
- package/dist/subject.d.mts +0 -62
- package/dist/subject.mjs +0 -36
- package/dist/themes/theme.d.mts +0 -209
- package/dist/themes/theme.mjs +0 -120
- package/dist/toolkit/client.d.mts +0 -169
- package/dist/toolkit/client.mjs +0 -209
- package/dist/toolkit/index.d.mts +0 -9
- package/dist/toolkit/index.mjs +0 -9
- package/dist/toolkit/providers/facebook.d.mts +0 -12
- package/dist/toolkit/providers/facebook.mjs +0 -16
- package/dist/toolkit/providers/github.d.mts +0 -12
- package/dist/toolkit/providers/github.mjs +0 -16
- package/dist/toolkit/providers/google.d.mts +0 -12
- package/dist/toolkit/providers/google.mjs +0 -20
- package/dist/toolkit/providers/strategy.d.mts +0 -40
- package/dist/toolkit/providers/strategy.mjs +0 -1
- package/dist/toolkit/storage.mjs +0 -157
- package/dist/toolkit/utils.mjs +0 -30
- package/dist/types.d.mts +0 -94
- package/dist/types.mjs +0 -1
- package/dist/ui/base.d.mts +0 -30
- package/dist/ui/base.mjs +0 -407
- package/dist/ui/code.d.mts +0 -43
- package/dist/ui/code.mjs +0 -173
- package/dist/ui/form.d.mts +0 -32
- package/dist/ui/form.mjs +0 -49
- package/dist/ui/icon.d.mts +0 -58
- package/dist/ui/icon.mjs +0 -247
- package/dist/ui/magiclink.d.mts +0 -41
- package/dist/ui/magiclink.mjs +0 -152
- package/dist/ui/passkey.d.mts +0 -27
- package/dist/ui/passkey.mjs +0 -323
- package/dist/ui/password.d.mts +0 -42
- package/dist/ui/password.mjs +0 -402
- package/dist/ui/select.d.mts +0 -34
- package/dist/ui/select.mjs +0 -98
- package/dist/ui/totp.d.mts +0 -34
- package/dist/ui/totp.mjs +0 -270
- package/dist/util.mjs +0 -128
package/dist/esm/keys.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/keys.ts
|
|
2
|
+
import {
|
|
3
|
+
exportJWK,
|
|
4
|
+
exportPKCS8,
|
|
5
|
+
exportSPKI,
|
|
6
|
+
generateKeyPair,
|
|
7
|
+
importPKCS8,
|
|
8
|
+
importSPKI
|
|
9
|
+
} from "jose";
|
|
10
|
+
import { Mutex } from "./mutex";
|
|
11
|
+
import { generateSecureToken } from "./random";
|
|
12
|
+
import { Storage } from "./storage/storage";
|
|
13
|
+
var signingAlg = "ES256";
|
|
14
|
+
var encryptionAlg = "RSA-OAEP-512";
|
|
15
|
+
var signingKeyMutex = new Mutex;
|
|
16
|
+
var encryptionKeyMutex = new Mutex;
|
|
17
|
+
var signingKeys = async (storage) => {
|
|
18
|
+
return signingKeyMutex.runExclusive(async () => {
|
|
19
|
+
const results = [];
|
|
20
|
+
const scanner = Storage.scan(storage, ["signing:key"]);
|
|
21
|
+
for await (const [, value] of scanner) {
|
|
22
|
+
try {
|
|
23
|
+
const publicKey = await importSPKI(value.publicKey, value.alg, {
|
|
24
|
+
extractable: true
|
|
25
|
+
});
|
|
26
|
+
const privateKey = await importPKCS8(value.privateKey, value.alg);
|
|
27
|
+
const jwk2 = await exportJWK(publicKey);
|
|
28
|
+
jwk2.kid = value.id;
|
|
29
|
+
jwk2.use = "sig";
|
|
30
|
+
results.push({
|
|
31
|
+
id: value.id,
|
|
32
|
+
alg: signingAlg,
|
|
33
|
+
created: new Date(value.created),
|
|
34
|
+
expired: value.expired ? new Date(value.expired) : undefined,
|
|
35
|
+
public: publicKey,
|
|
36
|
+
private: privateKey,
|
|
37
|
+
jwk: jwk2
|
|
38
|
+
});
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
results.sort((a, b) => b.created.getTime() - a.created.getTime());
|
|
42
|
+
if (results.filter((item) => !item.expired).length) {
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
const key = await generateKeyPair(signingAlg, {
|
|
46
|
+
extractable: true
|
|
47
|
+
});
|
|
48
|
+
const serialized = {
|
|
49
|
+
id: generateSecureToken(16),
|
|
50
|
+
publicKey: await exportSPKI(key.publicKey),
|
|
51
|
+
privateKey: await exportPKCS8(key.privateKey),
|
|
52
|
+
created: Date.now(),
|
|
53
|
+
alg: signingAlg
|
|
54
|
+
};
|
|
55
|
+
await Storage.set(storage, ["signing:key", serialized.id], serialized);
|
|
56
|
+
const jwk = await exportJWK(key.publicKey);
|
|
57
|
+
jwk.kid = serialized.id;
|
|
58
|
+
jwk.use = "sig";
|
|
59
|
+
const newKeyPair = {
|
|
60
|
+
id: serialized.id,
|
|
61
|
+
alg: signingAlg,
|
|
62
|
+
created: new Date(serialized.created),
|
|
63
|
+
expired: serialized.expired ? new Date(serialized.expired) : undefined,
|
|
64
|
+
public: key.publicKey,
|
|
65
|
+
private: key.privateKey,
|
|
66
|
+
jwk
|
|
67
|
+
};
|
|
68
|
+
return [newKeyPair, ...results];
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
var encryptionKeys = async (storage) => {
|
|
72
|
+
return encryptionKeyMutex.runExclusive(async () => {
|
|
73
|
+
const results = [];
|
|
74
|
+
const scanner = Storage.scan(storage, ["encryption:key"]);
|
|
75
|
+
for await (const [, value] of scanner) {
|
|
76
|
+
try {
|
|
77
|
+
const publicKey = await importSPKI(value.publicKey, value.alg, {
|
|
78
|
+
extractable: true
|
|
79
|
+
});
|
|
80
|
+
const privateKey = await importPKCS8(value.privateKey, value.alg);
|
|
81
|
+
const jwk2 = await exportJWK(publicKey);
|
|
82
|
+
jwk2.kid = value.id;
|
|
83
|
+
results.push({
|
|
84
|
+
id: value.id,
|
|
85
|
+
alg: encryptionAlg,
|
|
86
|
+
created: new Date(value.created),
|
|
87
|
+
expired: value.expired ? new Date(value.expired) : undefined,
|
|
88
|
+
public: publicKey,
|
|
89
|
+
private: privateKey,
|
|
90
|
+
jwk: jwk2
|
|
91
|
+
});
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
results.sort((a, b) => b.created.getTime() - a.created.getTime());
|
|
95
|
+
if (results.filter((item) => !item.expired).length) {
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
const key = await generateKeyPair(encryptionAlg, {
|
|
99
|
+
extractable: true
|
|
100
|
+
});
|
|
101
|
+
const serialized = {
|
|
102
|
+
id: generateSecureToken(16),
|
|
103
|
+
publicKey: await exportSPKI(key.publicKey),
|
|
104
|
+
privateKey: await exportPKCS8(key.privateKey),
|
|
105
|
+
created: Date.now(),
|
|
106
|
+
alg: encryptionAlg
|
|
107
|
+
};
|
|
108
|
+
await Storage.set(storage, ["encryption:key", serialized.id], serialized);
|
|
109
|
+
const jwk = await exportJWK(key.publicKey);
|
|
110
|
+
jwk.kid = serialized.id;
|
|
111
|
+
const newKeyPair = {
|
|
112
|
+
id: serialized.id,
|
|
113
|
+
alg: encryptionAlg,
|
|
114
|
+
created: new Date(serialized.created),
|
|
115
|
+
expired: serialized.expired ? new Date(serialized.expired) : undefined,
|
|
116
|
+
public: key.publicKey,
|
|
117
|
+
private: key.privateKey,
|
|
118
|
+
jwk
|
|
119
|
+
};
|
|
120
|
+
return [newKeyPair, ...results];
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
export {
|
|
124
|
+
signingKeys,
|
|
125
|
+
encryptionKeys
|
|
126
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/mutex.ts
|
|
2
|
+
class Semaphore {
|
|
3
|
+
capacity;
|
|
4
|
+
available;
|
|
5
|
+
deferredTasks = [];
|
|
6
|
+
constructor(capacity) {
|
|
7
|
+
this.capacity = capacity;
|
|
8
|
+
this.available = capacity;
|
|
9
|
+
}
|
|
10
|
+
async acquire() {
|
|
11
|
+
if (this.available > 0) {
|
|
12
|
+
this.available--;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
this.deferredTasks.push(resolve);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
release() {
|
|
20
|
+
const deferredTask = this.deferredTasks.shift();
|
|
21
|
+
if (deferredTask != null) {
|
|
22
|
+
deferredTask();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (this.available < this.capacity) {
|
|
26
|
+
this.available++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class Mutex {
|
|
32
|
+
semaphore = new Semaphore(1);
|
|
33
|
+
get isLocked() {
|
|
34
|
+
return this.semaphore.available === 0;
|
|
35
|
+
}
|
|
36
|
+
async acquire() {
|
|
37
|
+
return this.semaphore.acquire();
|
|
38
|
+
}
|
|
39
|
+
release() {
|
|
40
|
+
this.semaphore.release();
|
|
41
|
+
}
|
|
42
|
+
async runExclusive(fn) {
|
|
43
|
+
await this.acquire();
|
|
44
|
+
try {
|
|
45
|
+
return await fn();
|
|
46
|
+
} finally {
|
|
47
|
+
this.release();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
Mutex
|
|
53
|
+
};
|
package/dist/esm/pkce.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/pkce.ts
|
|
2
|
+
import { base64url } from "jose";
|
|
3
|
+
var timingSafeCompare = (a, b) => {
|
|
4
|
+
if (typeof a !== "string" || typeof b !== "string") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const encoder = new TextEncoder;
|
|
8
|
+
const aBytes = encoder.encode(a);
|
|
9
|
+
const bBytes = encoder.encode(b);
|
|
10
|
+
let diff = aBytes.length ^ bBytes.length;
|
|
11
|
+
for (const [i, aByte] of aBytes.entries()) {
|
|
12
|
+
diff |= aByte ^ (bBytes[i] ?? 0);
|
|
13
|
+
}
|
|
14
|
+
return diff === 0;
|
|
15
|
+
};
|
|
16
|
+
var generateVerifier = (length) => {
|
|
17
|
+
const buffer = new Uint8Array(length);
|
|
18
|
+
crypto.getRandomValues(buffer);
|
|
19
|
+
return base64url.encode(buffer);
|
|
20
|
+
};
|
|
21
|
+
var generateChallenge = async (verifier, method) => {
|
|
22
|
+
if (method === "plain") {
|
|
23
|
+
return verifier;
|
|
24
|
+
}
|
|
25
|
+
const encoder = new TextEncoder;
|
|
26
|
+
const data = encoder.encode(verifier);
|
|
27
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
28
|
+
return base64url.encode(new Uint8Array(hash));
|
|
29
|
+
};
|
|
30
|
+
var generatePKCE = async (length = 48) => {
|
|
31
|
+
if (!Number.isInteger(length) || length < 32 || length > 96) {
|
|
32
|
+
throw new RangeError("Random buffer length must be between 32 and 96 bytes (generates 43-128 character verifier)");
|
|
33
|
+
}
|
|
34
|
+
const verifier = generateVerifier(length);
|
|
35
|
+
if (verifier.length < 43 || verifier.length > 128) {
|
|
36
|
+
throw new Error("Generated verifier does not meet requirements");
|
|
37
|
+
}
|
|
38
|
+
if (!/^[A-Za-z0-9_-]+$/.test(verifier)) {
|
|
39
|
+
throw new Error("Generated verifier is not valid base64url format");
|
|
40
|
+
}
|
|
41
|
+
const challenge = await generateChallenge(verifier, "S256");
|
|
42
|
+
return {
|
|
43
|
+
verifier,
|
|
44
|
+
challenge,
|
|
45
|
+
method: "S256"
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
var validatePKCE = async (verifier, challenge, method = "S256") => {
|
|
49
|
+
const MIN_PROCESSING_TIME = 50;
|
|
50
|
+
const RANDOM_JITTER_MAX = 20;
|
|
51
|
+
const startTime = performance.now();
|
|
52
|
+
let isValid = false;
|
|
53
|
+
let hasEarlyFailure = false;
|
|
54
|
+
const normalizedVerifier = String(verifier || "");
|
|
55
|
+
const normalizedChallenge = String(challenge || "");
|
|
56
|
+
const validations = [
|
|
57
|
+
typeof verifier === "string" && typeof challenge === "string" && verifier && challenge,
|
|
58
|
+
normalizedVerifier.length >= 43 && normalizedVerifier.length <= 128,
|
|
59
|
+
normalizedChallenge.length >= 43 && normalizedChallenge.length <= 128,
|
|
60
|
+
/^[A-Za-z0-9_-]+$/.test(normalizedVerifier),
|
|
61
|
+
/^[A-Za-z0-9_-]+$/.test(normalizedChallenge)
|
|
62
|
+
];
|
|
63
|
+
hasEarlyFailure = !validations.every(Boolean);
|
|
64
|
+
const verifierToUse = hasEarlyFailure ? "dummyverifier_".repeat(6) : normalizedVerifier;
|
|
65
|
+
try {
|
|
66
|
+
const generatedChallenge = await generateChallenge(verifierToUse, method);
|
|
67
|
+
const challengeToCompare = hasEarlyFailure ? "dummychallenge_".repeat(6) : normalizedChallenge;
|
|
68
|
+
const comparisonResult = timingSafeCompare(generatedChallenge, challengeToCompare);
|
|
69
|
+
isValid = !hasEarlyFailure && comparisonResult;
|
|
70
|
+
} catch {
|
|
71
|
+
isValid = false;
|
|
72
|
+
}
|
|
73
|
+
const elapsed = performance.now() - startTime;
|
|
74
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
75
|
+
if (remainingTime > 0 || elapsed < MIN_PROCESSING_TIME) {
|
|
76
|
+
const jitterArray = new Uint32Array(1);
|
|
77
|
+
crypto.getRandomValues(jitterArray);
|
|
78
|
+
const jitter = (jitterArray[0] ?? 0) / 4294967295 * RANDOM_JITTER_MAX;
|
|
79
|
+
const totalDelay = Math.max(remainingTime, MIN_PROCESSING_TIME - elapsed) + jitter;
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, totalDelay));
|
|
81
|
+
}
|
|
82
|
+
return isValid;
|
|
83
|
+
};
|
|
84
|
+
export {
|
|
85
|
+
validatePKCE,
|
|
86
|
+
generatePKCE
|
|
87
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/apple.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var AppleProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "apple",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://appleid.apple.com/auth/authorize",
|
|
9
|
+
token: "https://appleid.apple.com/auth/token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
AppleProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/provider/code.ts
|
|
2
|
+
import { generateUnbiasedDigits, timingSafeCompare } from "../random";
|
|
3
|
+
var CodeProvider = (config) => {
|
|
4
|
+
const codeLength = config.length || 6;
|
|
5
|
+
const generateCode = () => {
|
|
6
|
+
return generateUnbiasedDigits(codeLength);
|
|
7
|
+
};
|
|
8
|
+
return {
|
|
9
|
+
type: "code",
|
|
10
|
+
init(routes, ctx) {
|
|
11
|
+
const transition = async (c, nextState, formData, error) => {
|
|
12
|
+
await ctx.set(c, "provider", 60 * 60 * 24, nextState);
|
|
13
|
+
const response = await config.request(c.req.raw, nextState, formData, error);
|
|
14
|
+
return ctx.forward(c, response);
|
|
15
|
+
};
|
|
16
|
+
routes.get("/authorize", (c) => {
|
|
17
|
+
return transition(c, { type: "start" });
|
|
18
|
+
});
|
|
19
|
+
routes.post("/authorize", async (c) => {
|
|
20
|
+
const formData = await c.req.formData();
|
|
21
|
+
const currentState = await ctx.get(c, "provider");
|
|
22
|
+
const action = formData.get("action")?.toString();
|
|
23
|
+
if (action === "request" || action === "resend") {
|
|
24
|
+
const code = generateCode();
|
|
25
|
+
const formEntries = Object.fromEntries(formData);
|
|
26
|
+
const { action: _, ...claims } = formEntries;
|
|
27
|
+
await config.sendCode(claims, code);
|
|
28
|
+
return transition(c, {
|
|
29
|
+
type: "code",
|
|
30
|
+
resend: action === "resend",
|
|
31
|
+
claims,
|
|
32
|
+
code
|
|
33
|
+
}, formData);
|
|
34
|
+
} else if (action === "verify" && currentState?.type === "code") {
|
|
35
|
+
const enteredCode = formData.get("code")?.toString();
|
|
36
|
+
if (!(currentState.code && enteredCode && timingSafeCompare(currentState.code, enteredCode))) {
|
|
37
|
+
return transition(c, {
|
|
38
|
+
...currentState,
|
|
39
|
+
resend: false
|
|
40
|
+
}, formData, { type: "invalid_code" });
|
|
41
|
+
}
|
|
42
|
+
const authorization = await ctx.get(c, "authorization");
|
|
43
|
+
if (!authorization) {
|
|
44
|
+
return transition(c, { type: "start" }, formData, {
|
|
45
|
+
type: "invalid_claim",
|
|
46
|
+
key: "session",
|
|
47
|
+
value: "Authentication session expired"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
await ctx.unset(c, "provider");
|
|
51
|
+
return await ctx.success(c, {
|
|
52
|
+
claims: currentState.claims
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return transition(c, { type: "start" });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export {
|
|
61
|
+
CodeProvider
|
|
62
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/discord.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var DiscordProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "discord",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://discord.com/oauth2/authorize",
|
|
9
|
+
token: "https://discord.com/api/oauth2/token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
DiscordProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/facebook.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var FacebookProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "facebook",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://www.facebook.com/v18.0/dialog/oauth",
|
|
9
|
+
token: "https://graph.facebook.com/v18.0/oauth/access_token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
FacebookProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/github.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var GithubProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "github",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://github.com/login/oauth/authorize",
|
|
9
|
+
token: "https://github.com/login/oauth/access_token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
GithubProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/gitlab.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var GitlabProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "gitlab",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://gitlab.com/oauth/authorize",
|
|
9
|
+
token: "https://gitlab.com/oauth/token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
GitlabProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/provider/google.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var GoogleProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "google",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
9
|
+
token: "https://oauth2.googleapis.com/token",
|
|
10
|
+
jwks: "https://www.googleapis.com/oauth2/v3/certs"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
GoogleProvider
|
|
16
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/linkedin.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var LinkedInProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "linkedin",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://www.linkedin.com/oauth/v2/authorization",
|
|
9
|
+
token: "https://www.linkedin.com/oauth/v2/accessToken"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
LinkedInProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/provider/magiclink.ts
|
|
2
|
+
import { generateUnbiasedDigits, timingSafeCompare } from "../random";
|
|
3
|
+
var MagicLinkProvider = (config) => {
|
|
4
|
+
const generateToken = () => {
|
|
5
|
+
return generateUnbiasedDigits(32);
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
type: "magiclink",
|
|
9
|
+
init(routes, ctx) {
|
|
10
|
+
const transition = async (c, nextState, formData, error) => {
|
|
11
|
+
await ctx.set(c, "provider", 60 * 60 * 24, nextState);
|
|
12
|
+
const response = await config.request(c.req.raw, nextState, formData, error);
|
|
13
|
+
return ctx.forward(c, response);
|
|
14
|
+
};
|
|
15
|
+
routes.get("/authorize", async (c) => {
|
|
16
|
+
return transition(c, { type: "start" });
|
|
17
|
+
});
|
|
18
|
+
routes.post("/authorize", async (c) => {
|
|
19
|
+
const formData = await c.req.formData();
|
|
20
|
+
const action = formData.get("action")?.toString();
|
|
21
|
+
if (action === "request" || action === "resend") {
|
|
22
|
+
const token = generateToken();
|
|
23
|
+
const formEntries = Object.fromEntries(formData);
|
|
24
|
+
const { action: _, ...claims } = formEntries;
|
|
25
|
+
const baseUrl = new URL(c.req.url).origin;
|
|
26
|
+
const magicUrl = new URL(`/auth/${ctx.name}/verify`, baseUrl);
|
|
27
|
+
magicUrl.searchParams.set("token", token);
|
|
28
|
+
for (const [key, value] of Object.entries(claims)) {
|
|
29
|
+
if (typeof value === "string") {
|
|
30
|
+
magicUrl.searchParams.set(key, value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
await config.sendLink(claims, magicUrl.toString());
|
|
34
|
+
return transition(c, {
|
|
35
|
+
type: "sent",
|
|
36
|
+
resend: action === "resend",
|
|
37
|
+
claims,
|
|
38
|
+
token
|
|
39
|
+
}, formData);
|
|
40
|
+
}
|
|
41
|
+
return transition(c, { type: "start" });
|
|
42
|
+
});
|
|
43
|
+
routes.get("/verify", async (c) => {
|
|
44
|
+
const url = new URL(c.req.url);
|
|
45
|
+
const token = url.searchParams.get("token");
|
|
46
|
+
const storedState = await ctx.get(c, "provider");
|
|
47
|
+
if (!token || !storedState || storedState.type !== "sent") {
|
|
48
|
+
return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
|
|
49
|
+
}
|
|
50
|
+
if (!timingSafeCompare(storedState.token, token)) {
|
|
51
|
+
return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
|
|
52
|
+
}
|
|
53
|
+
const urlClaims = {};
|
|
54
|
+
for (const [key, value] of url.searchParams) {
|
|
55
|
+
if (key !== "token" && value) {
|
|
56
|
+
urlClaims[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const claimsMatch = Object.keys(storedState.claims).every((key) => {
|
|
60
|
+
const urlValue = urlClaims[key];
|
|
61
|
+
const storedValue = storedState.claims[key];
|
|
62
|
+
if (!urlValue || !storedValue)
|
|
63
|
+
return false;
|
|
64
|
+
return timingSafeCompare(storedValue, urlValue);
|
|
65
|
+
});
|
|
66
|
+
if (!claimsMatch) {
|
|
67
|
+
return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
|
|
68
|
+
}
|
|
69
|
+
const authorization = await ctx.get(c, "authorization");
|
|
70
|
+
if (!authorization) {
|
|
71
|
+
return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
|
|
72
|
+
}
|
|
73
|
+
await ctx.unset(c, "provider");
|
|
74
|
+
return await ctx.success(c, {
|
|
75
|
+
claims: storedState.claims
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export {
|
|
82
|
+
MagicLinkProvider
|
|
83
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/microsoft.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2";
|
|
3
|
+
var MicrosoftProvider = (config) => {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "microsoft",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`,
|
|
9
|
+
token: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
MicrosoftProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/provider/oauth2.ts
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
|
+
import { OauthError } from "../error";
|
|
4
|
+
import { generatePKCE } from "../pkce";
|
|
5
|
+
import { generateSecureToken, timingSafeCompare } from "../random";
|
|
6
|
+
import { getRelativeUrl } from "../util";
|
|
7
|
+
var Oauth2Provider = (config) => {
|
|
8
|
+
const authQuery = config.query || {};
|
|
9
|
+
const handleCallbackLogic = async (c, ctx, provider, code) => {
|
|
10
|
+
if (!(provider && code)) {
|
|
11
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
12
|
+
}
|
|
13
|
+
const tokenRequestBody = new URLSearchParams({
|
|
14
|
+
client_id: config.clientID,
|
|
15
|
+
client_secret: config.clientSecret,
|
|
16
|
+
code,
|
|
17
|
+
grant_type: "authorization_code",
|
|
18
|
+
redirect_uri: provider.redirect,
|
|
19
|
+
...provider.codeVerifier ? { code_verifier: provider.codeVerifier } : {}
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(config.endpoint.token, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: {
|
|
25
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
26
|
+
Accept: "application/json"
|
|
27
|
+
},
|
|
28
|
+
body: tokenRequestBody.toString()
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Token request failed with status ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
const tokenData = await response.json();
|
|
34
|
+
if (tokenData.error) {
|
|
35
|
+
throw new OauthError(tokenData.error, tokenData.error_description || "");
|
|
36
|
+
}
|
|
37
|
+
if (tokenData.id_token && config.endpoint.jwks) {
|
|
38
|
+
try {
|
|
39
|
+
const jwks = createRemoteJWKSet(new URL(config.endpoint.jwks));
|
|
40
|
+
await jwtVerify(tokenData.id_token, jwks, {
|
|
41
|
+
issuer: config.endpoint.authorization.split("/").slice(0, 3).join("/"),
|
|
42
|
+
clockTolerance: 60
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new OauthError("invalid_request", `ID token validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return await ctx.success(c, {
|
|
49
|
+
clientID: config.clientID,
|
|
50
|
+
tokenset: {
|
|
51
|
+
get access() {
|
|
52
|
+
return tokenData.access_token;
|
|
53
|
+
},
|
|
54
|
+
get refresh() {
|
|
55
|
+
return tokenData.refresh_token || "";
|
|
56
|
+
},
|
|
57
|
+
get expiry() {
|
|
58
|
+
return tokenData.expires_in || 0;
|
|
59
|
+
},
|
|
60
|
+
get raw() {
|
|
61
|
+
return tokenData;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error instanceof OauthError) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
throw new OauthError("server_error", `Token exchange failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
type: config.type || "oauth2",
|
|
74
|
+
init(routes, ctx) {
|
|
75
|
+
routes.get("/authorize", async (c) => {
|
|
76
|
+
const state = generateSecureToken();
|
|
77
|
+
const pkce = config.pkce ? await generatePKCE() : undefined;
|
|
78
|
+
await ctx.set(c, "provider", 60 * 10, {
|
|
79
|
+
state,
|
|
80
|
+
redirect: getRelativeUrl(c, "./callback"),
|
|
81
|
+
codeVerifier: pkce?.verifier
|
|
82
|
+
});
|
|
83
|
+
const authorizationUrl = new URL(config.endpoint.authorization);
|
|
84
|
+
authorizationUrl.searchParams.set("client_id", config.clientID);
|
|
85
|
+
authorizationUrl.searchParams.set("redirect_uri", getRelativeUrl(c, "./callback"));
|
|
86
|
+
authorizationUrl.searchParams.set("response_type", "code");
|
|
87
|
+
authorizationUrl.searchParams.set("state", state);
|
|
88
|
+
authorizationUrl.searchParams.set("scope", config.scopes.join(" "));
|
|
89
|
+
if (pkce) {
|
|
90
|
+
authorizationUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
91
|
+
authorizationUrl.searchParams.set("code_challenge_method", pkce.method);
|
|
92
|
+
}
|
|
93
|
+
for (const [key, value] of Object.entries(authQuery)) {
|
|
94
|
+
authorizationUrl.searchParams.set(key, value);
|
|
95
|
+
}
|
|
96
|
+
return c.redirect(authorizationUrl.toString());
|
|
97
|
+
});
|
|
98
|
+
routes.get("/callback", async (c) => {
|
|
99
|
+
const provider = await ctx.get(c, "provider");
|
|
100
|
+
const code = c.req.query("code");
|
|
101
|
+
const state = c.req.query("state");
|
|
102
|
+
const error = c.req.query("error");
|
|
103
|
+
if (error) {
|
|
104
|
+
throw new OauthError(error, c.req.query("error_description") || "");
|
|
105
|
+
}
|
|
106
|
+
if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) {
|
|
107
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
108
|
+
}
|
|
109
|
+
return await handleCallbackLogic(c, ctx, provider, code);
|
|
110
|
+
});
|
|
111
|
+
routes.post("/callback", async (c) => {
|
|
112
|
+
const provider = await ctx.get(c, "provider");
|
|
113
|
+
const formData = await c.req.formData();
|
|
114
|
+
const code = formData.get("code")?.toString();
|
|
115
|
+
const state = formData.get("state")?.toString();
|
|
116
|
+
const error = formData.get("error")?.toString();
|
|
117
|
+
if (error) {
|
|
118
|
+
throw new OauthError(error, formData.get("error_description")?.toString() || "");
|
|
119
|
+
}
|
|
120
|
+
if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) {
|
|
121
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
122
|
+
}
|
|
123
|
+
return await handleCallbackLogic(c, ctx, provider, code);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
export {
|
|
129
|
+
Oauth2Provider
|
|
130
|
+
};
|