@robelest/convex-auth 0.0.2 → 0.0.3-preview.1
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/bin.cjs +1 -1
- package/dist/client/index.d.ts +33 -9
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +79 -13
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +48 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +10 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +8 -3
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +163 -3
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +124 -0
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +81 -2
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +45 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/providers/anonymous.d.ts +3 -0
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +3 -0
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/credentials.d.ts +3 -0
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +3 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/email.d.ts +3 -0
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js +3 -0
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/passkey.d.ts +7 -1
- package/dist/providers/passkey.d.ts.map +1 -1
- package/dist/providers/passkey.js +7 -1
- package/dist/providers/passkey.js.map +1 -1
- package/dist/providers/password.d.ts +3 -0
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +3 -0
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +3 -0
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js +3 -0
- package/dist/providers/phone.js.map +1 -1
- package/dist/providers/totp.d.ts +8 -0
- package/dist/providers/totp.d.ts.map +1 -1
- package/dist/providers/totp.js +8 -0
- package/dist/providers/totp.js.map +1 -1
- package/dist/server/convex-auth.d.ts +185 -25
- package/dist/server/convex-auth.d.ts.map +1 -1
- package/dist/server/convex-auth.js +317 -58
- package/dist/server/convex-auth.js.map +1 -1
- package/dist/server/email-templates.d.ts +18 -0
- package/dist/server/email-templates.d.ts.map +1 -0
- package/dist/server/email-templates.js +74 -0
- package/dist/server/email-templates.js.map +1 -0
- package/dist/server/errors.d.ts +146 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +176 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/implementation/apiKey.d.ts +74 -0
- package/dist/server/implementation/apiKey.d.ts.map +1 -0
- package/dist/server/implementation/apiKey.js +139 -0
- package/dist/server/implementation/apiKey.js.map +1 -0
- package/dist/server/implementation/index.d.ts +151 -14
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +216 -24
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js +2 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts +2 -2
- package/dist/server/implementation/mutations/index.d.ts +6 -6
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.js +2 -1
- package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.js +2 -1
- package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.js +2 -1
- package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
- package/dist/server/implementation/passkey.d.ts.map +1 -1
- package/dist/server/implementation/passkey.js +28 -29
- package/dist/server/implementation/passkey.js.map +1 -1
- package/dist/server/implementation/provider.d.ts.map +1 -1
- package/dist/server/implementation/provider.js +5 -4
- package/dist/server/implementation/provider.js.map +1 -1
- package/dist/server/implementation/redirects.d.ts.map +1 -1
- package/dist/server/implementation/redirects.js +2 -1
- package/dist/server/implementation/redirects.js.map +1 -1
- package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
- package/dist/server/implementation/refreshTokens.js +2 -1
- package/dist/server/implementation/refreshTokens.js.map +1 -1
- package/dist/server/implementation/signIn.d.ts.map +1 -1
- package/dist/server/implementation/signIn.js +8 -18
- package/dist/server/implementation/signIn.js.map +1 -1
- package/dist/server/implementation/totp.d.ts.map +1 -1
- package/dist/server/implementation/totp.js +16 -17
- package/dist/server/implementation/totp.js.map +1 -1
- package/dist/server/implementation/users.d.ts.map +1 -1
- package/dist/server/implementation/users.js +3 -2
- package/dist/server/implementation/users.js.map +1 -1
- package/dist/server/index.d.ts +157 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +180 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth/authorizationUrl.d.ts.map +1 -1
- package/dist/server/oauth/authorizationUrl.js +2 -1
- package/dist/server/oauth/authorizationUrl.js.map +1 -1
- package/dist/server/oauth/callback.d.ts.map +1 -1
- package/dist/server/oauth/callback.js +5 -4
- package/dist/server/oauth/callback.js.map +1 -1
- package/dist/server/oauth/checks.d.ts.map +1 -1
- package/dist/server/oauth/checks.js +2 -1
- package/dist/server/oauth/checks.js.map +1 -1
- package/dist/server/oauth/convexAuth.d.ts.map +1 -1
- package/dist/server/oauth/convexAuth.js +3 -2
- package/dist/server/oauth/convexAuth.js.map +1 -1
- package/dist/server/provider_utils.d.ts +2 -0
- package/dist/server/provider_utils.d.ts.map +1 -1
- package/dist/server/types.d.ts +240 -5
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils.d.ts.map +1 -1
- package/dist/server/utils.js +2 -1
- package/dist/server/utils.js.map +1 -1
- package/dist/server/version.d.ts +2 -0
- package/dist/server/version.d.ts.map +1 -0
- package/dist/server/version.js +3 -0
- package/dist/server/version.js.map +1 -0
- package/package.json +7 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/utils.ts +248 -0
- package/src/client/index.ts +105 -15
- package/src/component/_generated/component.ts +61 -0
- package/src/component/index.ts +11 -2
- package/src/component/public.ts +142 -0
- package/src/component/schema.ts +52 -0
- package/src/providers/anonymous.ts +3 -0
- package/src/providers/credentials.ts +3 -0
- package/src/providers/email.ts +3 -0
- package/src/providers/passkey.ts +8 -1
- package/src/providers/password.ts +3 -0
- package/src/providers/phone.ts +3 -0
- package/src/providers/totp.ts +9 -0
- package/src/server/convex-auth.ts +385 -73
- package/src/server/email-templates.ts +77 -0
- package/src/server/errors.ts +269 -0
- package/src/server/implementation/apiKey.ts +186 -0
- package/src/server/implementation/index.ts +288 -28
- package/src/server/implementation/mutations/createAccountFromCredentials.ts +2 -1
- package/src/server/implementation/mutations/modifyAccount.ts +2 -3
- package/src/server/implementation/mutations/userOAuth.ts +2 -1
- package/src/server/implementation/mutations/verifierSignature.ts +2 -1
- package/src/server/implementation/passkey.ts +33 -35
- package/src/server/implementation/provider.ts +5 -8
- package/src/server/implementation/redirects.ts +2 -3
- package/src/server/implementation/refreshTokens.ts +2 -1
- package/src/server/implementation/signIn.ts +9 -18
- package/src/server/implementation/totp.ts +18 -21
- package/src/server/implementation/users.ts +4 -7
- package/src/server/index.ts +240 -37
- package/src/server/oauth/authorizationUrl.ts +2 -1
- package/src/server/oauth/callback.ts +5 -4
- package/src/server/oauth/checks.ts +3 -1
- package/src/server/oauth/convexAuth.ts +6 -3
- package/src/server/types.ts +254 -5
- package/src/server/utils.ts +3 -1
- package/src/server/version.ts +2 -0
- package/dist/server/portal.d.ts +0 -116
- package/dist/server/portal.d.ts.map +0 -1
- package/dist/server/portal.js +0 -294
- package/dist/server/portal.js.map +0 -1
- package/src/server/portal.ts +0 -375
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default email templates generated by the Auth library.
|
|
3
|
+
*
|
|
4
|
+
* These are used when the library sends emails on behalf of the developer
|
|
5
|
+
* (magic links, portal admin sign-in). The developer provides the transport
|
|
6
|
+
* via `email.send`; the library provides the content.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Default magic link email template.
|
|
12
|
+
*
|
|
13
|
+
* Clean, minimal design that works across email clients.
|
|
14
|
+
* Used by the auto-registered `email` provider when `email` is
|
|
15
|
+
* configured in the Auth constructor.
|
|
16
|
+
*/
|
|
17
|
+
export function defaultMagicLinkEmail(url, host) {
|
|
18
|
+
const escapedHost = host.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
19
|
+
return `<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8" />
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
24
|
+
<title>Sign in to ${escapedHost}</title>
|
|
25
|
+
</head>
|
|
26
|
+
<body style="margin:0;padding:0;background-color:#f9fafb;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;">
|
|
27
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f9fafb;padding:40px 16px;">
|
|
28
|
+
<tr>
|
|
29
|
+
<td align="center">
|
|
30
|
+
<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;">
|
|
31
|
+
<tr>
|
|
32
|
+
<td style="padding:32px 32px 0 32px;text-align:center;">
|
|
33
|
+
<h1 style="margin:0 0 8px 0;font-size:20px;font-weight:600;color:#111827;line-height:1.3;">
|
|
34
|
+
Sign in to ${escapedHost}
|
|
35
|
+
</h1>
|
|
36
|
+
</td>
|
|
37
|
+
</tr>
|
|
38
|
+
<tr>
|
|
39
|
+
<td style="padding:24px 32px;">
|
|
40
|
+
<p style="margin:0 0 24px 0;font-size:15px;line-height:1.6;color:#4b5563;text-align:center;">
|
|
41
|
+
Click the button below to sign in. This link will expire shortly.
|
|
42
|
+
</p>
|
|
43
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
|
44
|
+
<tr>
|
|
45
|
+
<td align="center" style="padding:0 0 24px 0;">
|
|
46
|
+
<a href="${url}" target="_blank" style="display:inline-block;background-color:#111827;color:#ffffff;font-size:15px;font-weight:600;text-decoration:none;padding:12px 32px;border-radius:6px;line-height:1;">
|
|
47
|
+
Sign in
|
|
48
|
+
</a>
|
|
49
|
+
</td>
|
|
50
|
+
</tr>
|
|
51
|
+
</table>
|
|
52
|
+
<p style="margin:0 0 12px 0;font-size:13px;line-height:1.6;color:#9ca3af;">
|
|
53
|
+
If the button doesn't work, copy and paste this URL into your browser:
|
|
54
|
+
</p>
|
|
55
|
+
<p style="margin:0;font-size:13px;line-height:1.5;color:#6b7280;word-break:break-all;">
|
|
56
|
+
${url}
|
|
57
|
+
</p>
|
|
58
|
+
</td>
|
|
59
|
+
</tr>
|
|
60
|
+
<tr>
|
|
61
|
+
<td style="padding:20px 32px;border-top:1px solid #e5e7eb;">
|
|
62
|
+
<p style="margin:0;font-size:12px;line-height:1.5;color:#9ca3af;text-align:center;">
|
|
63
|
+
If you didn't request this email, you can safely ignore it.
|
|
64
|
+
</p>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
</table>
|
|
68
|
+
</td>
|
|
69
|
+
</tr>
|
|
70
|
+
</table>
|
|
71
|
+
</body>
|
|
72
|
+
</html>`;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=email-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-templates.js","sourceRoot":"","sources":["../../src/server/email-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,IAAY;IAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CACjD,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAE,CAC9E,CAAC;IAEF,OAAO;;;;;sBAKa,WAAW;;;;;;;;;;6BAUJ,WAAW;;;;;;;;;;;;+BAYT,GAAG;;;;;;;;;;kBAUhB,GAAG;;;;;;;;;;;;;;;;QAgBb,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error handling for Convex Auth.
|
|
3
|
+
*
|
|
4
|
+
* Every error thrown by the auth system uses `ConvexError` with a
|
|
5
|
+
* `{ code, message }` payload so clients can distinguish error types
|
|
6
|
+
* and display user-friendly messages.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { ConvexError } from "convex/values";
|
|
11
|
+
/**
|
|
12
|
+
* Map of every auth error code to its default human-readable message.
|
|
13
|
+
*
|
|
14
|
+
* Use the keys as the `code` argument to {@link throwAuthError}.
|
|
15
|
+
* Clients can match on these codes for conditional error handling.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* throwAuthError("NOT_SIGNED_IN");
|
|
20
|
+
* // ConvexError { data: { code: "NOT_SIGNED_IN", message: "You must be signed in..." } }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const AUTH_ERRORS: {
|
|
24
|
+
readonly PROVIDER_NOT_CONFIGURED: "This sign-in method is not available.";
|
|
25
|
+
readonly EMAIL_CONFIG_REQUIRED: "Email transport is not configured. Configure email in your Auth constructor.";
|
|
26
|
+
readonly MISSING_ENV_VAR: "A required server environment variable is missing.";
|
|
27
|
+
readonly MISSING_ACTION_CONTEXT: "Action context is required for this operation.";
|
|
28
|
+
readonly NOT_SIGNED_IN: "You must be signed in to perform this action.";
|
|
29
|
+
readonly INVALID_VERIFICATION_CODE: "Invalid or expired verification code.";
|
|
30
|
+
readonly INVALID_REFRESH_TOKEN: "Your session has expired. Please sign in again.";
|
|
31
|
+
readonly SIGN_IN_MISSING_PARAMS: "Cannot sign in: missing provider, code, or refresh token.";
|
|
32
|
+
readonly UNSUPPORTED_PROVIDER_TYPE: "This provider type is not supported.";
|
|
33
|
+
readonly INVALID_REDIRECT: "Invalid redirect URL.";
|
|
34
|
+
readonly EMAIL_SEND_FAILED: "Failed to send verification email. Please try again.";
|
|
35
|
+
readonly PORTAL_NOT_AUTHORIZED: "This email does not have portal admin access. Ask an admin for an invite link.";
|
|
36
|
+
readonly PORTAL_UNKNOWN_ACTION: "Unknown portal action.";
|
|
37
|
+
readonly INVITE_TOKEN_REQUIRED: "Invite token is required.";
|
|
38
|
+
readonly INVALID_INVITE: "Invalid or expired invite token.";
|
|
39
|
+
readonly INVITE_ALREADY_USED: "This invite has already been used.";
|
|
40
|
+
readonly INVITE_EXPIRED: "This invite has expired.";
|
|
41
|
+
readonly INVALID_API_KEY: "Invalid API key.";
|
|
42
|
+
readonly API_KEY_REVOKED: "This API key has been revoked.";
|
|
43
|
+
readonly API_KEY_EXPIRED: "This API key has expired.";
|
|
44
|
+
readonly API_KEY_RATE_LIMITED: "API key rate limit exceeded. Please try again later.";
|
|
45
|
+
readonly API_KEY_INVALID_SCOPE: "Invalid scope requested for API key.";
|
|
46
|
+
readonly OAUTH_MISSING_PROVIDER: "Missing OAuth provider ID.";
|
|
47
|
+
readonly OAUTH_MISSING_VERIFIER: "Missing sign-in verifier.";
|
|
48
|
+
readonly OAUTH_INVALID_STATE: "Invalid OAuth state. Please try signing in again.";
|
|
49
|
+
readonly OAUTH_PROVIDER_ERROR: "The sign-in provider returned an error.";
|
|
50
|
+
readonly OAUTH_MISSING_ID_TOKEN: "ID token claims are missing from the provider response.";
|
|
51
|
+
readonly OAUTH_INVALID_PROFILE: "The sign-in provider returned an invalid profile.";
|
|
52
|
+
readonly OAUTH_UNSUPPORTED_AUTH_METHOD: "Unsupported OAuth client authentication method.";
|
|
53
|
+
readonly OAUTH_NO_USERINFO: "No userinfo endpoint configured for this provider.";
|
|
54
|
+
readonly ACCOUNT_ALREADY_EXISTS: "An account with these credentials already exists.";
|
|
55
|
+
readonly ACCOUNT_NOT_FOUND: "Account not found.";
|
|
56
|
+
readonly INVALID_CREDENTIALS_PROVIDER: "This provider does not support credential operations.";
|
|
57
|
+
readonly MISSING_CRYPTO_FUNCTION: "This provider is missing a required cryptographic function.";
|
|
58
|
+
readonly USER_UPDATE_FAILED: "Could not update the user record.";
|
|
59
|
+
readonly INVALID_VERIFIER: "Invalid or expired verifier.";
|
|
60
|
+
readonly PASSKEY_MISSING_CONFIG: "Passkey provider requires SITE_URL or explicit rpId configuration.";
|
|
61
|
+
readonly PASSKEY_AUTH_REQUIRED: "Sign in first, then add a passkey to your account.";
|
|
62
|
+
readonly PASSKEY_MISSING_VERIFIER: "Missing verifier for passkey operation.";
|
|
63
|
+
readonly PASSKEY_INVALID_CLIENT_DATA: "Invalid passkey client data.";
|
|
64
|
+
readonly PASSKEY_INVALID_ORIGIN: "Passkey origin does not match the expected value.";
|
|
65
|
+
readonly PASSKEY_INVALID_CHALLENGE: "Invalid or expired passkey challenge.";
|
|
66
|
+
readonly PASSKEY_RP_MISMATCH: "Relying party ID mismatch.";
|
|
67
|
+
readonly PASSKEY_USER_PRESENCE: "User presence flag not set.";
|
|
68
|
+
readonly PASSKEY_USER_VERIFICATION: "User verification required but not performed.";
|
|
69
|
+
readonly PASSKEY_NO_CREDENTIAL: "No credential in attestation.";
|
|
70
|
+
readonly PASSKEY_UNSUPPORTED_ALGORITHM: "Unsupported passkey algorithm.";
|
|
71
|
+
readonly PASSKEY_INVALID_SIGNATURE: "Invalid passkey signature.";
|
|
72
|
+
readonly PASSKEY_UNKNOWN_CREDENTIAL: "Unknown passkey credential.";
|
|
73
|
+
readonly PASSKEY_COUNTER_ERROR: "Authenticator counter did not increase — possible credential cloning detected.";
|
|
74
|
+
readonly PASSKEY_MISSING_FLOW: "Missing passkey flow parameter.";
|
|
75
|
+
readonly PASSKEY_UNKNOWN_FLOW: "Unknown passkey flow.";
|
|
76
|
+
readonly TOTP_AUTH_REQUIRED: "Sign in first, then set up two-factor authentication.";
|
|
77
|
+
readonly TOTP_MISSING_VERIFIER: "Missing verifier for TOTP operation.";
|
|
78
|
+
readonly TOTP_MISSING_CODE: "Missing TOTP code.";
|
|
79
|
+
readonly TOTP_MISSING_ID: "Missing TOTP enrollment ID.";
|
|
80
|
+
readonly TOTP_NOT_FOUND: "TOTP enrollment not found.";
|
|
81
|
+
readonly TOTP_ALREADY_VERIFIED: "TOTP enrollment is already verified.";
|
|
82
|
+
readonly TOTP_INVALID_CODE: "Invalid TOTP code.";
|
|
83
|
+
readonly TOTP_INVALID_VERIFIER: "Invalid or expired TOTP verifier.";
|
|
84
|
+
readonly TOTP_NO_ENROLLMENT: "No verified TOTP enrollment found.";
|
|
85
|
+
readonly TOTP_MISSING_FLOW: "Missing TOTP flow parameter.";
|
|
86
|
+
readonly TOTP_UNKNOWN_FLOW: "Unknown TOTP flow.";
|
|
87
|
+
readonly INTERNAL_ERROR: "An unexpected error occurred.";
|
|
88
|
+
};
|
|
89
|
+
/** Union of all recognized auth error code strings (keys of {@link AUTH_ERRORS}). */
|
|
90
|
+
export type AuthErrorCode = keyof typeof AUTH_ERRORS;
|
|
91
|
+
/**
|
|
92
|
+
* Throw a structured `ConvexError` with `{ code, message }`.
|
|
93
|
+
*
|
|
94
|
+
* @param code Machine-readable error code from `AUTH_ERRORS`.
|
|
95
|
+
* @param message Optional override for the default human-readable message.
|
|
96
|
+
* @param context Optional extra fields merged into the error payload.
|
|
97
|
+
*/
|
|
98
|
+
export declare function throwAuthError(code: AuthErrorCode, message?: string, context?: Record<string, unknown>): never;
|
|
99
|
+
/**
|
|
100
|
+
* Type guard: check whether a caught value is a structured auth `ConvexError`.
|
|
101
|
+
*
|
|
102
|
+
* @param error - The caught value (typically from a `catch` block).
|
|
103
|
+
* @returns `true` when `error` is a `ConvexError` with `{ code, message }` data.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* try { await auth.signIn('email', { email }); }
|
|
108
|
+
* catch (e) {
|
|
109
|
+
* if (isAuthError(e)) console.log(e.data.code); // "EMAIL_SEND_FAILED"
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function isAuthError(error: unknown): error is ConvexError<{
|
|
114
|
+
code: AuthErrorCode;
|
|
115
|
+
message: string;
|
|
116
|
+
}>;
|
|
117
|
+
/**
|
|
118
|
+
* Extract `{ code, message }` from a caught error.
|
|
119
|
+
*
|
|
120
|
+
* Works for `ConvexError` (from Convex actions), plain `Error`
|
|
121
|
+
* instances, and structured auth errors. Returns `null` when the
|
|
122
|
+
* value is not an error object.
|
|
123
|
+
*
|
|
124
|
+
* @param error - The caught value to parse.
|
|
125
|
+
* @returns `{ code, message }` when extractable, or `null`.
|
|
126
|
+
* When `code` is `null`, the error is not a structured auth error
|
|
127
|
+
* but `message` still contains the error text.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* try {
|
|
132
|
+
* await auth.signIn("email", { email });
|
|
133
|
+
* } catch (e) {
|
|
134
|
+
* const err = parseAuthError(e);
|
|
135
|
+
* if (err?.code === "EMAIL_SEND_FAILED") { ... }
|
|
136
|
+
* }
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function parseAuthError(error: unknown): {
|
|
140
|
+
code: AuthErrorCode;
|
|
141
|
+
message: string;
|
|
142
|
+
} | {
|
|
143
|
+
code: null;
|
|
144
|
+
message: string;
|
|
145
|
+
} | null;
|
|
146
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsJmB,CAAC;AAE5C,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,WAAW,CAAC;AAMrD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,KAAK,CAMP;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,WAAW,CAAC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAQhE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,GACb;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYnF"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error handling for Convex Auth.
|
|
3
|
+
*
|
|
4
|
+
* Every error thrown by the auth system uses `ConvexError` with a
|
|
5
|
+
* `{ code, message }` payload so clients can distinguish error types
|
|
6
|
+
* and display user-friendly messages.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { ConvexError } from "convex/values";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Error code → default message map (single source of truth)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Map of every auth error code to its default human-readable message.
|
|
16
|
+
*
|
|
17
|
+
* Use the keys as the `code` argument to {@link throwAuthError}.
|
|
18
|
+
* Clients can match on these codes for conditional error handling.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* throwAuthError("NOT_SIGNED_IN");
|
|
23
|
+
* // ConvexError { data: { code: "NOT_SIGNED_IN", message: "You must be signed in..." } }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const AUTH_ERRORS = {
|
|
27
|
+
// ---- Configuration ----
|
|
28
|
+
PROVIDER_NOT_CONFIGURED: "This sign-in method is not available.",
|
|
29
|
+
EMAIL_CONFIG_REQUIRED: "Email transport is not configured. Configure email in your Auth constructor.",
|
|
30
|
+
MISSING_ENV_VAR: "A required server environment variable is missing.",
|
|
31
|
+
MISSING_ACTION_CONTEXT: "Action context is required for this operation.",
|
|
32
|
+
// ---- Authentication ----
|
|
33
|
+
NOT_SIGNED_IN: "You must be signed in to perform this action.",
|
|
34
|
+
INVALID_VERIFICATION_CODE: "Invalid or expired verification code.",
|
|
35
|
+
INVALID_REFRESH_TOKEN: "Your session has expired. Please sign in again.",
|
|
36
|
+
SIGN_IN_MISSING_PARAMS: "Cannot sign in: missing provider, code, or refresh token.",
|
|
37
|
+
UNSUPPORTED_PROVIDER_TYPE: "This provider type is not supported.",
|
|
38
|
+
INVALID_REDIRECT: "Invalid redirect URL.",
|
|
39
|
+
// ---- Email / Phone ----
|
|
40
|
+
EMAIL_SEND_FAILED: "Failed to send verification email. Please try again.",
|
|
41
|
+
// ---- Portal ----
|
|
42
|
+
PORTAL_NOT_AUTHORIZED: "This email does not have portal admin access. Ask an admin for an invite link.",
|
|
43
|
+
PORTAL_UNKNOWN_ACTION: "Unknown portal action.",
|
|
44
|
+
INVITE_TOKEN_REQUIRED: "Invite token is required.",
|
|
45
|
+
INVALID_INVITE: "Invalid or expired invite token.",
|
|
46
|
+
INVITE_ALREADY_USED: "This invite has already been used.",
|
|
47
|
+
INVITE_EXPIRED: "This invite has expired.",
|
|
48
|
+
// ---- API Keys ----
|
|
49
|
+
INVALID_API_KEY: "Invalid API key.",
|
|
50
|
+
API_KEY_REVOKED: "This API key has been revoked.",
|
|
51
|
+
API_KEY_EXPIRED: "This API key has expired.",
|
|
52
|
+
API_KEY_RATE_LIMITED: "API key rate limit exceeded. Please try again later.",
|
|
53
|
+
API_KEY_INVALID_SCOPE: "Invalid scope requested for API key.",
|
|
54
|
+
// ---- OAuth ----
|
|
55
|
+
OAUTH_MISSING_PROVIDER: "Missing OAuth provider ID.",
|
|
56
|
+
OAUTH_MISSING_VERIFIER: "Missing sign-in verifier.",
|
|
57
|
+
OAUTH_INVALID_STATE: "Invalid OAuth state. Please try signing in again.",
|
|
58
|
+
OAUTH_PROVIDER_ERROR: "The sign-in provider returned an error.",
|
|
59
|
+
OAUTH_MISSING_ID_TOKEN: "ID token claims are missing from the provider response.",
|
|
60
|
+
OAUTH_INVALID_PROFILE: "The sign-in provider returned an invalid profile.",
|
|
61
|
+
OAUTH_UNSUPPORTED_AUTH_METHOD: "Unsupported OAuth client authentication method.",
|
|
62
|
+
OAUTH_NO_USERINFO: "No userinfo endpoint configured for this provider.",
|
|
63
|
+
// ---- Credentials ----
|
|
64
|
+
ACCOUNT_ALREADY_EXISTS: "An account with these credentials already exists.",
|
|
65
|
+
ACCOUNT_NOT_FOUND: "Account not found.",
|
|
66
|
+
INVALID_CREDENTIALS_PROVIDER: "This provider does not support credential operations.",
|
|
67
|
+
MISSING_CRYPTO_FUNCTION: "This provider is missing a required cryptographic function.",
|
|
68
|
+
USER_UPDATE_FAILED: "Could not update the user record.",
|
|
69
|
+
// ---- Verifier ----
|
|
70
|
+
INVALID_VERIFIER: "Invalid or expired verifier.",
|
|
71
|
+
// ---- Passkey ----
|
|
72
|
+
PASSKEY_MISSING_CONFIG: "Passkey provider requires SITE_URL or explicit rpId configuration.",
|
|
73
|
+
PASSKEY_AUTH_REQUIRED: "Sign in first, then add a passkey to your account.",
|
|
74
|
+
PASSKEY_MISSING_VERIFIER: "Missing verifier for passkey operation.",
|
|
75
|
+
PASSKEY_INVALID_CLIENT_DATA: "Invalid passkey client data.",
|
|
76
|
+
PASSKEY_INVALID_ORIGIN: "Passkey origin does not match the expected value.",
|
|
77
|
+
PASSKEY_INVALID_CHALLENGE: "Invalid or expired passkey challenge.",
|
|
78
|
+
PASSKEY_RP_MISMATCH: "Relying party ID mismatch.",
|
|
79
|
+
PASSKEY_USER_PRESENCE: "User presence flag not set.",
|
|
80
|
+
PASSKEY_USER_VERIFICATION: "User verification required but not performed.",
|
|
81
|
+
PASSKEY_NO_CREDENTIAL: "No credential in attestation.",
|
|
82
|
+
PASSKEY_UNSUPPORTED_ALGORITHM: "Unsupported passkey algorithm.",
|
|
83
|
+
PASSKEY_INVALID_SIGNATURE: "Invalid passkey signature.",
|
|
84
|
+
PASSKEY_UNKNOWN_CREDENTIAL: "Unknown passkey credential.",
|
|
85
|
+
PASSKEY_COUNTER_ERROR: "Authenticator counter did not increase — possible credential cloning detected.",
|
|
86
|
+
PASSKEY_MISSING_FLOW: "Missing passkey flow parameter.",
|
|
87
|
+
PASSKEY_UNKNOWN_FLOW: "Unknown passkey flow.",
|
|
88
|
+
// ---- TOTP ----
|
|
89
|
+
TOTP_AUTH_REQUIRED: "Sign in first, then set up two-factor authentication.",
|
|
90
|
+
TOTP_MISSING_VERIFIER: "Missing verifier for TOTP operation.",
|
|
91
|
+
TOTP_MISSING_CODE: "Missing TOTP code.",
|
|
92
|
+
TOTP_MISSING_ID: "Missing TOTP enrollment ID.",
|
|
93
|
+
TOTP_NOT_FOUND: "TOTP enrollment not found.",
|
|
94
|
+
TOTP_ALREADY_VERIFIED: "TOTP enrollment is already verified.",
|
|
95
|
+
TOTP_INVALID_CODE: "Invalid TOTP code.",
|
|
96
|
+
TOTP_INVALID_VERIFIER: "Invalid or expired TOTP verifier.",
|
|
97
|
+
TOTP_NO_ENROLLMENT: "No verified TOTP enrollment found.",
|
|
98
|
+
TOTP_MISSING_FLOW: "Missing TOTP flow parameter.",
|
|
99
|
+
TOTP_UNKNOWN_FLOW: "Unknown TOTP flow.",
|
|
100
|
+
// ---- Internal (should never reach user) ----
|
|
101
|
+
INTERNAL_ERROR: "An unexpected error occurred.",
|
|
102
|
+
};
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Error helpers
|
|
105
|
+
// ============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Throw a structured `ConvexError` with `{ code, message }`.
|
|
108
|
+
*
|
|
109
|
+
* @param code Machine-readable error code from `AUTH_ERRORS`.
|
|
110
|
+
* @param message Optional override for the default human-readable message.
|
|
111
|
+
* @param context Optional extra fields merged into the error payload.
|
|
112
|
+
*/
|
|
113
|
+
export function throwAuthError(code, message, context) {
|
|
114
|
+
throw new ConvexError({
|
|
115
|
+
code,
|
|
116
|
+
message: message ?? AUTH_ERRORS[code],
|
|
117
|
+
...context,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Type guard: check whether a caught value is a structured auth `ConvexError`.
|
|
122
|
+
*
|
|
123
|
+
* @param error - The caught value (typically from a `catch` block).
|
|
124
|
+
* @returns `true` when `error` is a `ConvexError` with `{ code, message }` data.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* try { await auth.signIn('email', { email }); }
|
|
129
|
+
* catch (e) {
|
|
130
|
+
* if (isAuthError(e)) console.log(e.data.code); // "EMAIL_SEND_FAILED"
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export function isAuthError(error) {
|
|
135
|
+
return (error instanceof ConvexError &&
|
|
136
|
+
typeof error.data === "object" &&
|
|
137
|
+
error.data !== null &&
|
|
138
|
+
"code" in error.data &&
|
|
139
|
+
"message" in error.data);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extract `{ code, message }` from a caught error.
|
|
143
|
+
*
|
|
144
|
+
* Works for `ConvexError` (from Convex actions), plain `Error`
|
|
145
|
+
* instances, and structured auth errors. Returns `null` when the
|
|
146
|
+
* value is not an error object.
|
|
147
|
+
*
|
|
148
|
+
* @param error - The caught value to parse.
|
|
149
|
+
* @returns `{ code, message }` when extractable, or `null`.
|
|
150
|
+
* When `code` is `null`, the error is not a structured auth error
|
|
151
|
+
* but `message` still contains the error text.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* try {
|
|
156
|
+
* await auth.signIn("email", { email });
|
|
157
|
+
* } catch (e) {
|
|
158
|
+
* const err = parseAuthError(e);
|
|
159
|
+
* if (err?.code === "EMAIL_SEND_FAILED") { ... }
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function parseAuthError(error) {
|
|
164
|
+
if (isAuthError(error)) {
|
|
165
|
+
const { code, message } = error.data;
|
|
166
|
+
return { code, message };
|
|
167
|
+
}
|
|
168
|
+
if (error instanceof ConvexError && typeof error.data === "string") {
|
|
169
|
+
return { code: null, message: error.data };
|
|
170
|
+
}
|
|
171
|
+
if (error instanceof Error) {
|
|
172
|
+
return { code: null, message: error.message };
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/server/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key crypto utilities.
|
|
3
|
+
*
|
|
4
|
+
* Uses `@oslojs/crypto` primitives for key generation and hashing:
|
|
5
|
+
* - SHA-256 for hashing keys (API keys have high entropy, no need for bcrypt)
|
|
6
|
+
* - Cryptographically secure random generation for key material
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { KeyScope, ScopeChecker } from "../types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Generate a new API key.
|
|
13
|
+
*
|
|
14
|
+
* Returns the raw key (to be shown once to the user) and metadata for storage.
|
|
15
|
+
* The raw key is `{prefix}{32 random alphanumeric chars}`.
|
|
16
|
+
*
|
|
17
|
+
* @param prefix - Key prefix, defaults to "sk_live_"
|
|
18
|
+
* @returns `{ raw, hashedKey, displayPrefix }`
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateApiKey(prefix?: string): Promise<{
|
|
21
|
+
/** The full raw key — show to user once, never store. */
|
|
22
|
+
raw: string;
|
|
23
|
+
/** SHA-256 hex hash of the raw key — store this. */
|
|
24
|
+
hashedKey: string;
|
|
25
|
+
/** Truncated prefix for display (e.g. "sk_live_aBc1..."). */
|
|
26
|
+
displayPrefix: string;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Hash a raw API key for lookup.
|
|
30
|
+
*
|
|
31
|
+
* Used during Bearer token verification to find the stored key record.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hashApiKey(rawKey: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Build a `ScopeChecker` from an array of `KeyScope` entries.
|
|
36
|
+
*
|
|
37
|
+
* The checker provides a `.can(resource, action)` method that returns `true`
|
|
38
|
+
* if any scope entry grants the requested permission.
|
|
39
|
+
*
|
|
40
|
+
* A wildcard action `"*"` grants all actions on that resource.
|
|
41
|
+
* A wildcard resource `"*"` grants the action on all resources.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildScopeChecker(scopes: KeyScope[]): ScopeChecker;
|
|
44
|
+
/**
|
|
45
|
+
* Validate that requested scopes are a subset of the allowed scopes
|
|
46
|
+
* defined in the API key config.
|
|
47
|
+
*
|
|
48
|
+
* @param requested - Scopes the user wants on the new key.
|
|
49
|
+
* @param allowed - The scope definition from `apiKeys.scopes` config.
|
|
50
|
+
* @throws Error if any requested scope is not in the allowed set.
|
|
51
|
+
*/
|
|
52
|
+
export declare function validateScopes(requested: KeyScope[], allowed: Record<string, string[]> | undefined): void;
|
|
53
|
+
/**
|
|
54
|
+
* Check whether a key is rate-limited based on its stored state.
|
|
55
|
+
*
|
|
56
|
+
* Uses the same token-bucket algorithm as sign-in rate limiting:
|
|
57
|
+
* tokens refill linearly over the configured window.
|
|
58
|
+
*
|
|
59
|
+
* @returns `{ limited: boolean; newState: { attemptsLeft, lastAttemptTime } }`
|
|
60
|
+
*/
|
|
61
|
+
export declare function checkKeyRateLimit(rateLimit: {
|
|
62
|
+
maxRequests: number;
|
|
63
|
+
windowMs: number;
|
|
64
|
+
}, state: {
|
|
65
|
+
attemptsLeft: number;
|
|
66
|
+
lastAttemptTime: number;
|
|
67
|
+
} | undefined): {
|
|
68
|
+
limited: boolean;
|
|
69
|
+
newState: {
|
|
70
|
+
attemptsLeft: number;
|
|
71
|
+
lastAttemptTime: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=apiKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKey.d.ts","sourceRoot":"","sources":["../../../src/server/implementation/apiKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsB1D;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,MAAM,GAAE,MAA2B,GAAG,OAAO,CAAC;IACjF,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAOD;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,YAAY,CAWlE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,SAAS,GAC5C,IAAI,CAuBN;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpD,KAAK,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACnE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D,CAsCA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key crypto utilities.
|
|
3
|
+
*
|
|
4
|
+
* Uses `@oslojs/crypto` primitives for key generation and hashing:
|
|
5
|
+
* - SHA-256 for hashing keys (API keys have high entropy, no need for bcrypt)
|
|
6
|
+
* - Cryptographically secure random generation for key material
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { sha256, generateRandomString } from "./utils.js";
|
|
11
|
+
import { throwAuthError } from "../errors.js";
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Constants
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const DEFAULT_KEY_PREFIX = "sk_live_";
|
|
16
|
+
const KEY_RANDOM_LENGTH = 32;
|
|
17
|
+
const KEY_RANDOM_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
18
|
+
/**
|
|
19
|
+
* How many characters of the full key to store as the visible prefix.
|
|
20
|
+
* Includes the prefix string (e.g. "sk_live_") plus a few random chars.
|
|
21
|
+
*/
|
|
22
|
+
const VISIBLE_PREFIX_EXTRA_CHARS = 4;
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Key generation
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Generate a new API key.
|
|
28
|
+
*
|
|
29
|
+
* Returns the raw key (to be shown once to the user) and metadata for storage.
|
|
30
|
+
* The raw key is `{prefix}{32 random alphanumeric chars}`.
|
|
31
|
+
*
|
|
32
|
+
* @param prefix - Key prefix, defaults to "sk_live_"
|
|
33
|
+
* @returns `{ raw, hashedKey, displayPrefix }`
|
|
34
|
+
*/
|
|
35
|
+
export async function generateApiKey(prefix = DEFAULT_KEY_PREFIX) {
|
|
36
|
+
const randomPart = generateRandomString(KEY_RANDOM_LENGTH, KEY_RANDOM_ALPHABET);
|
|
37
|
+
const raw = `${prefix}${randomPart}`;
|
|
38
|
+
const hashedKey = await sha256(raw);
|
|
39
|
+
const displayPrefix = `${raw.substring(0, prefix.length + VISIBLE_PREFIX_EXTRA_CHARS)}...`;
|
|
40
|
+
return { raw, hashedKey, displayPrefix };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Hash a raw API key for lookup.
|
|
44
|
+
*
|
|
45
|
+
* Used during Bearer token verification to find the stored key record.
|
|
46
|
+
*/
|
|
47
|
+
export async function hashApiKey(rawKey) {
|
|
48
|
+
return sha256(rawKey);
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Scope checker
|
|
52
|
+
// ============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Build a `ScopeChecker` from an array of `KeyScope` entries.
|
|
55
|
+
*
|
|
56
|
+
* The checker provides a `.can(resource, action)` method that returns `true`
|
|
57
|
+
* if any scope entry grants the requested permission.
|
|
58
|
+
*
|
|
59
|
+
* A wildcard action `"*"` grants all actions on that resource.
|
|
60
|
+
* A wildcard resource `"*"` grants the action on all resources.
|
|
61
|
+
*/
|
|
62
|
+
export function buildScopeChecker(scopes) {
|
|
63
|
+
return {
|
|
64
|
+
scopes,
|
|
65
|
+
can(resource, action) {
|
|
66
|
+
return scopes.some((scope) => (scope.resource === resource || scope.resource === "*") &&
|
|
67
|
+
(scope.actions.includes(action) || scope.actions.includes("*")));
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate that requested scopes are a subset of the allowed scopes
|
|
73
|
+
* defined in the API key config.
|
|
74
|
+
*
|
|
75
|
+
* @param requested - Scopes the user wants on the new key.
|
|
76
|
+
* @param allowed - The scope definition from `apiKeys.scopes` config.
|
|
77
|
+
* @throws Error if any requested scope is not in the allowed set.
|
|
78
|
+
*/
|
|
79
|
+
export function validateScopes(requested, allowed) {
|
|
80
|
+
if (!allowed) {
|
|
81
|
+
// No scope restrictions configured — allow anything.
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const scope of requested) {
|
|
85
|
+
const allowedActions = allowed[scope.resource];
|
|
86
|
+
if (!allowedActions) {
|
|
87
|
+
throwAuthError("API_KEY_INVALID_SCOPE", `Unknown resource "${scope.resource}" in API key scopes. Allowed resources: ${Object.keys(allowed).join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
for (const action of scope.actions) {
|
|
90
|
+
if (action !== "*" && !allowedActions.includes(action)) {
|
|
91
|
+
throwAuthError("API_KEY_INVALID_SCOPE", `Unknown action "${action}" for resource "${scope.resource}". Allowed actions: ${allowedActions.join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Per-key rate limiting (token-bucket)
|
|
98
|
+
// ============================================================================
|
|
99
|
+
/**
|
|
100
|
+
* Check whether a key is rate-limited based on its stored state.
|
|
101
|
+
*
|
|
102
|
+
* Uses the same token-bucket algorithm as sign-in rate limiting:
|
|
103
|
+
* tokens refill linearly over the configured window.
|
|
104
|
+
*
|
|
105
|
+
* @returns `{ limited: boolean; newState: { attemptsLeft, lastAttemptTime } }`
|
|
106
|
+
*/
|
|
107
|
+
export function checkKeyRateLimit(rateLimit, state) {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (!state) {
|
|
110
|
+
// First request — create initial state with one token consumed.
|
|
111
|
+
return {
|
|
112
|
+
limited: false,
|
|
113
|
+
newState: {
|
|
114
|
+
attemptsLeft: rateLimit.maxRequests - 1,
|
|
115
|
+
lastAttemptTime: now,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const elapsed = now - state.lastAttemptTime;
|
|
120
|
+
const refillRate = rateLimit.maxRequests / rateLimit.windowMs;
|
|
121
|
+
const refilled = Math.min(rateLimit.maxRequests, state.attemptsLeft + elapsed * refillRate);
|
|
122
|
+
if (refilled < 1) {
|
|
123
|
+
return {
|
|
124
|
+
limited: true,
|
|
125
|
+
newState: {
|
|
126
|
+
attemptsLeft: refilled,
|
|
127
|
+
lastAttemptTime: now,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
limited: false,
|
|
133
|
+
newState: {
|
|
134
|
+
attemptsLeft: refilled - 1,
|
|
135
|
+
lastAttemptTime: now,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=apiKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKey.js","sourceRoot":"","sources":["../../../src/server/implementation/apiKey.ts"],"names":[],"mappings":"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"}
|