@robelest/convex-auth 0.0.3-preview → 0.0.3-preview.3
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 +15 -15
- package/dist/client/index.d.ts +40 -12
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +73 -12
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -2
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +1 -1
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/{portalBridge.d.ts → bridge.d.ts} +2 -2
- package/dist/component/bridge.d.ts.map +1 -0
- package/dist/component/{portalBridge.js → bridge.js} +2 -2
- package/dist/component/bridge.js.map +1 -0
- package/dist/component/index.d.ts +11 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +8 -2
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +24 -17
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +23 -4
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +11 -7
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +4 -1
- 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 → auth.d.ts} +226 -36
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/{convex-auth.js → auth.js} +287 -111
- package/dist/server/auth.js.map +1 -0
- package/dist/server/errors.d.ts +148 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +179 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/implementation/index.d.ts +170 -48
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +383 -167
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/{apiKey.d.ts → keys.d.ts} +1 -1
- package/dist/server/implementation/keys.d.ts.map +1 -0
- package/dist/server/implementation/{apiKey.js → keys.js} +4 -5
- package/dist/server/implementation/keys.js.map +1 -0
- package/dist/server/implementation/mutations/{modifyAccount.d.ts → account.d.ts} +3 -3
- package/dist/server/implementation/mutations/account.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{modifyAccount.js → account.js} +4 -3
- package/dist/server/implementation/mutations/account.js.map +1 -0
- package/dist/server/implementation/mutations/{createVerificationCode.d.ts → code.d.ts} +1 -1
- package/dist/server/implementation/mutations/code.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{createVerificationCode.js → code.js} +2 -2
- package/dist/server/implementation/mutations/code.js.map +1 -0
- package/dist/server/implementation/mutations/index.d.ts +33 -33
- package/dist/server/implementation/mutations/index.d.ts.map +1 -1
- package/dist/server/implementation/mutations/index.js +22 -22
- package/dist/server/implementation/mutations/index.js.map +1 -1
- package/dist/server/implementation/mutations/{invalidateSessions.d.ts → invalidate.d.ts} +1 -1
- package/dist/server/implementation/mutations/invalidate.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{invalidateSessions.js → invalidate.js} +2 -2
- package/dist/server/implementation/mutations/invalidate.js.map +1 -0
- package/dist/server/implementation/mutations/{userOAuth.d.ts → oauth.d.ts} +3 -3
- package/dist/server/implementation/mutations/oauth.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{userOAuth.js → oauth.js} +4 -3
- package/dist/server/implementation/mutations/oauth.js.map +1 -0
- package/dist/server/implementation/mutations/{refreshSession.d.ts → refresh.d.ts} +1 -1
- package/dist/server/implementation/mutations/refresh.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{refreshSession.js → refresh.js} +3 -3
- package/dist/server/implementation/mutations/refresh.js.map +1 -0
- package/dist/server/implementation/mutations/{createAccountFromCredentials.d.ts → register.d.ts} +4 -4
- package/dist/server/implementation/mutations/register.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{createAccountFromCredentials.js → register.js} +4 -3
- package/dist/server/implementation/mutations/register.js.map +1 -0
- package/dist/server/implementation/mutations/{retrieveAccountWithCredentials.d.ts → retrieve.d.ts} +3 -3
- package/dist/server/implementation/mutations/retrieve.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{retrieveAccountWithCredentials.js → retrieve.js} +3 -3
- package/dist/server/implementation/mutations/retrieve.js.map +1 -0
- package/dist/server/implementation/mutations/{verifierSignature.d.ts → signature.d.ts} +1 -1
- package/dist/server/implementation/mutations/signature.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{verifierSignature.js → signature.js} +4 -3
- package/dist/server/implementation/mutations/signature.js.map +1 -0
- package/dist/server/implementation/mutations/{signIn.d.ts → signin.d.ts} +1 -1
- package/dist/server/implementation/mutations/{signIn.d.ts.map → signin.d.ts.map} +1 -1
- package/dist/server/implementation/mutations/{signIn.js → signin.js} +2 -2
- package/dist/server/implementation/mutations/{signIn.js.map → signin.js.map} +1 -1
- package/dist/server/implementation/mutations/{signOut.d.ts → signout.d.ts} +1 -1
- package/dist/server/implementation/mutations/{signOut.d.ts.map → signout.d.ts.map} +1 -1
- package/dist/server/implementation/mutations/{signOut.js → signout.js} +2 -2
- package/dist/server/implementation/mutations/{signOut.js.map → signout.js.map} +1 -1
- package/dist/server/implementation/mutations/{storeRef.d.ts → store.d.ts} +1 -1
- package/dist/server/implementation/mutations/store.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{storeRef.js → store.js} +1 -1
- package/dist/server/implementation/mutations/store.js.map +1 -0
- package/dist/server/implementation/mutations/verifier.js +1 -1
- package/dist/server/implementation/mutations/verifier.js.map +1 -1
- package/dist/server/implementation/mutations/{verifyCodeAndSignIn.d.ts → verify.d.ts} +1 -1
- package/dist/server/implementation/mutations/verify.d.ts.map +1 -0
- package/dist/server/implementation/mutations/{verifyCodeAndSignIn.js → verify.js} +3 -3
- package/dist/server/implementation/mutations/verify.js.map +1 -0
- package/dist/server/implementation/passkey.d.ts.map +1 -1
- package/dist/server/implementation/passkey.js +47 -55
- 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/{rateLimit.d.ts → ratelimit.d.ts} +1 -1
- package/dist/server/implementation/{rateLimit.d.ts.map → ratelimit.d.ts.map} +1 -1
- package/dist/server/implementation/{rateLimit.js → ratelimit.js} +1 -1
- package/dist/server/implementation/{rateLimit.js.map → ratelimit.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 → refresh.d.ts} +1 -1
- package/dist/server/implementation/refresh.d.ts.map +1 -0
- package/dist/server/implementation/{refreshTokens.js → refresh.js} +3 -2
- package/dist/server/implementation/refresh.js.map +1 -0
- package/dist/server/implementation/sessions.js +1 -1
- package/dist/server/implementation/sessions.js.map +1 -1
- package/dist/server/implementation/{signIn.d.ts → signin.d.ts} +1 -1
- package/dist/server/implementation/{signIn.d.ts.map → signin.d.ts.map} +1 -1
- package/dist/server/implementation/{signIn.js → signin.js} +12 -8
- package/dist/server/implementation/signin.js.map +1 -0
- package/dist/server/implementation/totp.d.ts.map +1 -1
- package/dist/server/implementation/totp.js +29 -29
- package/dist/server/implementation/totp.js.map +1 -1
- package/dist/server/implementation/types.d.ts +131 -1
- package/dist/server/implementation/types.d.ts.map +1 -1
- package/dist/server/implementation/types.js +65 -1
- package/dist/server/implementation/types.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 +131 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +117 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth/{authorizationUrl.d.ts → authorization.d.ts} +1 -1
- package/dist/server/oauth/authorization.d.ts.map +1 -0
- package/dist/server/oauth/{authorizationUrl.js → authorization.js} +4 -3
- package/dist/server/oauth/authorization.js.map +1 -0
- package/dist/server/oauth/callback.d.ts.map +1 -1
- package/dist/server/oauth/callback.js +7 -6
- 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 → helpers.d.ts} +1 -1
- package/dist/server/oauth/helpers.d.ts.map +1 -0
- package/dist/server/oauth/{convexAuth.js → helpers.js} +6 -5
- package/dist/server/oauth/helpers.js.map +1 -0
- package/dist/server/oauth/lib/utils/{customFetch.d.ts → fetch.d.ts} +1 -1
- package/dist/server/oauth/lib/utils/fetch.d.ts.map +1 -0
- package/dist/server/oauth/lib/utils/{customFetch.js → fetch.js} +1 -1
- package/dist/server/oauth/lib/utils/fetch.js.map +1 -0
- package/dist/server/{provider_utils.d.ts → providers.d.ts} +1 -1
- package/dist/server/providers.d.ts.map +1 -0
- package/dist/server/{provider_utils.js → providers.js} +1 -1
- package/dist/server/providers.js.map +1 -0
- package/dist/server/{email-templates.d.ts → templates.d.ts} +8 -1
- package/dist/server/templates.d.ts.map +1 -0
- package/dist/server/{portal-email.js → templates.js} +74 -3
- package/dist/server/templates.js.map +1 -0
- package/dist/server/types.d.ts +88 -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 +1 -1
- package/dist/server/version.d.ts.map +1 -1
- package/dist/server/version.js +1 -1
- package/dist/server/version.js.map +1 -1
- package/package.json +5 -1
- package/src/cli/index.ts +5 -5
- package/src/cli/{portal-link.ts → link.ts} +1 -1
- package/src/cli/utils.ts +1 -1
- package/src/client/index.ts +102 -17
- package/src/component/_generated/api.ts +2 -2
- package/src/component/_generated/component.ts +1 -1
- package/src/component/{portalBridge.ts → bridge.ts} +2 -2
- package/src/component/index.ts +10 -2
- package/src/component/public.ts +25 -4
- package/src/component/schema.ts +4 -1
- 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/auth.ts +969 -0
- package/src/server/errors.ts +275 -0
- package/src/server/implementation/index.ts +370 -88
- package/src/server/implementation/{apiKey.ts → keys.ts} +7 -6
- package/src/server/implementation/mutations/{modifyAccount.ts → account.ts} +3 -4
- package/src/server/implementation/mutations/{createVerificationCode.ts → code.ts} +1 -1
- package/src/server/implementation/mutations/index.ts +22 -22
- package/src/server/implementation/mutations/{invalidateSessions.ts → invalidate.ts} +1 -1
- package/src/server/implementation/mutations/{userOAuth.ts → oauth.ts} +3 -2
- package/src/server/implementation/mutations/{refreshSession.ts → refresh.ts} +2 -2
- package/src/server/implementation/mutations/{createAccountFromCredentials.ts → register.ts} +3 -2
- package/src/server/implementation/mutations/{retrieveAccountWithCredentials.ts → retrieve.ts} +2 -2
- package/src/server/implementation/mutations/{verifierSignature.ts → signature.ts} +3 -2
- package/src/server/implementation/mutations/{signIn.ts → signin.ts} +1 -1
- package/src/server/implementation/mutations/{signOut.ts → signout.ts} +1 -1
- package/src/server/implementation/mutations/verifier.ts +1 -1
- package/src/server/implementation/mutations/{verifyCodeAndSignIn.ts → verify.ts} +2 -2
- package/src/server/implementation/passkey.ts +86 -116
- package/src/server/implementation/provider.ts +5 -8
- package/src/server/implementation/redirects.ts +2 -3
- package/src/server/implementation/{refreshTokens.ts → refresh.ts} +2 -1
- package/src/server/implementation/sessions.ts +1 -1
- package/src/server/implementation/{signIn.ts → signin.ts} +13 -11
- package/src/server/implementation/totp.ts +60 -84
- package/src/server/implementation/types.ts +316 -1
- package/src/server/implementation/users.ts +4 -7
- package/src/server/index.ts +142 -3
- package/src/server/oauth/{authorizationUrl.ts → authorization.ts} +3 -2
- package/src/server/oauth/callback.ts +7 -6
- package/src/server/oauth/checks.ts +3 -1
- package/src/server/oauth/{convexAuth.ts → helpers.ts} +8 -5
- package/src/server/{portal-email.ts → templates.ts} +78 -2
- package/src/server/types.ts +133 -4
- package/src/server/utils.ts +3 -1
- package/src/server/version.ts +1 -1
- package/dist/component/portalBridge.d.ts.map +0 -1
- package/dist/component/portalBridge.js.map +0 -1
- package/dist/server/convex-auth.d.ts.map +0 -1
- package/dist/server/convex-auth.js.map +0 -1
- package/dist/server/convex_types.d.ts +0 -17
- package/dist/server/convex_types.d.ts.map +0 -1
- package/dist/server/convex_types.js +0 -2
- package/dist/server/convex_types.js.map +0 -1
- package/dist/server/email-templates.d.ts.map +0 -1
- package/dist/server/email-templates.js +0 -74
- package/dist/server/email-templates.js.map +0 -1
- package/dist/server/implementation/apiKey.d.ts.map +0 -1
- package/dist/server/implementation/apiKey.js.map +0 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +0 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +0 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +0 -1
- package/dist/server/implementation/mutations/createVerificationCode.js.map +0 -1
- package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +0 -1
- package/dist/server/implementation/mutations/invalidateSessions.js.map +0 -1
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +0 -1
- package/dist/server/implementation/mutations/modifyAccount.js.map +0 -1
- package/dist/server/implementation/mutations/refreshSession.d.ts.map +0 -1
- package/dist/server/implementation/mutations/refreshSession.js.map +0 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +0 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +0 -1
- package/dist/server/implementation/mutations/storeRef.d.ts.map +0 -1
- package/dist/server/implementation/mutations/storeRef.js.map +0 -1
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +0 -1
- package/dist/server/implementation/mutations/userOAuth.js.map +0 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +0 -1
- package/dist/server/implementation/mutations/verifierSignature.js.map +0 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +0 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +0 -1
- package/dist/server/implementation/refreshTokens.d.ts.map +0 -1
- package/dist/server/implementation/refreshTokens.js.map +0 -1
- package/dist/server/implementation/signIn.js.map +0 -1
- package/dist/server/oauth/authorizationUrl.d.ts.map +0 -1
- package/dist/server/oauth/authorizationUrl.js.map +0 -1
- package/dist/server/oauth/convexAuth.d.ts.map +0 -1
- package/dist/server/oauth/convexAuth.js.map +0 -1
- package/dist/server/oauth/lib/utils/customFetch.d.ts.map +0 -1
- package/dist/server/oauth/lib/utils/customFetch.js.map +0 -1
- package/dist/server/portal-email.d.ts +0 -19
- package/dist/server/portal-email.d.ts.map +0 -1
- package/dist/server/portal-email.js.map +0 -1
- package/dist/server/provider_utils.d.ts.map +0 -1
- package/dist/server/provider_utils.js.map +0 -1
- package/src/server/convex-auth.ts +0 -602
- package/src/server/convex_types.ts +0 -55
- package/src/server/email-templates.ts +0 -77
- /package/src/cli/{generateKeys.ts → keys.ts} +0 -0
- /package/src/cli/{portal-upload.ts → upload.ts} +0 -0
- /package/src/server/implementation/mutations/{storeRef.ts → store.ts} +0 -0
- /package/src/server/implementation/{rateLimit.ts → ratelimit.ts} +0 -0
- /package/src/server/oauth/lib/utils/{customFetch.ts → fetch.ts} +0 -0
- /package/src/server/{provider_utils.ts → providers.ts} +0 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { actionGeneric, httpActionGeneric, internalMutationGeneric, } from "convex/server";
|
|
2
2
|
import { ConvexError, v } from "convex/values";
|
|
3
|
+
import { throwAuthError, isAuthError } from "../errors.js";
|
|
3
4
|
import { parse as parseCookies, serialize as serializeCookie } from "cookie";
|
|
4
5
|
import { redirectToParamCookie, useRedirectToParam } from "../cookies.js";
|
|
5
|
-
import { configDefaults, listAvailableProviders, materializeProvider, } from "../
|
|
6
|
+
import { configDefaults, listAvailableProviders, materializeProvider, } from "../providers.js";
|
|
6
7
|
import { requireEnv } from "../utils.js";
|
|
7
8
|
import { LOG_LEVELS, TOKEN_SUB_CLAIM_DIVIDER, logError, logWithLevel, } from "./utils.js";
|
|
8
9
|
import { callCreateAccountFromCredentials, callInvalidateSessions, callModifyAccount, callRetreiveAccountWithCredentials, callSignOut, callUserOAuth, callVerifierSignature, storeArgs, storeImpl, } from "./mutations/index.js";
|
|
9
|
-
import { signInImpl } from "./
|
|
10
|
+
import { signInImpl } from "./signin.js";
|
|
10
11
|
import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
|
|
11
|
-
import { generateApiKey, hashApiKey, buildScopeChecker, validateScopes, checkKeyRateLimit, } from "./
|
|
12
|
-
import { getAuthorizationUrl } from "../oauth/
|
|
13
|
-
import { defaultCookiesOptions, oAuthConfigToInternalProvider, } from "../oauth/
|
|
12
|
+
import { generateApiKey, hashApiKey, buildScopeChecker, validateScopes, checkKeyRateLimit, } from "./keys.js";
|
|
13
|
+
import { getAuthorizationUrl } from "../oauth/authorization.js";
|
|
14
|
+
import { defaultCookiesOptions, oAuthConfigToInternalProvider, } from "../oauth/helpers.js";
|
|
14
15
|
import { handleOAuth } from "../oauth/callback.js";
|
|
15
16
|
/**
|
|
16
17
|
* Configure the Convex Auth library. Returns an object with
|
|
@@ -42,10 +43,10 @@ export function Auth(config_) {
|
|
|
42
43
|
const getProviderOrThrow = (id, allowExtraProviders = false) => {
|
|
43
44
|
const provider = getProvider(id, allowExtraProviders);
|
|
44
45
|
if (provider === undefined) {
|
|
45
|
-
const
|
|
46
|
+
const detail = `Provider \`${id}\` is not configured, ` +
|
|
46
47
|
`available providers are ${listAvailableProviders(config, allowExtraProviders)}.`;
|
|
47
|
-
logWithLevel(LOG_LEVELS.ERROR,
|
|
48
|
-
|
|
48
|
+
logWithLevel(LOG_LEVELS.ERROR, detail);
|
|
49
|
+
throwAuthError("PROVIDER_NOT_CONFIGURED", detail, { provider: id });
|
|
49
50
|
}
|
|
50
51
|
return provider;
|
|
51
52
|
};
|
|
@@ -54,6 +55,9 @@ export function Auth(config_) {
|
|
|
54
55
|
/**
|
|
55
56
|
* Get the current user's ID from the auth context, or `null` if
|
|
56
57
|
* not signed in.
|
|
58
|
+
*
|
|
59
|
+
* @param ctx - Any Convex context with an `auth` field (query, mutation, or action).
|
|
60
|
+
* @returns The user's `Id<"user">`, or `null` when unauthenticated.
|
|
57
61
|
*/
|
|
58
62
|
current: async (ctx) => {
|
|
59
63
|
const identity = await ctx.auth.getUserIdentity();
|
|
@@ -66,24 +70,35 @@ export function Auth(config_) {
|
|
|
66
70
|
/**
|
|
67
71
|
* Get the current user's ID, or throw if not signed in.
|
|
68
72
|
* Use this when authentication is required.
|
|
73
|
+
*
|
|
74
|
+
* @param ctx - Any Convex context with an `auth` field.
|
|
75
|
+
* @returns The user's `Id<"user">`.
|
|
76
|
+
* @throws `ConvexError` with code `NOT_SIGNED_IN` when unauthenticated.
|
|
69
77
|
*/
|
|
70
78
|
require: async (ctx) => {
|
|
71
79
|
const identity = await ctx.auth.getUserIdentity();
|
|
72
80
|
if (identity === null) {
|
|
73
|
-
|
|
81
|
+
throwAuthError("NOT_SIGNED_IN");
|
|
74
82
|
}
|
|
75
83
|
const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
|
|
76
84
|
return userId;
|
|
77
85
|
},
|
|
78
86
|
/**
|
|
79
87
|
* Retrieve a user document by their ID.
|
|
88
|
+
*
|
|
89
|
+
* @param ctx - Convex context with `runQuery`.
|
|
90
|
+
* @param userId - The user document ID.
|
|
91
|
+
* @returns The user document, or `null` if not found.
|
|
80
92
|
*/
|
|
81
93
|
get: async (ctx, userId) => {
|
|
82
94
|
return await ctx.runQuery(config.component.public.userGetById, { userId });
|
|
83
95
|
},
|
|
84
96
|
/**
|
|
85
97
|
* Get the currently signed-in user's document, or `null` if not
|
|
86
|
-
* signed in. Convenience
|
|
98
|
+
* signed in. Convenience combining `current()` + `get()`.
|
|
99
|
+
*
|
|
100
|
+
* @param ctx - Convex context with `auth` and `runQuery`.
|
|
101
|
+
* @returns The user document, or `null` when unauthenticated.
|
|
87
102
|
*/
|
|
88
103
|
viewer: async (ctx) => {
|
|
89
104
|
const userId = await auth.user.current(ctx);
|
|
@@ -92,6 +107,19 @@ export function Auth(config_) {
|
|
|
92
107
|
}
|
|
93
108
|
return await ctx.runQuery(config.component.public.userGetById, { userId });
|
|
94
109
|
},
|
|
110
|
+
/**
|
|
111
|
+
* Update a user document with partial data.
|
|
112
|
+
*
|
|
113
|
+
* @param ctx - Convex context with `runMutation`.
|
|
114
|
+
* @param userId - The user document ID.
|
|
115
|
+
* @param data - Partial data to merge into the user document.
|
|
116
|
+
*/
|
|
117
|
+
patch: async (ctx, userId, data) => {
|
|
118
|
+
await ctx.runMutation(config.component.public.userPatch, {
|
|
119
|
+
userId,
|
|
120
|
+
data,
|
|
121
|
+
});
|
|
122
|
+
},
|
|
95
123
|
/**
|
|
96
124
|
* Query a user's group memberships.
|
|
97
125
|
*/
|
|
@@ -117,6 +145,9 @@ export function Auth(config_) {
|
|
|
117
145
|
/**
|
|
118
146
|
* Get the current session ID from the auth context, or `null` if
|
|
119
147
|
* not signed in.
|
|
148
|
+
*
|
|
149
|
+
* @param ctx - Any Convex context with an `auth` field.
|
|
150
|
+
* @returns The session's `Id<"session">`, or `null` when unauthenticated.
|
|
120
151
|
*/
|
|
121
152
|
current: async (ctx) => {
|
|
122
153
|
const identity = await ctx.auth.getUserIdentity();
|
|
@@ -128,6 +159,10 @@ export function Auth(config_) {
|
|
|
128
159
|
},
|
|
129
160
|
/**
|
|
130
161
|
* Invalidate sessions for a user, optionally preserving specific sessions.
|
|
162
|
+
*
|
|
163
|
+
* @param ctx - Convex action context.
|
|
164
|
+
* @param args.userId - The user whose sessions to invalidate.
|
|
165
|
+
* @param args.except - Session IDs to preserve (e.g. the current session).
|
|
131
166
|
*/
|
|
132
167
|
invalidate: async (ctx, args) => {
|
|
133
168
|
const actionCtx = ctx;
|
|
@@ -137,6 +172,10 @@ export function Auth(config_) {
|
|
|
137
172
|
account: {
|
|
138
173
|
/**
|
|
139
174
|
* Create an account and user for a credentials provider.
|
|
175
|
+
*
|
|
176
|
+
* @param ctx - Convex action context.
|
|
177
|
+
* @param args - Provider ID, account credentials, profile data, and link flags.
|
|
178
|
+
* @returns `{ account, user }` — the created account and user documents.
|
|
140
179
|
*/
|
|
141
180
|
create: async (ctx, args) => {
|
|
142
181
|
const actionCtx = ctx;
|
|
@@ -144,17 +183,25 @@ export function Auth(config_) {
|
|
|
144
183
|
},
|
|
145
184
|
/**
|
|
146
185
|
* Retrieve an account and user for a credentials provider.
|
|
186
|
+
*
|
|
187
|
+
* @param ctx - Convex action context.
|
|
188
|
+
* @param args - Provider ID and account credentials (id, optional secret).
|
|
189
|
+
* @returns `{ account, user }` — the matched account and user documents.
|
|
190
|
+
* @throws `ConvexError` with code `ACCOUNT_NOT_FOUND` when no match exists.
|
|
147
191
|
*/
|
|
148
192
|
get: async (ctx, args) => {
|
|
149
193
|
const actionCtx = ctx;
|
|
150
194
|
const result = await callRetreiveAccountWithCredentials(actionCtx, args);
|
|
151
195
|
if (typeof result === "string") {
|
|
152
|
-
|
|
196
|
+
throwAuthError("ACCOUNT_NOT_FOUND", result);
|
|
153
197
|
}
|
|
154
198
|
return result;
|
|
155
199
|
},
|
|
156
200
|
/**
|
|
157
|
-
* Update credentials for an existing account.
|
|
201
|
+
* Update credentials (secret) for an existing account.
|
|
202
|
+
*
|
|
203
|
+
* @param ctx - Convex action context.
|
|
204
|
+
* @param args - Provider ID and new account credentials (id + secret).
|
|
158
205
|
*/
|
|
159
206
|
updateCredentials: async (ctx, args) => {
|
|
160
207
|
const actionCtx = ctx;
|
|
@@ -164,9 +211,16 @@ export function Auth(config_) {
|
|
|
164
211
|
provider: {
|
|
165
212
|
/**
|
|
166
213
|
* Sign in via another provider, typically from a credentials flow.
|
|
214
|
+
*
|
|
215
|
+
* @param ctx - Convex action context.
|
|
216
|
+
* @param provider - The provider config to sign in with.
|
|
217
|
+
* @param args - Optional account ID and params.
|
|
218
|
+
* @returns `{ userId, sessionId }` on success, or `null`.
|
|
167
219
|
*/
|
|
168
220
|
signIn: async (ctx, provider, args) => {
|
|
169
|
-
const result = await signInImpl(enrichCtx(ctx), materializeProvider(provider),
|
|
221
|
+
const result = await signInImpl(enrichCtx(ctx), materializeProvider(provider),
|
|
222
|
+
// params type widened: Record<string, unknown> → Record<string, any>
|
|
223
|
+
args, {
|
|
170
224
|
generateTokens: false,
|
|
171
225
|
allowExtraProviders: true,
|
|
172
226
|
});
|
|
@@ -211,6 +265,7 @@ export function Auth(config_) {
|
|
|
211
265
|
*/
|
|
212
266
|
list: async (ctx, opts) => {
|
|
213
267
|
return await ctx.runQuery(config.component.public.groupList, {
|
|
268
|
+
type: opts?.type,
|
|
214
269
|
parentGroupId: opts?.parentGroupId,
|
|
215
270
|
});
|
|
216
271
|
},
|
|
@@ -338,15 +393,17 @@ export function Auth(config_) {
|
|
|
338
393
|
* the timestamp. If the invite has a group, the caller is responsible
|
|
339
394
|
* for creating the member record via `auth.group.member.add` in the
|
|
340
395
|
* same Convex mutation for transactional safety.
|
|
341
|
-
*
|
|
342
|
-
* @throws ConvexError with code `INVITE_NOT_FOUND` when the invite does
|
|
343
|
-
* not exist.
|
|
344
|
-
* @throws ConvexError with code `INVITE_NOT_PENDING` when the invite is
|
|
345
|
-
* not in `pending` status.
|
|
346
396
|
*
|
|
397
|
+
* @param ctx - Convex context with `runMutation`.
|
|
398
|
+
* @param inviteId - The invite document ID.
|
|
399
|
+
* @param acceptedByUserId - User accepting the invite (recorded for audit).
|
|
400
|
+
* @throws `ConvexError` with code `INVITE_NOT_FOUND` when the invite does not exist.
|
|
401
|
+
* @throws `ConvexError` with code `INVITE_NOT_PENDING` when the invite is not in `pending` status.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
347
404
|
* ```ts
|
|
348
|
-
|
|
349
|
-
|
|
405
|
+
* export const acceptInvite = mutation({
|
|
406
|
+
* args: { inviteId: v.string() },
|
|
350
407
|
* handler: async (ctx, { inviteId }) => {
|
|
351
408
|
* const userId = await auth.user.require(ctx);
|
|
352
409
|
* const invite = await auth.invite.get(ctx, inviteId);
|
|
@@ -373,10 +430,10 @@ export function Auth(config_) {
|
|
|
373
430
|
/**
|
|
374
431
|
* Revoke a pending invitation.
|
|
375
432
|
*
|
|
376
|
-
* @
|
|
377
|
-
*
|
|
378
|
-
* @throws ConvexError with code `
|
|
379
|
-
* not in `pending` status.
|
|
433
|
+
* @param ctx - Convex context with `runMutation`.
|
|
434
|
+
* @param inviteId - The invite document ID.
|
|
435
|
+
* @throws `ConvexError` with code `INVITE_NOT_FOUND` when the invite does not exist.
|
|
436
|
+
* @throws `ConvexError` with code `INVITE_NOT_PENDING` when the invite is not in `pending` status.
|
|
380
437
|
*/
|
|
381
438
|
revoke: async (ctx, inviteId) => {
|
|
382
439
|
await ctx.runMutation(config.component.public.inviteRevoke, { inviteId });
|
|
@@ -482,7 +539,7 @@ export function Auth(config_) {
|
|
|
482
539
|
// Validate scopes against config if defined
|
|
483
540
|
validateScopes(opts.scopes, config.apiKeys?.scopes);
|
|
484
541
|
const { raw, hashedKey, displayPrefix } = await generateApiKey(prefix);
|
|
485
|
-
const keyId = await ctx.runMutation(config.component.public.keyInsert, {
|
|
542
|
+
const keyId = (await ctx.runMutation(config.component.public.keyInsert, {
|
|
486
543
|
userId: opts.userId,
|
|
487
544
|
prefix: displayPrefix,
|
|
488
545
|
hashedKey,
|
|
@@ -490,8 +547,8 @@ export function Auth(config_) {
|
|
|
490
547
|
scopes: opts.scopes,
|
|
491
548
|
rateLimit: opts.rateLimit ?? config.apiKeys?.defaultRateLimit,
|
|
492
549
|
expiresAt: opts.expiresAt,
|
|
493
|
-
});
|
|
494
|
-
return { keyId
|
|
550
|
+
}));
|
|
551
|
+
return { keyId, raw };
|
|
495
552
|
},
|
|
496
553
|
/**
|
|
497
554
|
* Verify a raw API key string. Returns the userId and a scope checker
|
|
@@ -503,22 +560,22 @@ export function Auth(config_) {
|
|
|
503
560
|
*/
|
|
504
561
|
verify: async (ctx, rawKey) => {
|
|
505
562
|
const hashedKey = await hashApiKey(rawKey);
|
|
506
|
-
const key = await ctx.runQuery(config.component.public.keyGetByHashedKey, { hashedKey });
|
|
563
|
+
const key = (await ctx.runQuery(config.component.public.keyGetByHashedKey, { hashedKey }));
|
|
507
564
|
if (!key) {
|
|
508
|
-
|
|
565
|
+
throwAuthError("INVALID_API_KEY");
|
|
509
566
|
}
|
|
510
567
|
if (key.revoked) {
|
|
511
|
-
|
|
568
|
+
throwAuthError("API_KEY_REVOKED");
|
|
512
569
|
}
|
|
513
570
|
if (key.expiresAt && key.expiresAt < Date.now()) {
|
|
514
|
-
|
|
571
|
+
throwAuthError("API_KEY_EXPIRED");
|
|
515
572
|
}
|
|
516
573
|
// Check per-key rate limit
|
|
517
574
|
const patchData = { lastUsedAt: Date.now() };
|
|
518
575
|
if (key.rateLimit) {
|
|
519
576
|
const { limited, newState } = checkKeyRateLimit(key.rateLimit, key.rateLimitState ?? undefined);
|
|
520
577
|
if (limited) {
|
|
521
|
-
|
|
578
|
+
throwAuthError("API_KEY_RATE_LIMITED");
|
|
522
579
|
}
|
|
523
580
|
patchData.rateLimitState = newState;
|
|
524
581
|
}
|
|
@@ -538,14 +595,14 @@ export function Auth(config_) {
|
|
|
538
595
|
* Never includes the raw key — only the display prefix.
|
|
539
596
|
*/
|
|
540
597
|
list: async (ctx, opts) => {
|
|
541
|
-
return await ctx.runQuery(config.component.public.keyListByUserId, { userId: opts.userId });
|
|
598
|
+
return (await ctx.runQuery(config.component.public.keyListByUserId, { userId: opts.userId }));
|
|
542
599
|
},
|
|
543
600
|
/**
|
|
544
601
|
* Get a single API key by its document ID.
|
|
545
602
|
* Returns `null` if not found.
|
|
546
603
|
*/
|
|
547
604
|
get: async (ctx, keyId) => {
|
|
548
|
-
return await ctx.runQuery(config.component.public.keyGetById, { keyId
|
|
605
|
+
return (await ctx.runQuery(config.component.public.keyGetById, { keyId }));
|
|
549
606
|
},
|
|
550
607
|
/**
|
|
551
608
|
* Update an API key's metadata (name, scopes, rate limit).
|
|
@@ -555,7 +612,7 @@ export function Auth(config_) {
|
|
|
555
612
|
validateScopes(data.scopes, config.apiKeys?.scopes);
|
|
556
613
|
}
|
|
557
614
|
await ctx.runMutation(config.component.public.keyPatch, {
|
|
558
|
-
keyId
|
|
615
|
+
keyId,
|
|
559
616
|
data,
|
|
560
617
|
});
|
|
561
618
|
},
|
|
@@ -565,7 +622,7 @@ export function Auth(config_) {
|
|
|
565
622
|
*/
|
|
566
623
|
revoke: async (ctx, keyId) => {
|
|
567
624
|
await ctx.runMutation(config.component.public.keyPatch, {
|
|
568
|
-
keyId
|
|
625
|
+
keyId,
|
|
569
626
|
data: { revoked: true },
|
|
570
627
|
});
|
|
571
628
|
},
|
|
@@ -574,164 +631,317 @@ export function Auth(config_) {
|
|
|
574
631
|
*/
|
|
575
632
|
remove: async (ctx, keyId) => {
|
|
576
633
|
await ctx.runMutation(config.component.public.keyDelete, {
|
|
577
|
-
keyId
|
|
634
|
+
keyId,
|
|
578
635
|
});
|
|
579
636
|
},
|
|
580
637
|
},
|
|
581
638
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
* ```ts
|
|
585
|
-
* import { httpRouter } from "convex/server";
|
|
586
|
-
* import { auth } from "./auth.js";
|
|
587
|
-
*
|
|
588
|
-
* const http = httpRouter();
|
|
589
|
-
*
|
|
590
|
-
* auth.addHttpRoutes(http);
|
|
591
|
-
*
|
|
592
|
-
* export default http;
|
|
593
|
-
* ```
|
|
594
|
-
*
|
|
595
|
-
* The following routes are handled always:
|
|
596
|
-
*
|
|
597
|
-
* - `/.well-known/openid-configuration`
|
|
598
|
-
* - `/.well-known/jwks.json`
|
|
599
|
-
*
|
|
600
|
-
* The following routes are handled if OAuth is configured:
|
|
601
|
-
*
|
|
602
|
-
* - `/api/auth/signin/*`
|
|
603
|
-
* - `/api/auth/callback/*`
|
|
604
|
-
*
|
|
605
|
-
* @param http your HTTP router
|
|
639
|
+
* HTTP namespace — route registration and Bearer-authenticated endpoints.
|
|
606
640
|
*/
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
});
|
|
636
|
-
}),
|
|
637
|
-
});
|
|
638
|
-
if (hasOAuth) {
|
|
641
|
+
http: {
|
|
642
|
+
/**
|
|
643
|
+
* Register core HTTP routes for JWT verification and OAuth sign-in.
|
|
644
|
+
*
|
|
645
|
+
* ```ts
|
|
646
|
+
* import { httpRouter } from "convex/server";
|
|
647
|
+
* import { auth } from "./auth.js";
|
|
648
|
+
*
|
|
649
|
+
* const http = httpRouter();
|
|
650
|
+
*
|
|
651
|
+
* auth.http.add(http);
|
|
652
|
+
*
|
|
653
|
+
* export default http;
|
|
654
|
+
* ```
|
|
655
|
+
*
|
|
656
|
+
* The following routes are handled always:
|
|
657
|
+
*
|
|
658
|
+
* - `/.well-known/openid-configuration`
|
|
659
|
+
* - `/.well-known/jwks.json`
|
|
660
|
+
*
|
|
661
|
+
* The following routes are handled if OAuth is configured:
|
|
662
|
+
*
|
|
663
|
+
* - `/api/auth/signin/*`
|
|
664
|
+
* - `/api/auth/callback/*`
|
|
665
|
+
*
|
|
666
|
+
* @param http your HTTP router
|
|
667
|
+
*/
|
|
668
|
+
add: (http) => {
|
|
639
669
|
http.route({
|
|
640
|
-
|
|
670
|
+
path: "/.well-known/openid-configuration",
|
|
641
671
|
method: "GET",
|
|
642
|
-
handler: httpActionGeneric(
|
|
672
|
+
handler: httpActionGeneric(async () => {
|
|
673
|
+
return new Response(JSON.stringify({
|
|
674
|
+
issuer: requireEnv("CONVEX_SITE_URL"),
|
|
675
|
+
jwks_uri: requireEnv("CONVEX_SITE_URL") + "/.well-known/jwks.json",
|
|
676
|
+
authorization_endpoint: requireEnv("CONVEX_SITE_URL") + "/oauth/authorize",
|
|
677
|
+
}), {
|
|
678
|
+
status: 200,
|
|
679
|
+
headers: {
|
|
680
|
+
"Content-Type": "application/json",
|
|
681
|
+
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
http.route({
|
|
687
|
+
path: "/.well-known/jwks.json",
|
|
688
|
+
method: "GET",
|
|
689
|
+
handler: httpActionGeneric(async () => {
|
|
690
|
+
return new Response(requireEnv("JWKS"), {
|
|
691
|
+
status: 200,
|
|
692
|
+
headers: {
|
|
693
|
+
"Content-Type": "application/json",
|
|
694
|
+
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
}),
|
|
698
|
+
});
|
|
699
|
+
if (hasOAuth) {
|
|
700
|
+
http.route({
|
|
701
|
+
pathPrefix: "/api/auth/signin/",
|
|
702
|
+
method: "GET",
|
|
703
|
+
handler: httpActionGeneric(convertErrorsToResponse(400, async (ctx, request) => {
|
|
704
|
+
const url = new URL(request.url);
|
|
705
|
+
const pathParts = url.pathname.split("/");
|
|
706
|
+
const providerId = pathParts.at(-1);
|
|
707
|
+
if (providerId === null) {
|
|
708
|
+
throwAuthError("OAUTH_MISSING_PROVIDER");
|
|
709
|
+
}
|
|
710
|
+
const verifier = url.searchParams.get("code");
|
|
711
|
+
if (verifier === null) {
|
|
712
|
+
throwAuthError("OAUTH_MISSING_VERIFIER");
|
|
713
|
+
}
|
|
714
|
+
const provider = getProviderOrThrow(providerId);
|
|
715
|
+
const { redirect, cookies, signature } = await getAuthorizationUrl({
|
|
716
|
+
provider: await oAuthConfigToInternalProvider(provider),
|
|
717
|
+
cookies: defaultCookiesOptions(providerId),
|
|
718
|
+
});
|
|
719
|
+
await callVerifierSignature(ctx, {
|
|
720
|
+
verifier,
|
|
721
|
+
signature,
|
|
722
|
+
});
|
|
723
|
+
const redirectTo = url.searchParams.get("redirectTo");
|
|
724
|
+
if (redirectTo !== null) {
|
|
725
|
+
cookies.push(redirectToParamCookie(providerId, redirectTo));
|
|
726
|
+
}
|
|
727
|
+
const headers = new Headers({ Location: redirect });
|
|
728
|
+
for (const { name, value, options } of cookies) {
|
|
729
|
+
headers.append("Set-Cookie", serializeCookie(name, value, options));
|
|
730
|
+
}
|
|
731
|
+
return new Response(null, { status: 302, headers });
|
|
732
|
+
})),
|
|
733
|
+
});
|
|
734
|
+
const callbackAction = httpActionGeneric(async (genericCtx, request) => {
|
|
735
|
+
const ctx = genericCtx;
|
|
643
736
|
const url = new URL(request.url);
|
|
644
737
|
const pathParts = url.pathname.split("/");
|
|
645
738
|
const providerId = pathParts.at(-1);
|
|
646
|
-
|
|
647
|
-
throw new Error("Missing provider id");
|
|
648
|
-
}
|
|
649
|
-
const verifier = url.searchParams.get("code");
|
|
650
|
-
if (verifier === null) {
|
|
651
|
-
throw new Error("Missing sign-in verifier");
|
|
652
|
-
}
|
|
739
|
+
logWithLevel(LOG_LEVELS.DEBUG, "Handling OAuth callback for provider:", providerId);
|
|
653
740
|
const provider = getProviderOrThrow(providerId);
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
await callVerifierSignature(ctx, {
|
|
659
|
-
verifier,
|
|
660
|
-
signature,
|
|
741
|
+
const cookies = getCookies(request);
|
|
742
|
+
const maybeRedirectTo = useRedirectToParam(provider.id, cookies);
|
|
743
|
+
const destinationUrl = await redirectAbsoluteUrl(config, {
|
|
744
|
+
redirectTo: maybeRedirectTo?.redirectTo,
|
|
661
745
|
});
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
746
|
+
const params = url.searchParams;
|
|
747
|
+
// Handle OAuth providers that use formData (such as Apple)
|
|
748
|
+
if (request.headers.get("Content-Type") ===
|
|
749
|
+
"application/x-www-form-urlencoded") {
|
|
750
|
+
const formData = await request.formData();
|
|
751
|
+
for (const [key, value] of formData.entries()) {
|
|
752
|
+
if (typeof value === "string") {
|
|
753
|
+
params.append(key, value);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const { profile, tokens, signature } = await handleOAuth(Object.fromEntries(params.entries()), cookies, {
|
|
759
|
+
provider: await oAuthConfigToInternalProvider(provider),
|
|
760
|
+
cookies: defaultCookiesOptions(provider.id),
|
|
761
|
+
});
|
|
762
|
+
const { id, ...profileFromCallback } = await provider.profile(profile, tokens);
|
|
763
|
+
if (typeof id !== "string") {
|
|
764
|
+
throwAuthError("OAUTH_INVALID_PROFILE", `The profile method of the ${providerId} config must return a string ID`, { provider: providerId });
|
|
765
|
+
}
|
|
766
|
+
const verificationCode = await callUserOAuth(ctx, {
|
|
767
|
+
provider: providerId,
|
|
768
|
+
providerAccountId: id,
|
|
769
|
+
profile: profileFromCallback,
|
|
770
|
+
signature,
|
|
771
|
+
});
|
|
772
|
+
return new Response(null, {
|
|
773
|
+
status: 302,
|
|
774
|
+
headers: {
|
|
775
|
+
Location: setURLSearchParam(destinationUrl, "code", verificationCode),
|
|
776
|
+
"Cache-Control": "must-revalidate",
|
|
777
|
+
},
|
|
778
|
+
});
|
|
665
779
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
780
|
+
catch (error) {
|
|
781
|
+
logError(error);
|
|
782
|
+
return Response.redirect(destinationUrl);
|
|
669
783
|
}
|
|
670
|
-
|
|
671
|
-
|
|
784
|
+
});
|
|
785
|
+
http.route({
|
|
786
|
+
pathPrefix: "/api/auth/callback/",
|
|
787
|
+
method: "GET",
|
|
788
|
+
handler: callbackAction,
|
|
789
|
+
});
|
|
790
|
+
http.route({
|
|
791
|
+
pathPrefix: "/api/auth/callback/",
|
|
792
|
+
method: "POST",
|
|
793
|
+
handler: callbackAction,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
/**
|
|
798
|
+
* Wrap an HTTP action handler with Bearer token authentication.
|
|
799
|
+
*
|
|
800
|
+
* Extracts the `Authorization: Bearer <key>` header, verifies the
|
|
801
|
+
* API key via `auth.key.verify()`, and injects `ctx.key` with the
|
|
802
|
+
* verified key info. Returns structured JSON error responses for
|
|
803
|
+
* missing/invalid/revoked/expired/rate-limited keys.
|
|
804
|
+
*
|
|
805
|
+
* If the handler returns a plain object, it is auto-wrapped in a
|
|
806
|
+
* `200 JSON` response. If it returns a `Response`, CORS headers
|
|
807
|
+
* are merged and the response is passed through.
|
|
808
|
+
*
|
|
809
|
+
* ```ts
|
|
810
|
+
* const handler = auth.http.action(async (ctx, request) => {
|
|
811
|
+
* const data = await ctx.runQuery(api.data.get, { userId: ctx.key.userId });
|
|
812
|
+
* return { data };
|
|
813
|
+
* });
|
|
814
|
+
* http.route({ path: "/api/data", method: "GET", handler });
|
|
815
|
+
* ```
|
|
816
|
+
*
|
|
817
|
+
* @param handler - Receives enriched `ctx` (with `ctx.key`) and the raw `Request`.
|
|
818
|
+
* @param options.scope - Optional scope check; returns 403 if the key lacks permission.
|
|
819
|
+
* @param options.cors - CORS config; defaults to permissive (`*`).
|
|
820
|
+
*/
|
|
821
|
+
action: (handler, options) => {
|
|
822
|
+
const corsConfig = options?.cors ?? {};
|
|
823
|
+
const corsHeaders = {
|
|
824
|
+
"Access-Control-Allow-Origin": corsConfig.origin ?? "*",
|
|
825
|
+
"Access-Control-Allow-Methods": corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
826
|
+
"Access-Control-Allow-Headers": corsConfig.headers ?? "Content-Type,Authorization",
|
|
827
|
+
};
|
|
828
|
+
const jsonError = (status, code, message) => new Response(JSON.stringify({ error: message, code }), {
|
|
829
|
+
status,
|
|
830
|
+
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
672
831
|
});
|
|
673
|
-
|
|
832
|
+
return httpActionGeneric(async (genericCtx, request) => {
|
|
674
833
|
const ctx = genericCtx;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (typeof value === "string") {
|
|
692
|
-
params.append(key, value);
|
|
834
|
+
try {
|
|
835
|
+
// 1. Extract Bearer token
|
|
836
|
+
const authHeader = request.headers.get("Authorization");
|
|
837
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
838
|
+
return jsonError(401, "MISSING_BEARER_TOKEN", "Missing or malformed Authorization: Bearer header.");
|
|
839
|
+
}
|
|
840
|
+
const rawKey = authHeader.slice(7);
|
|
841
|
+
// 2. Verify API key
|
|
842
|
+
let keyResult;
|
|
843
|
+
try {
|
|
844
|
+
keyResult = await auth.key.verify(ctx, rawKey);
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
if (isAuthError(error)) {
|
|
848
|
+
const { code, message } = error.data;
|
|
849
|
+
return jsonError(403, code, message);
|
|
693
850
|
}
|
|
851
|
+
throw error;
|
|
694
852
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
});
|
|
701
|
-
const { id, ...profileFromCallback } = await provider.profile(profile, tokens);
|
|
702
|
-
if (typeof id !== "string") {
|
|
703
|
-
throw new Error(`The profile method of the ${providerId} config must return a string ID`);
|
|
853
|
+
// 3. Optional scope check
|
|
854
|
+
if (options?.scope) {
|
|
855
|
+
if (!keyResult.scopes.can(options.scope.resource, options.scope.action)) {
|
|
856
|
+
return jsonError(403, "SCOPE_CHECK_FAILED", "This API key does not have the required permissions.");
|
|
857
|
+
}
|
|
704
858
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
return new Response(null, {
|
|
712
|
-
status: 302,
|
|
713
|
-
headers: {
|
|
714
|
-
Location: setURLSearchParam(destinationUrl, "code", verificationCode),
|
|
715
|
-
"Cache-Control": "must-revalidate",
|
|
859
|
+
// 4. Enrich context with key info
|
|
860
|
+
const enrichedCtx = Object.assign(ctx, {
|
|
861
|
+
key: {
|
|
862
|
+
userId: keyResult.userId,
|
|
863
|
+
keyId: keyResult.keyId,
|
|
864
|
+
scopes: keyResult.scopes,
|
|
716
865
|
},
|
|
717
866
|
});
|
|
867
|
+
// 5. Call handler
|
|
868
|
+
const result = await handler(enrichedCtx, request);
|
|
869
|
+
// 6. Auto-wrap plain objects as JSON responses
|
|
870
|
+
if (result instanceof Response) {
|
|
871
|
+
// Merge CORS headers into existing response
|
|
872
|
+
const headers = new Headers(result.headers);
|
|
873
|
+
for (const [k, val] of Object.entries(corsHeaders)) {
|
|
874
|
+
if (!headers.has(k))
|
|
875
|
+
headers.set(k, val);
|
|
876
|
+
}
|
|
877
|
+
return new Response(result.body, {
|
|
878
|
+
status: result.status,
|
|
879
|
+
statusText: result.statusText,
|
|
880
|
+
headers,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
return new Response(JSON.stringify(result), {
|
|
884
|
+
status: 200,
|
|
885
|
+
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
886
|
+
});
|
|
718
887
|
}
|
|
719
888
|
catch (error) {
|
|
720
889
|
logError(error);
|
|
721
|
-
return
|
|
890
|
+
return jsonError(500, "INTERNAL_ERROR", "An unexpected error occurred.");
|
|
722
891
|
}
|
|
723
892
|
});
|
|
893
|
+
},
|
|
894
|
+
/**
|
|
895
|
+
* Register a Bearer-authenticated route **and** its OPTIONS preflight
|
|
896
|
+
* in a single call.
|
|
897
|
+
*
|
|
898
|
+
* ```ts
|
|
899
|
+
* auth.http.route(http, {
|
|
900
|
+
* path: "/api/messages",
|
|
901
|
+
* method: "POST",
|
|
902
|
+
* handler: async (ctx, request) => {
|
|
903
|
+
* const { body } = await request.json();
|
|
904
|
+
* await ctx.runMutation(internal.messages.sendAsUser, {
|
|
905
|
+
* userId: ctx.key.userId,
|
|
906
|
+
* body,
|
|
907
|
+
* });
|
|
908
|
+
* return { success: true };
|
|
909
|
+
* },
|
|
910
|
+
* });
|
|
911
|
+
* ```
|
|
912
|
+
*
|
|
913
|
+
* @param http - The Convex HTTP router.
|
|
914
|
+
* @param routeConfig.path - The URL path to match.
|
|
915
|
+
* @param routeConfig.method - HTTP method (GET, POST, PUT, PATCH, DELETE).
|
|
916
|
+
* @param routeConfig.handler - Receives enriched `ctx` (with `ctx.key`) and the raw `Request`.
|
|
917
|
+
* @param routeConfig.scope - Optional scope check; returns 403 if the key lacks permission.
|
|
918
|
+
* @param routeConfig.cors - CORS config; defaults to permissive (`*`).
|
|
919
|
+
*/
|
|
920
|
+
route: (http, routeConfig) => {
|
|
921
|
+
const corsConfig = routeConfig.cors ?? {};
|
|
922
|
+
const corsHeaders = {
|
|
923
|
+
"Access-Control-Allow-Origin": corsConfig.origin ?? "*",
|
|
924
|
+
"Access-Control-Allow-Methods": corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
925
|
+
"Access-Control-Allow-Headers": corsConfig.headers ?? "Content-Type,Authorization",
|
|
926
|
+
};
|
|
927
|
+
// Register OPTIONS preflight
|
|
724
928
|
http.route({
|
|
725
|
-
|
|
726
|
-
method: "
|
|
727
|
-
handler:
|
|
929
|
+
path: routeConfig.path,
|
|
930
|
+
method: "OPTIONS",
|
|
931
|
+
handler: httpActionGeneric(async () => {
|
|
932
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
933
|
+
}),
|
|
728
934
|
});
|
|
935
|
+
// Register the main route with Bearer auth wrapping
|
|
729
936
|
http.route({
|
|
730
|
-
|
|
731
|
-
method:
|
|
732
|
-
handler:
|
|
937
|
+
path: routeConfig.path,
|
|
938
|
+
method: routeConfig.method,
|
|
939
|
+
handler: auth.http.action(routeConfig.handler, {
|
|
940
|
+
scope: routeConfig.scope,
|
|
941
|
+
cors: routeConfig.cors,
|
|
942
|
+
}),
|
|
733
943
|
});
|
|
734
|
-
}
|
|
944
|
+
},
|
|
735
945
|
},
|
|
736
946
|
};
|
|
737
947
|
const enrichCtx = (ctx) => ({
|
|
@@ -789,7 +999,7 @@ export function Auth(config_) {
|
|
|
789
999
|
return { totpSetup: { uri: result.uri, secret: result.secret, totpId: result.totpId }, verifier: result.verifier };
|
|
790
1000
|
default: {
|
|
791
1001
|
const _typecheck = result;
|
|
792
|
-
|
|
1002
|
+
throwAuthError("INTERNAL_ERROR", `Unexpected result from signIn, ${String(result)}`);
|
|
793
1003
|
}
|
|
794
1004
|
}
|
|
795
1005
|
},
|
|
@@ -821,10 +1031,16 @@ function convertErrorsToResponse(errorStatusCode, action) {
|
|
|
821
1031
|
return await action(ctx, request);
|
|
822
1032
|
}
|
|
823
1033
|
catch (error) {
|
|
824
|
-
if (error
|
|
1034
|
+
if (isAuthError(error)) {
|
|
1035
|
+
return new Response(JSON.stringify({ code: error.data.code, message: error.data.message }), {
|
|
1036
|
+
status: errorStatusCode,
|
|
1037
|
+
headers: { "Content-Type": "application/json" },
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
else if (error instanceof ConvexError) {
|
|
825
1041
|
return new Response(null, {
|
|
826
1042
|
status: errorStatusCode,
|
|
827
|
-
statusText: error.data,
|
|
1043
|
+
statusText: typeof error.data === "string" ? error.data : "Error",
|
|
828
1044
|
});
|
|
829
1045
|
}
|
|
830
1046
|
else {
|