@lupinum/better-convex-nuxt 0.8.0-beta.40
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/LICENSE +21 -0
- package/LICENSES/Apache-2.0.txt +201 -0
- package/README.md +151 -0
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/module.d.mts +55 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +309 -0
- package/dist/runtime/auth/auth-identity.d.ts +31 -0
- package/dist/runtime/auth/auth-identity.js +21 -0
- package/dist/runtime/auth/better-auth-browser-adapter.d.ts +33 -0
- package/dist/runtime/auth/better-auth-browser-adapter.js +215 -0
- package/dist/runtime/auth/client-engine-types.d.ts +4 -0
- package/dist/runtime/auth/client-engine-types.js +0 -0
- package/dist/runtime/auth/integrated-client.d.ts +25 -0
- package/dist/runtime/auth/integrated-client.js +132 -0
- package/dist/runtime/auth/operation-tracker.d.ts +7 -0
- package/dist/runtime/auth/operation-tracker.js +14 -0
- package/dist/runtime/auth/session-synchronization.d.ts +23 -0
- package/dist/runtime/auth/session-synchronization.js +156 -0
- package/dist/runtime/auth/token-fetcher.d.ts +66 -0
- package/dist/runtime/auth/token-fetcher.js +131 -0
- package/dist/runtime/auth/validate-auth-client-definition.d.ts +12 -0
- package/dist/runtime/auth/validate-auth-client-definition.js +47 -0
- package/dist/runtime/auth-client/convex-client-plugin.d.ts +31 -0
- package/dist/runtime/auth-client/convex-client-plugin.js +14 -0
- package/dist/runtime/auth-client/definition-types.d.ts +6 -0
- package/dist/runtime/auth-client/definition-types.js +0 -0
- package/dist/runtime/auth-client/empty-definition.d.ts +2 -0
- package/dist/runtime/auth-client/empty-definition.js +2 -0
- package/dist/runtime/auth-client/index.d.ts +71 -0
- package/dist/runtime/auth-client/index.js +3 -0
- package/dist/runtime/cli/auth-schema.d.ts +2 -0
- package/dist/runtime/cli/auth-schema.js +169 -0
- package/dist/runtime/cli/convex.d.ts +11 -0
- package/dist/runtime/cli/convex.js +274 -0
- package/dist/runtime/composables/useConvex.d.ts +4 -0
- package/dist/runtime/composables/useConvex.js +9 -0
- package/dist/runtime/composables/useConvexAction.d.ts +4 -0
- package/dist/runtime/composables/useConvexAction.js +18 -0
- package/dist/runtime/composables/useConvexAttachment.d.ts +9 -0
- package/dist/runtime/composables/useConvexAttachment.js +14 -0
- package/dist/runtime/composables/useConvexAuth.d.ts +10 -0
- package/dist/runtime/composables/useConvexAuth.js +37 -0
- package/dist/runtime/composables/useConvexConfig.d.ts +12 -0
- package/dist/runtime/composables/useConvexConfig.js +7 -0
- package/dist/runtime/composables/useConvexConnectionState.d.ts +33 -0
- package/dist/runtime/composables/useConvexConnectionState.js +24 -0
- package/dist/runtime/composables/useConvexFileUpload.d.ts +169 -0
- package/dist/runtime/composables/useConvexFileUpload.js +198 -0
- package/dist/runtime/composables/useConvexMutation.d.ts +13 -0
- package/dist/runtime/composables/useConvexMutation.js +24 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +13 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.js +256 -0
- package/dist/runtime/composables/useConvexQuery.d.ts +19 -0
- package/dist/runtime/composables/useConvexQuery.js +308 -0
- package/dist/runtime/convex-auth/adapter/create-adapter.d.ts +33 -0
- package/dist/runtime/convex-auth/adapter/create-adapter.js +308 -0
- package/dist/runtime/convex-auth/adapter/define-functions.d.ts +160 -0
- package/dist/runtime/convex-auth/adapter/define-functions.js +497 -0
- package/dist/runtime/convex-auth/adapter/generate-schema.d.ts +16 -0
- package/dist/runtime/convex-auth/adapter/generate-schema.js +332 -0
- package/dist/runtime/convex-auth/adapter/metadata.d.ts +44 -0
- package/dist/runtime/convex-auth/adapter/metadata.js +91 -0
- package/dist/runtime/convex-auth/adapter/query.d.ts +32 -0
- package/dist/runtime/convex-auth/adapter/query.js +275 -0
- package/dist/runtime/convex-auth/adapter/relationships.d.ts +20 -0
- package/dist/runtime/convex-auth/adapter/relationships.js +128 -0
- package/dist/runtime/convex-auth/component/_generated/api.js +5 -0
- package/dist/runtime/convex-auth/component/_generated/component.d.ts +116 -0
- package/dist/runtime/convex-auth/component/_generated/component.js +0 -0
- package/dist/runtime/convex-auth/component/_generated/dataModel.js +0 -0
- package/dist/runtime/convex-auth/component/_generated/server.js +16 -0
- package/dist/runtime/convex-auth/component/adapter.d.ts +142 -0
- package/dist/runtime/convex-auth/component/adapter.js +16 -0
- package/dist/runtime/convex-auth/component/convex.config.d.ts +2 -0
- package/dist/runtime/convex-auth/component/convex.config.js +3 -0
- package/dist/runtime/convex-auth/component/schema.d.ts +799 -0
- package/dist/runtime/convex-auth/component/schema.js +180 -0
- package/dist/runtime/convex-auth/component/schemaMetadata.d.ts +2094 -0
- package/dist/runtime/convex-auth/component/schemaMetadata.js +2161 -0
- package/dist/runtime/convex-auth/context.d.ts +5 -0
- package/dist/runtime/convex-auth/context.js +8 -0
- package/dist/runtime/convex-auth/create-auth-component.d.ts +41 -0
- package/dist/runtime/convex-auth/create-auth-component.js +157 -0
- package/dist/runtime/convex-auth/index.d.ts +11 -0
- package/dist/runtime/convex-auth/index.js +7 -0
- package/dist/runtime/convex-auth/internal-session.d.ts +2 -0
- package/dist/runtime/convex-auth/internal-session.js +1 -0
- package/dist/runtime/convex-auth/jwks-rotation.d.ts +58 -0
- package/dist/runtime/convex-auth/jwks-rotation.js +163 -0
- package/dist/runtime/convex-auth/oauth-live-access.d.ts +17 -0
- package/dist/runtime/convex-auth/oauth-live-access.js +78 -0
- package/dist/runtime/convex-auth/oauth-provider-compat.d.ts +10 -0
- package/dist/runtime/convex-auth/oauth-provider-compat.js +170 -0
- package/dist/runtime/convex-auth/oauth-resource.d.ts +39 -0
- package/dist/runtime/convex-auth/oauth-resource.js +118 -0
- package/dist/runtime/convex-auth/oauth-security.d.ts +82 -0
- package/dist/runtime/convex-auth/oauth-security.js +454 -0
- package/dist/runtime/convex-auth/origin.d.ts +4 -0
- package/dist/runtime/convex-auth/origin.js +9 -0
- package/dist/runtime/convex-auth/plugin.d.ts +18 -0
- package/dist/runtime/convex-auth/plugin.js +605 -0
- package/dist/runtime/convex-auth/provider.d.ts +2 -0
- package/dist/runtime/convex-auth/provider.js +11 -0
- package/dist/runtime/convex-auth/test.d.ts +410 -0
- package/dist/runtime/convex-auth/test.js +9 -0
- package/dist/runtime/convex-auth/types.d.ts +18 -0
- package/dist/runtime/convex-auth/types.js +0 -0
- package/dist/runtime/convex-auth/user-projection.d.ts +106 -0
- package/dist/runtime/convex-auth/user-projection.js +128 -0
- package/dist/runtime/devtools/auth-proxy-registry.d.ts +4 -0
- package/dist/runtime/devtools/auth-proxy-registry.js +47 -0
- package/dist/runtime/devtools/bridge-setup.d.ts +21 -0
- package/dist/runtime/devtools/bridge-setup.js +165 -0
- package/dist/runtime/devtools/path-utils.d.ts +2 -0
- package/dist/runtime/devtools/path-utils.js +12 -0
- package/dist/runtime/devtools/server.d.ts +2 -0
- package/dist/runtime/devtools/server.js +108 -0
- package/dist/runtime/devtools/setup-client.d.ts +16 -0
- package/dist/runtime/devtools/setup-client.js +43 -0
- package/dist/runtime/devtools/sink.d.ts +18 -0
- package/dist/runtime/devtools/sink.js +110 -0
- package/dist/runtime/devtools/transport.d.ts +16 -0
- package/dist/runtime/devtools/transport.js +149 -0
- package/dist/runtime/devtools/types.d.ts +167 -0
- package/dist/runtime/devtools/types.js +1 -0
- package/dist/runtime/devtools/ui/dist/200.html +1 -0
- package/dist/runtime/devtools/ui/dist/404.html +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/C4MASuCh.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DJHkqtZq.js +3 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DWwrCltl.js +2 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/DZ_UiYoH.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/_m9ujb-i.js +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/latest.json +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/better-convex-nuxt-devtools.json +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/entry.DpFDKYZ5.css +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/error-404.DYUJo5xt.css +1 -0
- package/dist/runtime/devtools/ui/dist/_nuxt/error-500.DQdoJJXg.css +1 -0
- package/dist/runtime/devtools/ui/dist/convex-logo.svg +5 -0
- package/dist/runtime/devtools/ui/dist/index.html +1 -0
- package/dist/runtime/errors/index.d.ts +7 -0
- package/dist/runtime/errors/index.js +5 -0
- package/dist/runtime/middleware/convex-auth.global.d.ts +2 -0
- package/dist/runtime/middleware/convex-auth.global.js +31 -0
- package/dist/runtime/plugin.auth.client.d.ts +3 -0
- package/dist/runtime/plugin.auth.client.js +217 -0
- package/dist/runtime/plugin.client.d.ts +3 -0
- package/dist/runtime/plugin.client.js +43 -0
- package/dist/runtime/plugin.server.d.ts +12 -0
- package/dist/runtime/plugin.server.js +98 -0
- package/dist/runtime/plugins/convex-call-error-payload.d.ts +14 -0
- package/dist/runtime/plugins/convex-call-error-payload.js +12 -0
- package/dist/runtime/runtime-context.d.ts +26 -0
- package/dist/runtime/runtime-context.js +48 -0
- package/dist/runtime/server/api/auth/[...].d.ts +10 -0
- package/dist/runtime/server/api/auth/[...].js +509 -0
- package/dist/runtime/server/api/auth/authorization-server-metadata.d.ts +2 -0
- package/dist/runtime/server/api/auth/authorization-server-metadata.js +6 -0
- package/dist/runtime/server/api/auth/body-size.d.ts +15 -0
- package/dist/runtime/server/api/auth/body-size.js +179 -0
- package/dist/runtime/server/api/auth/headers.d.ts +6 -0
- package/dist/runtime/server/api/auth/headers.js +98 -0
- package/dist/runtime/server/api/auth/jwks.d.ts +2 -0
- package/dist/runtime/server/api/auth/jwks.js +6 -0
- package/dist/runtime/server/api/auth/security.d.ts +14 -0
- package/dist/runtime/server/api/auth/security.js +85 -0
- package/dist/runtime/server/api/release-fingerprint.d.ts +2 -0
- package/dist/runtime/server/api/release-fingerprint.js +36 -0
- package/dist/runtime/server/index.d.ts +1 -0
- package/dist/runtime/server/index.js +3 -0
- package/dist/runtime/server/plugins/runtime-config.d.ts +7 -0
- package/dist/runtime/server/plugins/runtime-config.js +7 -0
- package/dist/runtime/server/utils/auth-snapshot.d.ts +26 -0
- package/dist/runtime/server/utils/auth-snapshot.js +165 -0
- package/dist/runtime/server/utils/http.d.ts +16 -0
- package/dist/runtime/server/utils/http.js +133 -0
- package/dist/runtime/server/utils/server-convex-caller.d.ts +31 -0
- package/dist/runtime/server/utils/server-convex-caller.js +151 -0
- package/dist/runtime/server/utils/server-convex-options.d.ts +83 -0
- package/dist/runtime/server/utils/server-convex-options.js +69 -0
- package/dist/runtime/server/utils/signed-client-ip.d.ts +6 -0
- package/dist/runtime/server/utils/signed-client-ip.js +29 -0
- package/dist/runtime/server/utils/ssr-auth-headers.d.ts +26 -0
- package/dist/runtime/server/utils/ssr-auth-headers.js +49 -0
- package/dist/runtime/server/utils/token-exchange.d.ts +27 -0
- package/dist/runtime/server/utils/token-exchange.js +96 -0
- package/dist/runtime/shared/auth-cookie.d.ts +4 -0
- package/dist/runtime/shared/auth-cookie.js +25 -0
- package/dist/runtime/shared/auth-origin.d.ts +15 -0
- package/dist/runtime/shared/auth-origin.js +42 -0
- package/dist/runtime/shared/bounded-stream.d.ts +2 -0
- package/dist/runtime/shared/bounded-stream.js +50 -0
- package/dist/runtime/shared/client-ip.d.ts +21 -0
- package/dist/runtime/shared/client-ip.js +123 -0
- package/dist/runtime/shared/release-fingerprint.d.ts +5 -0
- package/dist/runtime/shared/release-fingerprint.js +17 -0
- package/dist/runtime/types.d.ts +19 -0
- package/dist/runtime/utils/auth-config.d.ts +24 -0
- package/dist/runtime/utils/auth-config.js +45 -0
- package/dist/runtime/utils/auth-contract.d.ts +22 -0
- package/dist/runtime/utils/auth-contract.js +0 -0
- package/dist/runtime/utils/auth-errors.d.ts +5 -0
- package/dist/runtime/utils/auth-errors.js +22 -0
- package/dist/runtime/utils/auth-identity-state.d.ts +4 -0
- package/dist/runtime/utils/auth-identity-state.js +8 -0
- package/dist/runtime/utils/auth-pending-state.d.ts +13 -0
- package/dist/runtime/utils/auth-pending-state.js +4 -0
- package/dist/runtime/utils/auth-pending.d.ts +10 -0
- package/dist/runtime/utils/auth-pending.js +30 -0
- package/dist/runtime/utils/auth-route-protection.d.ts +16 -0
- package/dist/runtime/utils/auth-route-protection.js +58 -0
- package/dist/runtime/utils/auth-status.d.ts +47 -0
- package/dist/runtime/utils/auth-status.js +8 -0
- package/dist/runtime/utils/bounded-convex-fetch.d.ts +18 -0
- package/dist/runtime/utils/bounded-convex-fetch.js +152 -0
- package/dist/runtime/utils/callable-devtools.d.ts +17 -0
- package/dist/runtime/utils/callable-devtools.js +49 -0
- package/dist/runtime/utils/callable-observer.d.ts +2 -0
- package/dist/runtime/utils/callable-observer.js +1 -0
- package/dist/runtime/utils/composable-scope.d.ts +2 -0
- package/dist/runtime/utils/composable-scope.js +8 -0
- package/dist/runtime/utils/config-defaults.d.ts +8 -0
- package/dist/runtime/utils/config-defaults.js +8 -0
- package/dist/runtime/utils/convex-cache.d.ts +68 -0
- package/dist/runtime/utils/convex-cache.js +47 -0
- package/dist/runtime/utils/convex-config.d.ts +16 -0
- package/dist/runtime/utils/convex-config.js +50 -0
- package/dist/runtime/utils/convex-shared.d.ts +67 -0
- package/dist/runtime/utils/convex-shared.js +134 -0
- package/dist/runtime/utils/identity-changed-error.d.ts +4 -0
- package/dist/runtime/utils/identity-changed-error.js +12 -0
- package/dist/runtime/utils/identity-key.d.ts +19 -0
- package/dist/runtime/utils/identity-key.js +10 -0
- package/dist/runtime/utils/integrated-auth-client.d.ts +5 -0
- package/dist/runtime/utils/integrated-auth-client.js +0 -0
- package/dist/runtime/utils/logger.d.ts +42 -0
- package/dist/runtime/utils/logger.js +190 -0
- package/dist/runtime/utils/mime-type.d.ts +33 -0
- package/dist/runtime/utils/mime-type.js +10 -0
- package/dist/runtime/utils/query-execution-gate.d.ts +47 -0
- package/dist/runtime/utils/query-execution-gate.js +63 -0
- package/dist/runtime/utils/query-execution.d.ts +8 -0
- package/dist/runtime/utils/query-execution.js +19 -0
- package/dist/runtime/utils/query-foundation.d.ts +20 -0
- package/dist/runtime/utils/query-foundation.js +39 -0
- package/dist/runtime/utils/query-state.d.ts +12 -0
- package/dist/runtime/utils/query-state.js +12 -0
- package/dist/runtime/utils/reactive-args.d.ts +5 -0
- package/dist/runtime/utils/reactive-args.js +42 -0
- package/dist/runtime/utils/runtime-config-normalize.d.ts +22 -0
- package/dist/runtime/utils/runtime-config-normalize.js +28 -0
- package/dist/runtime/utils/runtime-config.d.ts +4 -0
- package/dist/runtime/utils/runtime-config.js +8 -0
- package/dist/runtime/utils/sanitize-diagnostic.d.ts +6 -0
- package/dist/runtime/utils/sanitize-diagnostic.js +81 -0
- package/dist/runtime/utils/shared-helpers.d.ts +46 -0
- package/dist/runtime/utils/shared-helpers.js +139 -0
- package/dist/runtime/utils/site-url.d.ts +4 -0
- package/dist/runtime/utils/site-url.js +15 -0
- package/dist/runtime/utils/ssr-pagination-state.d.ts +3 -0
- package/dist/runtime/utils/ssr-pagination-state.js +3 -0
- package/dist/runtime/utils/types.d.ts +29 -0
- package/dist/runtime/utils/types.js +0 -0
- package/dist/runtime/utils/upload-core.d.ts +16 -0
- package/dist/runtime/utils/upload-core.js +116 -0
- package/dist/types.d.mts +21 -0
- package/package.json +253 -0
- package/security/upstream-convex-better-auth.json +477 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import { oauthAuthorizationServerMetadata } from "@better-auth/oauth-provider";
|
|
2
|
+
import {
|
|
3
|
+
APIError,
|
|
4
|
+
createAuthEndpoint,
|
|
5
|
+
createAuthMiddleware,
|
|
6
|
+
sessionMiddleware
|
|
7
|
+
} from "better-auth/api";
|
|
8
|
+
import { decodeBasicCredentials } from "better-auth/oauth2";
|
|
9
|
+
import { bearer } from "better-auth/plugins";
|
|
10
|
+
import { hasBetterAuthCookie } from "../shared/auth-cookie.js";
|
|
11
|
+
import { VERIFIED_CLIENT_IP_HEADER } from "../shared/client-ip.js";
|
|
12
|
+
import { INTERNAL_SESSION_HEADER } from "./internal-session.js";
|
|
13
|
+
import {
|
|
14
|
+
assertSupportedJwksOptions,
|
|
15
|
+
createPublicJwksResponse,
|
|
16
|
+
rejectImplicitSigningKeyCreation,
|
|
17
|
+
sanitizeStoredJwk
|
|
18
|
+
} from "./jwks-rotation.js";
|
|
19
|
+
import {
|
|
20
|
+
assertSafePinnedClientProvisioning,
|
|
21
|
+
assertSafePinnedClientUpdate,
|
|
22
|
+
assertSafePinnedResourceProvisioning,
|
|
23
|
+
validatePinnedOAuthProviderRuntime
|
|
24
|
+
} from "./oauth-provider-compat.js";
|
|
25
|
+
import {
|
|
26
|
+
OAuthSecurityError,
|
|
27
|
+
assertSafeStoredOAuthClient,
|
|
28
|
+
assertSafeStoredOAuthClientResource,
|
|
29
|
+
assertSafeStoredOAuthResource,
|
|
30
|
+
hardenOAuthProviderCallbacks,
|
|
31
|
+
installUrlCanParseCompatibility,
|
|
32
|
+
parseBoundedFormBody,
|
|
33
|
+
parseBoundedFormRequest,
|
|
34
|
+
projectOAuthAuthorizationServerMetadata,
|
|
35
|
+
requireSingleParameter,
|
|
36
|
+
validateOAuthProviderProfile
|
|
37
|
+
} from "./oauth-security.js";
|
|
38
|
+
import { normalizeAuthOrigin } from "./origin.js";
|
|
39
|
+
const forbiddenCustomClaims = /* @__PURE__ */ new Set(["aud", "exp", "iat", "iss", "jti", "nbf", "sub"]);
|
|
40
|
+
const TOKEN_FIELDS = [
|
|
41
|
+
"client_assertion",
|
|
42
|
+
"client_assertion_type",
|
|
43
|
+
"client_id",
|
|
44
|
+
"client_secret",
|
|
45
|
+
"code",
|
|
46
|
+
"code_verifier",
|
|
47
|
+
"grant_type",
|
|
48
|
+
"redirect_uri",
|
|
49
|
+
"refresh_token",
|
|
50
|
+
"resource",
|
|
51
|
+
"scope"
|
|
52
|
+
];
|
|
53
|
+
const REVOKE_FIELDS = [
|
|
54
|
+
"client_assertion",
|
|
55
|
+
"client_assertion_type",
|
|
56
|
+
"client_id",
|
|
57
|
+
"client_secret",
|
|
58
|
+
"token",
|
|
59
|
+
"token_type_hint"
|
|
60
|
+
];
|
|
61
|
+
const CODE_VERIFIER_PATTERN = /^[\w.~-]{43,128}$/u;
|
|
62
|
+
const AUTHORIZE_BODY_MAX_BYTES = 8 * 1024;
|
|
63
|
+
const sharedJwksReader = async (endpointContext) => {
|
|
64
|
+
const rows = await endpointContext.context.adapter.findMany({
|
|
65
|
+
model: "jwks"
|
|
66
|
+
});
|
|
67
|
+
return rows.map((row) => sanitizeStoredJwk(row));
|
|
68
|
+
};
|
|
69
|
+
function configureSharedJwks(context, errorCode) {
|
|
70
|
+
const configuredPlugins = context.options.plugins ?? [];
|
|
71
|
+
const jwtIndexes = configuredPlugins.map((plugin, index) => plugin.id === "jwt" ? index : -1).filter((index) => index >= 0);
|
|
72
|
+
const convexIndexes = configuredPlugins.map((plugin, index) => plugin.id === "@lupinum/better-convex-nuxt" ? index : -1).filter((index) => index >= 0);
|
|
73
|
+
if (jwtIndexes.length !== 1 || convexIndexes.length !== 1 || jwtIndexes[0] >= convexIndexes[0]) {
|
|
74
|
+
throw new Error(errorCode);
|
|
75
|
+
}
|
|
76
|
+
const jwtPlugin = configuredPlugins[jwtIndexes[0]];
|
|
77
|
+
const jwtOptions = jwtPlugin.options;
|
|
78
|
+
assertSupportedJwksOptions(jwtOptions, errorCode);
|
|
79
|
+
if (!jwtOptions || jwtOptions.adapter?.getJwks !== void 0 && jwtOptions.adapter.getJwks !== sharedJwksReader || jwtOptions.jwt?.audience !== context.baseURL || jwtOptions.jwt.expirationTime !== "10m" || jwtOptions.jwt.issuer !== context.baseURL) {
|
|
80
|
+
throw new Error(errorCode);
|
|
81
|
+
}
|
|
82
|
+
jwtOptions.adapter = {
|
|
83
|
+
createJwk: rejectImplicitSigningKeyCreation,
|
|
84
|
+
getJwks: sharedJwksReader
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const officialBearerBefore = bearer().hooks.before[0];
|
|
88
|
+
const internalSessionBearerBefore = {
|
|
89
|
+
matcher: (context) => {
|
|
90
|
+
const headers = context.request?.headers ?? context.headers;
|
|
91
|
+
return headers?.get(INTERNAL_SESSION_HEADER) === "1" && officialBearerBefore.matcher(context);
|
|
92
|
+
},
|
|
93
|
+
handler: officialBearerBefore.handler
|
|
94
|
+
};
|
|
95
|
+
const INVALID_SESSION_MESSAGE = "AUTH_SESSION_INVALID";
|
|
96
|
+
function unauthorized() {
|
|
97
|
+
throw new APIError("UNAUTHORIZED", { message: INVALID_SESSION_MESSAGE });
|
|
98
|
+
}
|
|
99
|
+
function unauthorizedResponse() {
|
|
100
|
+
return Response.json(
|
|
101
|
+
{ code: "UNAUTHORIZED", message: INVALID_SESSION_MESSAGE },
|
|
102
|
+
{ headers: { "Cache-Control": "private, no-store" }, status: 401 }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
function hasPresentedSessionCredentials(headers) {
|
|
106
|
+
if (headers.has("authorization")) return true;
|
|
107
|
+
return hasBetterAuthCookie(headers.get("cookie"));
|
|
108
|
+
}
|
|
109
|
+
function validateSessionJwt(options) {
|
|
110
|
+
const providers = options.authConfig.providers.filter(
|
|
111
|
+
(provider2) => Boolean(
|
|
112
|
+
provider2 && typeof provider2 === "object" && provider2.applicationID === "convex"
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
if (providers.length !== 1) throw new Error("AUTH_PROVIDER_INVALID");
|
|
116
|
+
const provider = providers[0];
|
|
117
|
+
if (provider.type !== "customJwt" || provider.algorithm !== "RS256" || typeof options.sessionJwt.issuer !== "string" || provider.issuer !== options.sessionJwt.issuer || options.sessionJwt.audience !== "convex") {
|
|
118
|
+
throw new Error("AUTH_SESSION_JWT_CONFIG_INVALID");
|
|
119
|
+
}
|
|
120
|
+
normalizeAuthOrigin(options.sessionJwt.issuer, "CONVEX_SITE_URL");
|
|
121
|
+
const lifetime = options.sessionJwt.expirationTime;
|
|
122
|
+
if (typeof lifetime !== "string") throw new Error("AUTH_SESSION_JWT_LIFETIME_INVALID");
|
|
123
|
+
const match = /^(\d+)(m|s)$/.exec(lifetime);
|
|
124
|
+
if (!match) throw new Error("AUTH_SESSION_JWT_LIFETIME_INVALID");
|
|
125
|
+
const seconds = Number(match[1]) * (match[2] === "m" ? 60 : 1);
|
|
126
|
+
if (!Number.isSafeInteger(seconds) || seconds <= 0 || seconds > 15 * 60) {
|
|
127
|
+
throw new Error("AUTH_SESSION_JWT_LIFETIME_INVALID");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function oauthFailure(error, status = 400) {
|
|
131
|
+
const headers = new Headers({
|
|
132
|
+
"Cache-Control": "no-store",
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
Pragma: "no-cache"
|
|
135
|
+
});
|
|
136
|
+
if (error === "invalid_client") {
|
|
137
|
+
headers.set("WWW-Authenticate", 'Basic realm="oauth2"');
|
|
138
|
+
}
|
|
139
|
+
return Response.json({ error }, { headers, status });
|
|
140
|
+
}
|
|
141
|
+
function oauthRequestPath(request, baseURL) {
|
|
142
|
+
try {
|
|
143
|
+
const requestUrl = new URL(request.url);
|
|
144
|
+
const base = new URL(baseURL);
|
|
145
|
+
if (requestUrl.origin !== base.origin) return null;
|
|
146
|
+
return requestUrl.pathname;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function loadSafeOAuthBinding(context, options, clientId, resourceId) {
|
|
152
|
+
const client = await context.adapter.findOne({
|
|
153
|
+
model: "oauthClient",
|
|
154
|
+
where: [{ field: "clientId", value: clientId }]
|
|
155
|
+
});
|
|
156
|
+
if (!client) throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
157
|
+
try {
|
|
158
|
+
assertSafeStoredOAuthClient(client, options.scopes);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error instanceof OAuthSecurityError) {
|
|
161
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
if (resourceId === void 0) return { client };
|
|
166
|
+
const resource = await context.adapter.findOne({
|
|
167
|
+
model: "oauthResource",
|
|
168
|
+
where: [{ field: "identifier", value: resourceId }]
|
|
169
|
+
});
|
|
170
|
+
const link = await context.adapter.findOne({
|
|
171
|
+
model: "oauthClientResource",
|
|
172
|
+
where: [
|
|
173
|
+
{ field: "clientId", value: clientId },
|
|
174
|
+
{ field: "resourceId", value: resourceId }
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
if (!resource || !link) throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
178
|
+
try {
|
|
179
|
+
assertSafeStoredOAuthResource(resource, options.scopes);
|
|
180
|
+
assertSafeStoredOAuthClientResource(link, clientId, resourceId);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof OAuthSecurityError) {
|
|
183
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
return { client, resource };
|
|
188
|
+
}
|
|
189
|
+
function confidentialBasicClientId(request) {
|
|
190
|
+
const authorization = request.headers.get("authorization");
|
|
191
|
+
if (!authorization || authorization.includes(",") || !/^Basic \S+$/.test(authorization)) {
|
|
192
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const credentials = decodeBasicCredentials(authorization);
|
|
196
|
+
if (!credentials.clientId || !credentials.clientSecret) {
|
|
197
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
198
|
+
}
|
|
199
|
+
return credentials.clientId;
|
|
200
|
+
} catch {
|
|
201
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function guardedClientAuthentication(request, parameters) {
|
|
205
|
+
if (parameters.has("client_secret") || parameters.has("client_assertion") || parameters.has("client_assertion_type")) {
|
|
206
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
207
|
+
}
|
|
208
|
+
const bodyClientIds = parameters.getAll("client_id");
|
|
209
|
+
const authorization = request.headers.get("authorization");
|
|
210
|
+
if (bodyClientIds.length === 1) {
|
|
211
|
+
if (bodyClientIds[0] === "" || authorization !== null) {
|
|
212
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
213
|
+
}
|
|
214
|
+
return { clientId: bodyClientIds[0], method: "none" };
|
|
215
|
+
}
|
|
216
|
+
if (bodyClientIds.length !== 0) {
|
|
217
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
clientId: confidentialBasicClientId(request),
|
|
221
|
+
method: "client_secret_basic"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function assertClientAuthenticationMethod(client, authentication) {
|
|
225
|
+
if (authentication.method === "none" && (client.public !== true || client.tokenEndpointAuthMethod !== "none" || client.clientSecret !== void 0 && client.clientSecret !== null) || authentication.method === "client_secret_basic" && (client.public !== false || client.tokenEndpointAuthMethod !== "client_secret_basic")) {
|
|
226
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CLIENT_INVALID");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function canonicalRedirectUrl(value) {
|
|
230
|
+
if (value.includes("#")) return null;
|
|
231
|
+
let url;
|
|
232
|
+
try {
|
|
233
|
+
url = new URL(value);
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (url.href !== value || url.username || url.password || url.hash) return null;
|
|
238
|
+
return url;
|
|
239
|
+
}
|
|
240
|
+
function matchesProviderRedirectUri(client, requested) {
|
|
241
|
+
if (!Array.isArray(client.redirectUris)) return false;
|
|
242
|
+
const requestedUrl = canonicalRedirectUrl(requested);
|
|
243
|
+
if (!requestedUrl) return false;
|
|
244
|
+
if (client.redirectUris.includes(requested)) return true;
|
|
245
|
+
return client.redirectUris.some((registered) => {
|
|
246
|
+
const registeredUrl = canonicalRedirectUrl(registered);
|
|
247
|
+
if (!registeredUrl) return false;
|
|
248
|
+
const registeredLoopbackIp = registeredUrl.hostname === "127.0.0.1" || registeredUrl.hostname === "[::1]";
|
|
249
|
+
return registeredLoopbackIp && registeredUrl.protocol === requestedUrl.protocol && registeredUrl.hostname === requestedUrl.hostname && registeredUrl.pathname === requestedUrl.pathname && registeredUrl.search === requestedUrl.search;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function assertRegisteredRedirectUri(client, redirectUri) {
|
|
253
|
+
if (!matchesProviderRedirectUri(client, redirectUri)) {
|
|
254
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function authorizeParameters(request) {
|
|
258
|
+
if (request.method === "GET") return new URL(request.url).searchParams;
|
|
259
|
+
if (request.method !== "POST") {
|
|
260
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
261
|
+
}
|
|
262
|
+
return await parseBoundedFormBody(request, AUTHORIZE_BODY_MAX_BYTES);
|
|
263
|
+
}
|
|
264
|
+
function oauthAuthorizationFailure(redirectUri, state, issuer, description) {
|
|
265
|
+
const location = new URL(redirectUri);
|
|
266
|
+
location.searchParams.set("error", "invalid_target");
|
|
267
|
+
location.searchParams.set("error_description", description);
|
|
268
|
+
if (state) location.searchParams.set("state", state);
|
|
269
|
+
location.searchParams.set("iss", issuer);
|
|
270
|
+
return new Response(null, {
|
|
271
|
+
headers: {
|
|
272
|
+
"Cache-Control": "no-store",
|
|
273
|
+
Location: location.toString(),
|
|
274
|
+
Pragma: "no-cache"
|
|
275
|
+
},
|
|
276
|
+
status: 302
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async function guardAuthorizeProfile(request, context, options) {
|
|
280
|
+
const parameters = await authorizeParameters(request);
|
|
281
|
+
const clientIds = parameters.getAll("client_id");
|
|
282
|
+
const redirectUris = parameters.getAll("redirect_uri");
|
|
283
|
+
if (redirectUris.length === 1 && !canonicalRedirectUrl(redirectUris[0])) {
|
|
284
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
285
|
+
}
|
|
286
|
+
if (clientIds.length !== 1) return;
|
|
287
|
+
const { client } = await loadSafeOAuthBinding(context, options, clientIds[0]);
|
|
288
|
+
const trustedRedirect = redirectUris.length === 1 && matchesProviderRedirectUri(client, redirectUris[0]) ? redirectUris[0] : null;
|
|
289
|
+
const resources = parameters.getAll("resource");
|
|
290
|
+
let resourceErrorDescription = "exactly one resource is required";
|
|
291
|
+
if (resources.length === 1) {
|
|
292
|
+
try {
|
|
293
|
+
await loadSafeOAuthBinding(context, options, clientIds[0], resources[0]);
|
|
294
|
+
return;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (!(error instanceof OAuthSecurityError)) throw error;
|
|
297
|
+
resourceErrorDescription = "requested resource is invalid";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!trustedRedirect) {
|
|
301
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
302
|
+
}
|
|
303
|
+
const states = parameters.getAll("state");
|
|
304
|
+
return oauthAuthorizationFailure(
|
|
305
|
+
trustedRedirect,
|
|
306
|
+
states.length === 1 ? states[0] : null,
|
|
307
|
+
context.baseURL,
|
|
308
|
+
resourceErrorDescription
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
async function guardTokenRequest(request, context, options) {
|
|
312
|
+
if (request.method !== "POST" || request.headers.has("dpop")) {
|
|
313
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
314
|
+
}
|
|
315
|
+
const parameters = await parseBoundedFormRequest(request, TOKEN_FIELDS);
|
|
316
|
+
const authentication = guardedClientAuthentication(request, parameters);
|
|
317
|
+
if (requireSingleParameter(parameters, "grant_type") !== "authorization_code" || parameters.has("refresh_token") || parameters.has("scope")) {
|
|
318
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
319
|
+
}
|
|
320
|
+
requireSingleParameter(parameters, "code");
|
|
321
|
+
const redirectUri = requireSingleParameter(parameters, "redirect_uri");
|
|
322
|
+
const verifier = requireSingleParameter(parameters, "code_verifier");
|
|
323
|
+
if (!CODE_VERIFIER_PATTERN.test(verifier)) {
|
|
324
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
325
|
+
}
|
|
326
|
+
const resourceId = requireSingleParameter(parameters, "resource");
|
|
327
|
+
const { client } = await loadSafeOAuthBinding(
|
|
328
|
+
context,
|
|
329
|
+
options,
|
|
330
|
+
authentication.clientId,
|
|
331
|
+
resourceId
|
|
332
|
+
);
|
|
333
|
+
assertClientAuthenticationMethod(client, authentication);
|
|
334
|
+
assertRegisteredRedirectUri(client, redirectUri);
|
|
335
|
+
}
|
|
336
|
+
async function guardRevokeRequest(request, context, options) {
|
|
337
|
+
if (request.method !== "POST" || request.headers.has("dpop")) {
|
|
338
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
339
|
+
}
|
|
340
|
+
const parameters = await parseBoundedFormRequest(request, REVOKE_FIELDS);
|
|
341
|
+
const authentication = guardedClientAuthentication(request, parameters);
|
|
342
|
+
requireSingleParameter(parameters, "token");
|
|
343
|
+
const hint = parameters.get("token_type_hint");
|
|
344
|
+
if (hint !== null && hint !== "access_token") {
|
|
345
|
+
throw new OAuthSecurityError("AUTH_OAUTH_REQUEST_INVALID");
|
|
346
|
+
}
|
|
347
|
+
const { client } = await loadSafeOAuthBinding(context, options, authentication.clientId);
|
|
348
|
+
assertClientAuthenticationMethod(client, authentication);
|
|
349
|
+
}
|
|
350
|
+
function hasSafeGlobalAuthRuntime(context) {
|
|
351
|
+
const ipAddress = context.options.advanced?.ipAddress;
|
|
352
|
+
const ipHeaders = ipAddress?.ipAddressHeaders;
|
|
353
|
+
const trustedProxies = ipAddress?.trustedProxies;
|
|
354
|
+
return context.options.rateLimit?.enabled === true && context.options.rateLimit.storage === "database" && context.options.rateLimit.modelName === "rateLimit" && context.options.rateLimit.customStorage === void 0 && context.options.rateLimit.customRules === void 0 && Array.isArray(ipHeaders) && ipHeaders.length === 1 && ipHeaders[0]?.toLowerCase() === VERIFIED_CLIENT_IP_HEADER && (ipAddress?.disableIpTracking === void 0 || ipAddress.disableIpTracking === false) && (trustedProxies === void 0 || Array.isArray(trustedProxies) && trustedProxies.length === 0) && (ipAddress?.ipv6Subnet === void 0 || ipAddress.ipv6Subnet === 64) && (context.options.advanced?.trustedProxyHeaders === void 0 || context.options.advanced.trustedProxyHeaders === false);
|
|
355
|
+
}
|
|
356
|
+
export function convexAuth(options) {
|
|
357
|
+
validateSessionJwt(options);
|
|
358
|
+
const oauthOptions = options.oauthProvider;
|
|
359
|
+
if (oauthOptions) installUrlCanParseCompatibility();
|
|
360
|
+
const hardenedOAuth = oauthOptions ? hardenOAuthProviderCallbacks(oauthOptions) : void 0;
|
|
361
|
+
let providerRuntimeOptions;
|
|
362
|
+
return {
|
|
363
|
+
id: "@lupinum/better-convex-nuxt",
|
|
364
|
+
rateLimit: [
|
|
365
|
+
{
|
|
366
|
+
pathMatcher: (path) => path === "/convex/token",
|
|
367
|
+
window: 10,
|
|
368
|
+
max: 300
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
init: (context) => {
|
|
372
|
+
configureSharedJwks(
|
|
373
|
+
context,
|
|
374
|
+
oauthOptions ? "AUTH_OAUTH_CONFIG_INVALID" : "AUTH_JWKS_CONFIG_INVALID"
|
|
375
|
+
);
|
|
376
|
+
if (!hasSafeGlobalAuthRuntime(context)) {
|
|
377
|
+
if (oauthOptions) throw new OAuthSecurityError("AUTH_OAUTH_CONFIG_INVALID");
|
|
378
|
+
throw new Error("AUTH_CONFIG_INVALID");
|
|
379
|
+
}
|
|
380
|
+
if (!oauthOptions || !hardenedOAuth) {
|
|
381
|
+
if ((context.options.plugins ?? []).some((plugin) => plugin.id === "oauth-provider")) {
|
|
382
|
+
throw new OAuthSecurityError("AUTH_OAUTH_CONFIG_INVALID");
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
validateOAuthProviderProfile(oauthOptions);
|
|
387
|
+
providerRuntimeOptions = validatePinnedOAuthProviderRuntime(
|
|
388
|
+
context,
|
|
389
|
+
oauthOptions,
|
|
390
|
+
hardenedOAuth
|
|
391
|
+
);
|
|
392
|
+
},
|
|
393
|
+
onRequest: async (request, context) => {
|
|
394
|
+
const path = oauthRequestPath(request, context.baseURL);
|
|
395
|
+
const issuerPath = new URL(context.baseURL).pathname;
|
|
396
|
+
if (path === `${issuerPath}/jwks`) {
|
|
397
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
398
|
+
return {
|
|
399
|
+
response: new Response(null, {
|
|
400
|
+
headers: { Allow: "GET, HEAD", "Cache-Control": "private, no-store" },
|
|
401
|
+
status: 405
|
|
402
|
+
})
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const rows = await context.adapter.findMany({
|
|
407
|
+
model: "jwks"
|
|
408
|
+
});
|
|
409
|
+
return { response: createPublicJwksResponse(rows, request.method) };
|
|
410
|
+
} catch {
|
|
411
|
+
return {
|
|
412
|
+
response: new Response(null, {
|
|
413
|
+
headers: { "Cache-Control": "private, no-store" },
|
|
414
|
+
status: 500
|
|
415
|
+
})
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (path === `${issuerPath}/convex/token` && request.headers.has("authorization") && request.headers.get(INTERNAL_SESSION_HEADER) !== "1") {
|
|
420
|
+
return { response: unauthorizedResponse() };
|
|
421
|
+
}
|
|
422
|
+
if (request.method === "GET" && path === `${issuerPath}/convex/token` && !hasPresentedSessionCredentials(request.headers)) {
|
|
423
|
+
return {
|
|
424
|
+
response: Response.json(
|
|
425
|
+
{ token: null },
|
|
426
|
+
{ headers: { "Cache-Control": "private, no-store" } }
|
|
427
|
+
)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
if (!oauthOptions || !providerRuntimeOptions) return;
|
|
431
|
+
if (!path) return { response: oauthFailure("invalid_request") };
|
|
432
|
+
const metadataPaths = /* @__PURE__ */ new Set([
|
|
433
|
+
`/.well-known/oauth-authorization-server${issuerPath}`,
|
|
434
|
+
`${issuerPath}/.well-known/oauth-authorization-server`
|
|
435
|
+
]);
|
|
436
|
+
try {
|
|
437
|
+
if (metadataPaths.has(path)) {
|
|
438
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
439
|
+
return {
|
|
440
|
+
response: new Response(null, {
|
|
441
|
+
headers: { Allow: "GET, HEAD" },
|
|
442
|
+
status: 405
|
|
443
|
+
})
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const official = oauthAuthorizationServerMetadata(
|
|
447
|
+
{ context },
|
|
448
|
+
providerRuntimeOptions
|
|
449
|
+
);
|
|
450
|
+
const projected = projectOAuthAuthorizationServerMetadata(
|
|
451
|
+
official,
|
|
452
|
+
context.baseURL,
|
|
453
|
+
oauthOptions.scopes
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
response: request.method === "HEAD" ? new Response(null, {
|
|
457
|
+
headers: { "Content-Type": "application/json" },
|
|
458
|
+
status: 200
|
|
459
|
+
}) : Response.json(projected)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
if (path === `${issuerPath}/oauth2/authorize`) {
|
|
463
|
+
const resourceFailure = await guardAuthorizeProfile(request, context, oauthOptions);
|
|
464
|
+
if (resourceFailure) return { response: resourceFailure };
|
|
465
|
+
} else if (path === `${issuerPath}/oauth2/token`) {
|
|
466
|
+
await guardTokenRequest(request, context, oauthOptions);
|
|
467
|
+
} else if (path === `${issuerPath}/oauth2/revoke`) {
|
|
468
|
+
await guardRevokeRequest(request, context, oauthOptions);
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
if (error instanceof OAuthSecurityError) {
|
|
472
|
+
const invalidClient = error.code === "AUTH_OAUTH_CLIENT_INVALID";
|
|
473
|
+
return {
|
|
474
|
+
response: oauthFailure(
|
|
475
|
+
invalidClient ? "invalid_client" : "invalid_request",
|
|
476
|
+
invalidClient ? 401 : 400
|
|
477
|
+
)
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return { response: oauthFailure("server_error", 500) };
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
hooks: {
|
|
484
|
+
after: [
|
|
485
|
+
{
|
|
486
|
+
matcher: (context) => context.path === "/convex/token",
|
|
487
|
+
handler: createAuthMiddleware(async (context) => {
|
|
488
|
+
context.setHeader("Cache-Control", "private, no-store");
|
|
489
|
+
})
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
before: [
|
|
493
|
+
internalSessionBearerBefore,
|
|
494
|
+
...oauthOptions ? [
|
|
495
|
+
{
|
|
496
|
+
matcher: (context) => context.path === "/admin/oauth2/create-client" || context.path === "/admin/oauth2/update-client" || context.path === "/admin/oauth2/resources" && context.method !== "GET" || context.path === "/admin/oauth2/resources/:identifier" && context.method !== "GET" && context.method !== "DELETE",
|
|
497
|
+
handler: createAuthMiddleware(async (context) => {
|
|
498
|
+
if (context.path === "/admin/oauth2/create-client") {
|
|
499
|
+
assertSafePinnedClientProvisioning(
|
|
500
|
+
context.method,
|
|
501
|
+
context.body,
|
|
502
|
+
oauthOptions.scopes
|
|
503
|
+
);
|
|
504
|
+
} else if (context.path === "/admin/oauth2/update-client") {
|
|
505
|
+
const body = context.body;
|
|
506
|
+
assertSafePinnedClientUpdate(context.method, body.update, oauthOptions.scopes);
|
|
507
|
+
} else {
|
|
508
|
+
assertSafePinnedResourceProvisioning(
|
|
509
|
+
context.method,
|
|
510
|
+
context.body,
|
|
511
|
+
oauthOptions.scopes
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
}
|
|
516
|
+
] : []
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
endpoints: {
|
|
520
|
+
getConvexToken: createAuthEndpoint(
|
|
521
|
+
"/convex/token",
|
|
522
|
+
{
|
|
523
|
+
method: "GET",
|
|
524
|
+
requireHeaders: true,
|
|
525
|
+
use: [sessionMiddleware],
|
|
526
|
+
metadata: {
|
|
527
|
+
openapi: {
|
|
528
|
+
description: "Issue a short-lived Convex session token",
|
|
529
|
+
responses: {
|
|
530
|
+
200: {
|
|
531
|
+
description: "Success",
|
|
532
|
+
content: {
|
|
533
|
+
"application/json": {
|
|
534
|
+
schema: {
|
|
535
|
+
properties: { token: { nullable: true, type: "string" } },
|
|
536
|
+
required: ["token"],
|
|
537
|
+
type: "object"
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
async (ctx) => {
|
|
547
|
+
ctx.setHeader("Cache-Control", "private, no-store");
|
|
548
|
+
const authenticated = ctx.context.session;
|
|
549
|
+
if (!authenticated || ctx.context.newSession) unauthorized();
|
|
550
|
+
const persistedSession = await ctx.context.adapter.findOne({
|
|
551
|
+
model: "session",
|
|
552
|
+
where: [
|
|
553
|
+
{ field: "id", value: authenticated.session.id },
|
|
554
|
+
{ field: "userId", value: authenticated.user.id },
|
|
555
|
+
{ field: "expiresAt", operator: "gt", value: /* @__PURE__ */ new Date() }
|
|
556
|
+
]
|
|
557
|
+
});
|
|
558
|
+
if (!persistedSession || persistedSession.token !== authenticated.session.token || persistedSession.expiresAt.getTime() <= Date.now()) {
|
|
559
|
+
unauthorized();
|
|
560
|
+
}
|
|
561
|
+
const persistedUser = await ctx.context.adapter.findOne({
|
|
562
|
+
model: "user",
|
|
563
|
+
where: [{ field: "id", value: persistedSession.userId }]
|
|
564
|
+
});
|
|
565
|
+
if (!persistedUser || persistedUser.id !== authenticated.user.id) unauthorized();
|
|
566
|
+
const customClaims = await options.sessionJwt.definePayload?.({
|
|
567
|
+
session: persistedSession,
|
|
568
|
+
user: persistedUser
|
|
569
|
+
}) ?? {};
|
|
570
|
+
for (const claim of Object.keys(customClaims)) {
|
|
571
|
+
if (forbiddenCustomClaims.has(claim)) {
|
|
572
|
+
throw new Error(`AUTH_SESSION_JWT_RESERVED_CLAIM:${claim}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const jwtPlugin = ctx.context.getPlugin("jwt");
|
|
576
|
+
if (!jwtPlugin) throw new Error("AUTH_JWT_PLUGIN_REQUIRED");
|
|
577
|
+
const result = await jwtPlugin.endpoints.signJWT({
|
|
578
|
+
...ctx,
|
|
579
|
+
asResponse: false,
|
|
580
|
+
body: {
|
|
581
|
+
overrideOptions: {
|
|
582
|
+
jwt: {
|
|
583
|
+
audience: options.sessionJwt.audience,
|
|
584
|
+
expirationTime: options.sessionJwt.expirationTime,
|
|
585
|
+
issuer: options.sessionJwt.issuer
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
payload: {
|
|
589
|
+
...customClaims,
|
|
590
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
591
|
+
sid: persistedSession.id,
|
|
592
|
+
sub: persistedUser.id,
|
|
593
|
+
token_use: "convex-session"
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
method: "POST",
|
|
597
|
+
returnHeaders: false,
|
|
598
|
+
returnStatus: false
|
|
599
|
+
});
|
|
600
|
+
return { token: result.token };
|
|
601
|
+
}
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { requireAuthOrigin } from "./origin.js";
|
|
2
|
+
export function getConvexAuthProvider(env = process.env) {
|
|
3
|
+
const issuer = requireAuthOrigin("CONVEX_SITE_URL", env);
|
|
4
|
+
return {
|
|
5
|
+
algorithm: "RS256",
|
|
6
|
+
applicationID: "convex",
|
|
7
|
+
issuer,
|
|
8
|
+
jwks: `${issuer}/api/auth/jwks`,
|
|
9
|
+
type: "customJwt"
|
|
10
|
+
};
|
|
11
|
+
}
|