@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/provider/oauth2.mjs
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { getRelativeUrl } from "../util.mjs";
|
|
2
|
-
import { OauthError } from "../error.mjs";
|
|
3
|
-
import { generatePKCE } from "../pkce.mjs";
|
|
4
|
-
import { generateSecureToken, timingSafeCompare } from "../random.mjs";
|
|
5
|
-
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
6
|
-
|
|
7
|
-
//#region src/provider/oauth2.ts
|
|
8
|
-
/**
|
|
9
|
-
* OAuth 2.0 authentication provider for Draft Auth.
|
|
10
|
-
* Implements the Authorization Code Grant flow with optional PKCE support.
|
|
11
|
-
*
|
|
12
|
-
* ## Quick Setup
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* import { Oauth2Provider } from "@draftlab/auth/provider/oauth2"
|
|
16
|
-
*
|
|
17
|
-
* export default issuer({
|
|
18
|
-
* providers: {
|
|
19
|
-
* github: Oauth2Provider({
|
|
20
|
-
* clientID: process.env.GITHUB_CLIENT_ID,
|
|
21
|
-
* clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
22
|
-
* endpoint: {
|
|
23
|
-
* authorization: "https://github.com/login/oauth/authorize",
|
|
24
|
-
* token: "https://github.com/login/oauth/access_token"
|
|
25
|
-
* },
|
|
26
|
-
* scopes: ["user:email", "read:user"]
|
|
27
|
-
* }),
|
|
28
|
-
* discord: Oauth2Provider({
|
|
29
|
-
* clientID: process.env.DISCORD_CLIENT_ID,
|
|
30
|
-
* clientSecret: process.env.DISCORD_CLIENT_SECRET,
|
|
31
|
-
* endpoint: {
|
|
32
|
-
* authorization: "https://discord.com/api/oauth2/authorize",
|
|
33
|
-
* token: "https://discord.com/api/oauth2/token"
|
|
34
|
-
* },
|
|
35
|
-
* scopes: ["identify", "email"],
|
|
36
|
-
* pkce: true // Required by some providers
|
|
37
|
-
* })
|
|
38
|
-
* }
|
|
39
|
-
* })
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* ## Features
|
|
43
|
-
*
|
|
44
|
-
* - **Authorization Code Grant**: Secure server-side OAuth 2.0 flow
|
|
45
|
-
* - **PKCE Support**: Optional Proof Key for Code Exchange for enhanced security
|
|
46
|
-
* - **Flexible Endpoints**: Configure custom authorization and token endpoints
|
|
47
|
-
* - **Custom Parameters**: Support for provider-specific authorization parameters
|
|
48
|
-
*
|
|
49
|
-
* ## User Data
|
|
50
|
-
*
|
|
51
|
-
* The provider returns access tokens:
|
|
52
|
-
*
|
|
53
|
-
* ```ts
|
|
54
|
-
* success: async (ctx, value) => {
|
|
55
|
-
* if (value.provider === "oauth2") {
|
|
56
|
-
* // Access token for API calls: value.tokenset.access
|
|
57
|
-
* // Refresh token (if provided): value.tokenset.refresh
|
|
58
|
-
* // Client ID used: value.clientID
|
|
59
|
-
* }
|
|
60
|
-
* }
|
|
61
|
-
* ```
|
|
62
|
-
*
|
|
63
|
-
* @packageDocumentation
|
|
64
|
-
*/
|
|
65
|
-
/**
|
|
66
|
-
* Creates an OAuth 2.0 authentication provider.
|
|
67
|
-
* Implements the Authorization Code Grant flow with optional PKCE support.
|
|
68
|
-
*
|
|
69
|
-
* @param config - OAuth 2.0 provider configuration
|
|
70
|
-
* @returns Provider instance implementing OAuth 2.0 authentication
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* // GitHub provider with basic configuration
|
|
75
|
-
* const githubProvider = Oauth2Provider({
|
|
76
|
-
* clientID: process.env.GITHUB_CLIENT_ID,
|
|
77
|
-
* clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
78
|
-
* endpoint: {
|
|
79
|
-
* authorization: "https://github.com/login/oauth/authorize",
|
|
80
|
-
* token: "https://github.com/login/oauth/access_token"
|
|
81
|
-
* },
|
|
82
|
-
* scopes: ["user:email", "read:user"]
|
|
83
|
-
* })
|
|
84
|
-
*
|
|
85
|
-
* // Provider with PKCE and custom parameters
|
|
86
|
-
* const customProvider = Oauth2Provider({
|
|
87
|
-
* clientID: "my-client-id",
|
|
88
|
-
* clientSecret: "my-client-secret",
|
|
89
|
-
* endpoint: {
|
|
90
|
-
* authorization: "https://provider.com/oauth/authorize",
|
|
91
|
-
* token: "https://provider.com/oauth/token",
|
|
92
|
-
* jwks: "https://provider.com/.well-known/jwks.json"
|
|
93
|
-
* },
|
|
94
|
-
* scopes: ["read", "write"],
|
|
95
|
-
* pkce: true,
|
|
96
|
-
* query: {
|
|
97
|
-
* prompt: "consent",
|
|
98
|
-
* access_type: "offline"
|
|
99
|
-
* }
|
|
100
|
-
* })
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
const Oauth2Provider = (config) => {
|
|
104
|
-
const authQuery = config.query || {};
|
|
105
|
-
/**
|
|
106
|
-
* Handles the OAuth 2.0 callback logic for both GET and POST requests.
|
|
107
|
-
* Exchanges the authorization code for tokens and processes the response.
|
|
108
|
-
*/
|
|
109
|
-
const handleCallbackLogic = async (c, ctx, provider, code) => {
|
|
110
|
-
if (!(provider && code)) return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
111
|
-
const tokenRequestBody = new URLSearchParams({
|
|
112
|
-
client_id: config.clientID,
|
|
113
|
-
client_secret: config.clientSecret,
|
|
114
|
-
code,
|
|
115
|
-
grant_type: "authorization_code",
|
|
116
|
-
redirect_uri: provider.redirect,
|
|
117
|
-
...provider.codeVerifier ? { code_verifier: provider.codeVerifier } : {}
|
|
118
|
-
});
|
|
119
|
-
try {
|
|
120
|
-
const response = await fetch(config.endpoint.token, {
|
|
121
|
-
method: "POST",
|
|
122
|
-
headers: {
|
|
123
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
124
|
-
Accept: "application/json"
|
|
125
|
-
},
|
|
126
|
-
body: tokenRequestBody.toString()
|
|
127
|
-
});
|
|
128
|
-
if (!response.ok) throw new Error(`Token request failed with status ${response.status}`);
|
|
129
|
-
const tokenData = await response.json();
|
|
130
|
-
if (tokenData.error) throw new OauthError(tokenData.error, tokenData.error_description || "");
|
|
131
|
-
if (tokenData.id_token && config.endpoint.jwks) try {
|
|
132
|
-
const jwks = createRemoteJWKSet(new URL(config.endpoint.jwks));
|
|
133
|
-
await jwtVerify(tokenData.id_token, jwks, {
|
|
134
|
-
issuer: config.endpoint.authorization.split("/").slice(0, 3).join("/"),
|
|
135
|
-
clockTolerance: 60
|
|
136
|
-
});
|
|
137
|
-
} catch (error) {
|
|
138
|
-
throw new OauthError("invalid_request", `ID token validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
139
|
-
}
|
|
140
|
-
return await ctx.success(c, {
|
|
141
|
-
clientID: config.clientID,
|
|
142
|
-
tokenset: {
|
|
143
|
-
get access() {
|
|
144
|
-
return tokenData.access_token;
|
|
145
|
-
},
|
|
146
|
-
get refresh() {
|
|
147
|
-
return tokenData.refresh_token || "";
|
|
148
|
-
},
|
|
149
|
-
get expiry() {
|
|
150
|
-
return tokenData.expires_in || 0;
|
|
151
|
-
},
|
|
152
|
-
get raw() {
|
|
153
|
-
return tokenData;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (error instanceof OauthError) throw error;
|
|
159
|
-
throw new OauthError("server_error", `Token exchange failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
return {
|
|
163
|
-
type: config.type || "oauth2",
|
|
164
|
-
init(routes, ctx) {
|
|
165
|
-
/**
|
|
166
|
-
* Initiates OAuth 2.0 authorization flow.
|
|
167
|
-
* Redirects user to the provider's authorization endpoint with proper parameters.
|
|
168
|
-
*/
|
|
169
|
-
routes.get("/authorize", async (c) => {
|
|
170
|
-
const state = generateSecureToken();
|
|
171
|
-
const pkce = config.pkce ? await generatePKCE() : void 0;
|
|
172
|
-
await ctx.set(c, "provider", 600, {
|
|
173
|
-
state,
|
|
174
|
-
redirect: getRelativeUrl(c, "./callback"),
|
|
175
|
-
codeVerifier: pkce?.verifier
|
|
176
|
-
});
|
|
177
|
-
const authorizationUrl = new URL(config.endpoint.authorization);
|
|
178
|
-
authorizationUrl.searchParams.set("client_id", config.clientID);
|
|
179
|
-
authorizationUrl.searchParams.set("redirect_uri", getRelativeUrl(c, "./callback"));
|
|
180
|
-
authorizationUrl.searchParams.set("response_type", "code");
|
|
181
|
-
authorizationUrl.searchParams.set("state", state);
|
|
182
|
-
authorizationUrl.searchParams.set("scope", config.scopes.join(" "));
|
|
183
|
-
if (pkce) {
|
|
184
|
-
authorizationUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
185
|
-
authorizationUrl.searchParams.set("code_challenge_method", pkce.method);
|
|
186
|
-
}
|
|
187
|
-
for (const [key, value] of Object.entries(authQuery)) authorizationUrl.searchParams.set(key, value);
|
|
188
|
-
return c.redirect(authorizationUrl.toString());
|
|
189
|
-
});
|
|
190
|
-
/**
|
|
191
|
-
* Handles OAuth 2.0 callback via query parameters (GET request).
|
|
192
|
-
* Standard OAuth 2.0 callback method for most providers.
|
|
193
|
-
*/
|
|
194
|
-
routes.get("/callback", async (c) => {
|
|
195
|
-
const provider = await ctx.get(c, "provider");
|
|
196
|
-
const code = c.query("code");
|
|
197
|
-
const state = c.query("state");
|
|
198
|
-
const error = c.query("error");
|
|
199
|
-
if (error) throw new OauthError(error, c.query("error_description") || "");
|
|
200
|
-
if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
201
|
-
return await handleCallbackLogic(c, ctx, provider, code);
|
|
202
|
-
});
|
|
203
|
-
/**
|
|
204
|
-
* Handles OAuth 2.0 callback via form data (POST request).
|
|
205
|
-
* Alternative callback method supported by some providers.
|
|
206
|
-
*/
|
|
207
|
-
routes.post("/callback", async (c) => {
|
|
208
|
-
const provider = await ctx.get(c, "provider");
|
|
209
|
-
const formData = await c.formData();
|
|
210
|
-
const code = formData.get("code")?.toString();
|
|
211
|
-
const state = formData.get("state")?.toString();
|
|
212
|
-
const error = formData.get("error")?.toString();
|
|
213
|
-
if (error) throw new OauthError(error, formData.get("error_description")?.toString() || "");
|
|
214
|
-
if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
215
|
-
return await handleCallbackLogic(c, ctx, provider, code);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
//#endregion
|
|
222
|
-
export { Oauth2Provider };
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Provider } from "./provider.mjs";
|
|
2
|
-
import { AuthenticatorSelectionCriteria, AuthenticatorTransportFuture, Base64URLString, CredentialDeviceType } from "@simplewebauthn/server";
|
|
3
|
-
|
|
4
|
-
//#region src/provider/passkey.d.ts
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* User model for passkey authentication.
|
|
8
|
-
* Contains the core user data needed for WebAuthn operations.
|
|
9
|
-
*/
|
|
10
|
-
interface UserModel {
|
|
11
|
-
id: string;
|
|
12
|
-
username: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Original PasskeyModel structure for in-memory use.
|
|
16
|
-
* Represents a registered credential with public key as Uint8Array.
|
|
17
|
-
*/
|
|
18
|
-
interface PasskeyModel {
|
|
19
|
-
id: string;
|
|
20
|
-
publicKey: Uint8Array<ArrayBuffer>;
|
|
21
|
-
userId: string;
|
|
22
|
-
webauthnUserID: string;
|
|
23
|
-
counter: number;
|
|
24
|
-
deviceType: CredentialDeviceType;
|
|
25
|
-
backedUp: boolean;
|
|
26
|
-
transports?: AuthenticatorTransportFuture[];
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* PasskeyModel version for KV storage with publicKey as string.
|
|
30
|
-
* Used for storing credentials in a key-value store.
|
|
31
|
-
*/
|
|
32
|
-
interface PasskeyModelStored extends Omit<PasskeyModel, "publicKey"> {
|
|
33
|
-
publicKey: string;
|
|
34
|
-
}
|
|
35
|
-
declare const DEFAULT_COPY: {
|
|
36
|
-
error_user_not_allowed: string;
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Configuration for the PasskeyProvider.
|
|
40
|
-
* Defines how the passkey authentication flow should behave.
|
|
41
|
-
*/
|
|
42
|
-
interface PasskeyProviderConfig {
|
|
43
|
-
/**
|
|
44
|
-
* Custom authorization handler that generates the UI for authorization.
|
|
45
|
-
*/
|
|
46
|
-
authorize: (req: Request) => Promise<Response>;
|
|
47
|
-
/**
|
|
48
|
-
* Custom registration handler that generates the UI for registration.
|
|
49
|
-
*/
|
|
50
|
-
register: (req: Request) => Promise<Response>;
|
|
51
|
-
/**
|
|
52
|
-
* The human-readable name of the relying party (your application).
|
|
53
|
-
*/
|
|
54
|
-
rpName: string;
|
|
55
|
-
/**
|
|
56
|
-
* The ID of the relying party, typically the domain name without protocol.
|
|
57
|
-
*/
|
|
58
|
-
rpID?: string;
|
|
59
|
-
/**
|
|
60
|
-
* The origin URL(s) that are allowed to initiate WebAuthn ceremonies.
|
|
61
|
-
*/
|
|
62
|
-
origin?: string | string[];
|
|
63
|
-
/**
|
|
64
|
-
* Optional function to check if a user is allowed to register a passkey.
|
|
65
|
-
*/
|
|
66
|
-
userCanRegisterPasskey?: (userId: string, req: Request) => Promise<boolean>;
|
|
67
|
-
/**
|
|
68
|
-
* Optional WebAuthn authenticator selection criteria.
|
|
69
|
-
*/
|
|
70
|
-
authenticatorSelection?: AuthenticatorSelectionCriteria;
|
|
71
|
-
/**
|
|
72
|
-
* Optional attestation type.
|
|
73
|
-
*/
|
|
74
|
-
attestationType?: "none" | "direct" | "enterprise";
|
|
75
|
-
/**
|
|
76
|
-
* Optional timeout for challenges in milliseconds.
|
|
77
|
-
*/
|
|
78
|
-
timeout?: number;
|
|
79
|
-
/**
|
|
80
|
-
* Custom copy texts for error messages and UI elements.
|
|
81
|
-
*/
|
|
82
|
-
copy?: Partial<typeof DEFAULT_COPY>;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Creates a passkey (WebAuthn) authentication provider.
|
|
86
|
-
*
|
|
87
|
-
* This provider enables passwordless authentication using biometrics, hardware security
|
|
88
|
-
* keys, or platform authenticators. It implements the Web Authentication (WebAuthn) standard.
|
|
89
|
-
*
|
|
90
|
-
* It handles:
|
|
91
|
-
* - Passkey registration (creating new credentials)
|
|
92
|
-
* - Authentication with existing passkeys
|
|
93
|
-
* - Secure storage of credentials
|
|
94
|
-
* - Challenge verification
|
|
95
|
-
*
|
|
96
|
-
* @param config Configuration options for the passkey provider
|
|
97
|
-
* @returns A Provider instance configured for passkey authentication
|
|
98
|
-
*/
|
|
99
|
-
declare const PasskeyProvider: (config: PasskeyProviderConfig) => Provider<{
|
|
100
|
-
userId: string;
|
|
101
|
-
credentialId?: Base64URLString;
|
|
102
|
-
}>;
|
|
103
|
-
//#endregion
|
|
104
|
-
export { PasskeyModel, PasskeyModelStored, PasskeyProvider, PasskeyProviderConfig, UserModel };
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { Storage } from "../storage/storage.mjs";
|
|
2
|
-
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
|
|
3
|
-
|
|
4
|
-
//#region src/provider/passkey.ts
|
|
5
|
-
/**
|
|
6
|
-
* Converts a Uint8Array to a Base64URL encoded string.
|
|
7
|
-
* This is used to convert binary data for storage in databases or JSON.
|
|
8
|
-
*
|
|
9
|
-
* @param bytes - The Uint8Array to convert
|
|
10
|
-
* @returns Base64URL encoded string
|
|
11
|
-
*/
|
|
12
|
-
const uint8ArrayToBase64Url = (bytes) => {
|
|
13
|
-
let str = "";
|
|
14
|
-
for (const charCode of bytes) str += String.fromCharCode(charCode);
|
|
15
|
-
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Converts a Base64URL encoded string back to a Uint8Array.
|
|
19
|
-
* This is used to convert stored data back to binary format for WebAuthn operations.
|
|
20
|
-
*
|
|
21
|
-
* @param base64urlString - The Base64URL encoded string to convert
|
|
22
|
-
* @returns Uint8Array containing the decoded data
|
|
23
|
-
*/
|
|
24
|
-
const base64UrlToUint8Array = (base64urlString) => {
|
|
25
|
-
const base64 = base64urlString.replace(/-/g, "+").replace(/_/g, "/");
|
|
26
|
-
/**
|
|
27
|
-
* Pad with '=' until it's a multiple of four
|
|
28
|
-
* (4 - (85 % 4 = 1) = 3) % 4 = 3 padding
|
|
29
|
-
* (4 - (86 % 4 = 2) = 2) % 4 = 2 padding
|
|
30
|
-
* (4 - (87 % 4 = 3) = 1) % 4 = 1 padding
|
|
31
|
-
* (4 - (88 % 4 = 0) = 4) % 4 = 0 padding
|
|
32
|
-
*/
|
|
33
|
-
const padLength = (4 - base64.length % 4) % 4;
|
|
34
|
-
const padded = base64.padEnd(base64.length + padLength, "=");
|
|
35
|
-
const binary = atob(padded);
|
|
36
|
-
const buffer = new ArrayBuffer(binary.length);
|
|
37
|
-
const bytes = new Uint8Array(buffer);
|
|
38
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
39
|
-
return bytes;
|
|
40
|
-
};
|
|
41
|
-
const userKey = (userId) => [
|
|
42
|
-
"passkey",
|
|
43
|
-
"user",
|
|
44
|
-
userId
|
|
45
|
-
];
|
|
46
|
-
const passkeyKey = (userId, credentialId) => [
|
|
47
|
-
"passkey",
|
|
48
|
-
"user",
|
|
49
|
-
userId,
|
|
50
|
-
"credential",
|
|
51
|
-
credentialId,
|
|
52
|
-
"passkey"
|
|
53
|
-
];
|
|
54
|
-
const optionsKey = (userId) => [
|
|
55
|
-
"passkey",
|
|
56
|
-
"user",
|
|
57
|
-
userId,
|
|
58
|
-
"options"
|
|
59
|
-
];
|
|
60
|
-
const userPasskeysIndexKey = (userId) => [
|
|
61
|
-
"passkey",
|
|
62
|
-
"user",
|
|
63
|
-
userId,
|
|
64
|
-
"passkeys"
|
|
65
|
-
];
|
|
66
|
-
const DEFAULT_COPY = { error_user_not_allowed: "There is already an account with this email. Login to add a passkey." };
|
|
67
|
-
/**
|
|
68
|
-
* Creates a passkey (WebAuthn) authentication provider.
|
|
69
|
-
*
|
|
70
|
-
* This provider enables passwordless authentication using biometrics, hardware security
|
|
71
|
-
* keys, or platform authenticators. It implements the Web Authentication (WebAuthn) standard.
|
|
72
|
-
*
|
|
73
|
-
* It handles:
|
|
74
|
-
* - Passkey registration (creating new credentials)
|
|
75
|
-
* - Authentication with existing passkeys
|
|
76
|
-
* - Secure storage of credentials
|
|
77
|
-
* - Challenge verification
|
|
78
|
-
*
|
|
79
|
-
* @param config Configuration options for the passkey provider
|
|
80
|
-
* @returns A Provider instance configured for passkey authentication
|
|
81
|
-
*/
|
|
82
|
-
const PasskeyProvider = (config) => {
|
|
83
|
-
const copy = {
|
|
84
|
-
...DEFAULT_COPY,
|
|
85
|
-
...config.copy
|
|
86
|
-
};
|
|
87
|
-
return {
|
|
88
|
-
type: "passkey",
|
|
89
|
-
init(routes, ctx) {
|
|
90
|
-
const { rpName, authenticatorSelection, attestationType = "none", timeout = 300 * 1e3 } = config;
|
|
91
|
-
const getStoredUserById = async (userId) => {
|
|
92
|
-
return await Storage.get(ctx.storage, userKey(userId));
|
|
93
|
-
};
|
|
94
|
-
const saveUser = async (user) => {
|
|
95
|
-
await Storage.set(ctx.storage, userKey(user.id), user);
|
|
96
|
-
};
|
|
97
|
-
const getStoredPasskeyById = async (userId, credentialID) => {
|
|
98
|
-
const storedPasskey = await Storage.get(ctx.storage, passkeyKey(userId, credentialID));
|
|
99
|
-
if (!storedPasskey) return null;
|
|
100
|
-
return {
|
|
101
|
-
...storedPasskey,
|
|
102
|
-
publicKey: base64UrlToUint8Array(storedPasskey.publicKey)
|
|
103
|
-
};
|
|
104
|
-
};
|
|
105
|
-
const getStoredUserPasskeys = async (userId) => {
|
|
106
|
-
const passkeyIds = await Storage.get(ctx.storage, userPasskeysIndexKey(userId)) || [];
|
|
107
|
-
const passkeys = [];
|
|
108
|
-
for (const id of passkeyIds) {
|
|
109
|
-
const pk = await getStoredPasskeyById(userId, id);
|
|
110
|
-
if (pk) passkeys.push(pk);
|
|
111
|
-
}
|
|
112
|
-
return passkeys;
|
|
113
|
-
};
|
|
114
|
-
const saveNewStoredPasskey = async (passkeyData) => {
|
|
115
|
-
const storablePasskey = {
|
|
116
|
-
...passkeyData,
|
|
117
|
-
publicKey: uint8ArrayToBase64Url(passkeyData.publicKey)
|
|
118
|
-
};
|
|
119
|
-
await Storage.set(ctx.storage, passkeyKey(passkeyData.userId, passkeyData.id), storablePasskey);
|
|
120
|
-
const passkeyIds = await Storage.get(ctx.storage, userPasskeysIndexKey(passkeyData.userId)) || [];
|
|
121
|
-
if (!passkeyIds.includes(passkeyData.id)) {
|
|
122
|
-
passkeyIds.push(passkeyData.id);
|
|
123
|
-
await Storage.set(ctx.storage, userPasskeysIndexKey(passkeyData.userId), passkeyIds);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const updateStoredPasskeyCounter = async (userId, credentialID, newCounter) => {
|
|
127
|
-
const passkey = await getStoredPasskeyById(userId, credentialID);
|
|
128
|
-
if (passkey) {
|
|
129
|
-
passkey.counter = newCounter;
|
|
130
|
-
const storablePasskey = {
|
|
131
|
-
...passkey,
|
|
132
|
-
publicKey: uint8ArrayToBase64Url(passkey.publicKey)
|
|
133
|
-
};
|
|
134
|
-
await Storage.set(ctx.storage, passkeyKey(userId, credentialID), storablePasskey);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
routes.get("/authorize", async (c) => {
|
|
138
|
-
return ctx.forward(c, await config.authorize(c.request));
|
|
139
|
-
});
|
|
140
|
-
routes.get("/register", async (c) => {
|
|
141
|
-
return ctx.forward(c, await config.register(c.request));
|
|
142
|
-
});
|
|
143
|
-
routes.get("/register-request", async (c) => {
|
|
144
|
-
const userId = c.query("userId");
|
|
145
|
-
const rpID = config.rpID || c.query("rpID");
|
|
146
|
-
const otherDevice = c.query("otherDevice") === "true";
|
|
147
|
-
if (!userId) return c.json({ error: "User ID for registration is required." }, { status: 400 });
|
|
148
|
-
if (!rpID) return c.json({ error: "RP ID for registration is required." }, { status: 400 });
|
|
149
|
-
const username = c.query("username") || userId;
|
|
150
|
-
let user = await getStoredUserById(userId);
|
|
151
|
-
if (config.userCanRegisterPasskey) {
|
|
152
|
-
if (!await config.userCanRegisterPasskey(userId, c.request)) return c.json({ error: copy.error_user_not_allowed }, { status: 403 });
|
|
153
|
-
}
|
|
154
|
-
if (!user) {
|
|
155
|
-
user = {
|
|
156
|
-
id: userId,
|
|
157
|
-
username
|
|
158
|
-
};
|
|
159
|
-
await saveUser(user);
|
|
160
|
-
}
|
|
161
|
-
const userPasskeys = await getStoredUserPasskeys(user.id);
|
|
162
|
-
const regOptions = await generateRegistrationOptions({
|
|
163
|
-
rpName,
|
|
164
|
-
rpID,
|
|
165
|
-
userName: user.username,
|
|
166
|
-
attestationType,
|
|
167
|
-
excludeCredentials: userPasskeys.map((pk) => ({
|
|
168
|
-
id: pk.id,
|
|
169
|
-
transports: pk.transports
|
|
170
|
-
})),
|
|
171
|
-
authenticatorSelection: authenticatorSelection ?? {
|
|
172
|
-
residentKey: "preferred",
|
|
173
|
-
userVerification: "preferred",
|
|
174
|
-
authenticatorAttachment: otherDevice ? void 0 : "platform"
|
|
175
|
-
},
|
|
176
|
-
timeout
|
|
177
|
-
});
|
|
178
|
-
await Storage.set(ctx.storage, optionsKey(user.id), regOptions);
|
|
179
|
-
return c.json(regOptions);
|
|
180
|
-
});
|
|
181
|
-
routes.post("/register-verify", async (c) => {
|
|
182
|
-
const body = await c.parseJson();
|
|
183
|
-
const userId = c.query("userId");
|
|
184
|
-
const rpID = config.rpID || c.query("rpID");
|
|
185
|
-
const origin = config.origin || c.query("origin");
|
|
186
|
-
if (!userId) return c.json({
|
|
187
|
-
verified: false,
|
|
188
|
-
error: "User ID for verification is required."
|
|
189
|
-
}, { status: 400 });
|
|
190
|
-
if (!rpID) return c.json({ error: "RP ID for verification is required." }, { status: 400 });
|
|
191
|
-
if (!origin) return c.json({ error: "Origin for verification is required." }, { status: 400 });
|
|
192
|
-
const user = await getStoredUserById(userId);
|
|
193
|
-
if (!user) return c.json({
|
|
194
|
-
verified: false,
|
|
195
|
-
error: "User not found during verification."
|
|
196
|
-
}, { status: 404 });
|
|
197
|
-
const regOptions = await Storage.get(ctx.storage, optionsKey(user.id));
|
|
198
|
-
if (!regOptions) return c.json({
|
|
199
|
-
verified: false,
|
|
200
|
-
error: "Registration options not found."
|
|
201
|
-
}, { status: 400 });
|
|
202
|
-
const challenge = regOptions.challenge;
|
|
203
|
-
let verification;
|
|
204
|
-
try {
|
|
205
|
-
verification = await verifyRegistrationResponse({
|
|
206
|
-
response: body,
|
|
207
|
-
expectedChallenge: challenge,
|
|
208
|
-
expectedOrigin: origin,
|
|
209
|
-
expectedRPID: rpID,
|
|
210
|
-
requireUserVerification: authenticatorSelection?.userVerification !== "discouraged"
|
|
211
|
-
});
|
|
212
|
-
} catch (error) {
|
|
213
|
-
console.error("Passkey Registration Verification Error:", error);
|
|
214
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
215
|
-
return c.json({
|
|
216
|
-
verified: false,
|
|
217
|
-
error: message
|
|
218
|
-
}, { status: 400 });
|
|
219
|
-
}
|
|
220
|
-
const { verified, registrationInfo } = verification;
|
|
221
|
-
if (verified && registrationInfo) {
|
|
222
|
-
const { credential, credentialDeviceType, credentialBackedUp } = registrationInfo;
|
|
223
|
-
if (credential) {
|
|
224
|
-
const newPasskey = {
|
|
225
|
-
id: credential.id,
|
|
226
|
-
userId: user.id,
|
|
227
|
-
webauthnUserID: regOptions.user.id,
|
|
228
|
-
publicKey: credential.publicKey,
|
|
229
|
-
counter: credential.counter,
|
|
230
|
-
transports: credential.transports,
|
|
231
|
-
deviceType: credentialDeviceType,
|
|
232
|
-
backedUp: credentialBackedUp
|
|
233
|
-
};
|
|
234
|
-
await saveNewStoredPasskey(newPasskey);
|
|
235
|
-
return ctx.success(c, {
|
|
236
|
-
userId: user.id,
|
|
237
|
-
credentialId: newPasskey.id,
|
|
238
|
-
verified: true
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return c.json({
|
|
243
|
-
verified: false,
|
|
244
|
-
error: "Registration verification failed."
|
|
245
|
-
}, { status: 400 });
|
|
246
|
-
});
|
|
247
|
-
routes.get("/authenticate-options", async (c) => {
|
|
248
|
-
const userId = c.query("userId");
|
|
249
|
-
if (!userId) return c.json({ error: "User ID for authentication is required." }, { status: 400 });
|
|
250
|
-
const rpID = config.rpID || c.query("rpID");
|
|
251
|
-
if (!rpID) return c.json({ error: "RP ID for authentication is required." }, { status: 400 });
|
|
252
|
-
const userForAuth = await getStoredUserById(userId);
|
|
253
|
-
if (!userForAuth) return c.json({ error: "User not found for authentication." }, { status: 404 });
|
|
254
|
-
const authOptions = await generateAuthenticationOptions({
|
|
255
|
-
rpID,
|
|
256
|
-
allowCredentials: (await getStoredUserPasskeys(userForAuth.id)).map((pk) => ({
|
|
257
|
-
id: pk.id,
|
|
258
|
-
transports: pk.transports
|
|
259
|
-
})),
|
|
260
|
-
userVerification: authenticatorSelection?.userVerification ?? "preferred",
|
|
261
|
-
timeout
|
|
262
|
-
});
|
|
263
|
-
await Storage.set(ctx.storage, optionsKey(userForAuth.id), authOptions);
|
|
264
|
-
return c.json(authOptions);
|
|
265
|
-
});
|
|
266
|
-
routes.post("/authenticate-verify", async (c) => {
|
|
267
|
-
const body = await c.parseJson();
|
|
268
|
-
const userId = c.query("userId");
|
|
269
|
-
if (!userId) return c.json({ error: "User ID for authentication is required." }, { status: 400 });
|
|
270
|
-
const rpID = config.rpID || c.query("rpID");
|
|
271
|
-
if (!rpID) return c.json({ error: "RP ID for authentication is required." }, { status: 400 });
|
|
272
|
-
const origin = config.origin || c.query("origin");
|
|
273
|
-
if (!origin) return c.json({ error: "Origin for authentication is required." }, { status: 400 });
|
|
274
|
-
const user = await getStoredUserById(userId);
|
|
275
|
-
if (!user) return c.json({
|
|
276
|
-
verified: false,
|
|
277
|
-
error: `User ${userId} not found.`
|
|
278
|
-
}, { status: 404 });
|
|
279
|
-
const authOptions = await Storage.get(ctx.storage, optionsKey(user.id));
|
|
280
|
-
if (!authOptions) return c.json({ error: "Authentication options not found." }, { status: 400 });
|
|
281
|
-
const passkey = await getStoredPasskeyById(userId, body.id);
|
|
282
|
-
if (!passkey) return c.json({
|
|
283
|
-
verified: false,
|
|
284
|
-
error: `Passkey ${body.id} not found for user ${user.username}.`
|
|
285
|
-
}, { status: 400 });
|
|
286
|
-
const { publicKey, counter, transports } = passkey;
|
|
287
|
-
if (!publicKey || typeof counter !== "number" || !transports) return c.json({ error: "Passkey not found for authentication." }, { status: 400 });
|
|
288
|
-
const challenge = authOptions.challenge;
|
|
289
|
-
if (!challenge) return c.json({ error: "Authentication challenge not found." }, { status: 400 });
|
|
290
|
-
const { verified, authenticationInfo } = await verifyAuthenticationResponse({
|
|
291
|
-
response: body,
|
|
292
|
-
expectedChallenge: challenge,
|
|
293
|
-
expectedOrigin: origin || "",
|
|
294
|
-
expectedRPID: rpID,
|
|
295
|
-
credential: {
|
|
296
|
-
id: passkey.id,
|
|
297
|
-
publicKey,
|
|
298
|
-
counter,
|
|
299
|
-
transports
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
if (verified) {
|
|
303
|
-
await updateStoredPasskeyCounter(user.id, passkey.id, authenticationInfo.newCounter);
|
|
304
|
-
return ctx.success(c, {
|
|
305
|
-
userId: user.id,
|
|
306
|
-
credentialId: passkey.id,
|
|
307
|
-
verified: true
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
return c.json({
|
|
311
|
-
verified: false,
|
|
312
|
-
error: "Authentication verification failed."
|
|
313
|
-
}, { status: 400 });
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
//#endregion
|
|
320
|
-
export { PasskeyProvider };
|