@robelest/convex-auth 0.0.4-preview.30 → 0.0.4-preview.32

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.
Files changed (104) hide show
  1. package/dist/bin.js +125 -36
  2. package/dist/browser/index.d.ts +3 -13
  3. package/dist/browser/index.js +47 -12
  4. package/dist/browser/navigation.js +1 -1
  5. package/dist/browser/passkey.js +7 -7
  6. package/dist/browser/runtime.js +13 -15
  7. package/dist/client/core/types.d.ts +179 -63
  8. package/dist/client/core/types.js +6 -0
  9. package/dist/client/factors/totp.js +1 -1
  10. package/dist/client/index.d.ts +5 -4
  11. package/dist/client/index.js +115 -56
  12. package/dist/client/runtime/mutex.js +3 -2
  13. package/dist/component/_generated/component.d.ts +40 -0
  14. package/dist/component/http.js +9 -0
  15. package/dist/component/index.d.ts +1 -1
  16. package/dist/component/model.d.ts +27 -27
  17. package/dist/component/model.js +2 -1
  18. package/dist/component/modules.js +1 -0
  19. package/dist/component/public/factors/passkeys.js +31 -1
  20. package/dist/component/public/identity/codes.js +1 -1
  21. package/dist/component/public/identity/tokens.js +2 -1
  22. package/dist/component/public/identity/verifiers.js +15 -5
  23. package/dist/component/public.js +2 -2
  24. package/dist/component/schema.d.ts +287 -285
  25. package/dist/component/schema.js +2 -1
  26. package/dist/core/index.d.ts +8 -3
  27. package/dist/core/index.js +7 -2
  28. package/dist/expo/index.d.ts +21 -0
  29. package/dist/expo/index.js +148 -0
  30. package/dist/expo/passkey.js +174 -0
  31. package/dist/providers/apple.d.ts +1 -1
  32. package/dist/providers/apple.js +6 -8
  33. package/dist/providers/custom.d.ts +1 -1
  34. package/dist/providers/custom.js +4 -7
  35. package/dist/providers/github.d.ts +1 -1
  36. package/dist/providers/github.js +5 -8
  37. package/dist/providers/google.d.ts +1 -1
  38. package/dist/providers/google.js +5 -8
  39. package/dist/providers/microsoft.d.ts +1 -1
  40. package/dist/providers/microsoft.js +5 -9
  41. package/dist/providers/password.d.ts +18 -37
  42. package/dist/providers/password.js +170 -115
  43. package/dist/providers/redirect.d.ts +1 -0
  44. package/dist/providers/redirect.js +20 -0
  45. package/dist/server/auth.d.ts +6 -7
  46. package/dist/server/auth.js +3 -2
  47. package/dist/server/{ctxCache.js → cache/context.js} +2 -2
  48. package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
  49. package/dist/server/context.js +3 -10
  50. package/dist/server/contract.d.ts +2 -87
  51. package/dist/server/contract.js +1 -1
  52. package/dist/server/cookies.js +25 -1
  53. package/dist/server/core.js +1 -1
  54. package/dist/server/errors.js +24 -1
  55. package/dist/server/{auth-context.d.ts → facade.d.ts} +3 -45
  56. package/dist/server/{auth-context.js → facade.js} +11 -12
  57. package/dist/server/http.d.ts +7 -7
  58. package/dist/server/http.js +47 -7
  59. package/dist/server/{convexIdentity.d.ts → identity/convex.d.ts} +3 -3
  60. package/dist/server/index.d.ts +5 -3
  61. package/dist/server/index.js +3 -2
  62. package/dist/server/mounts.d.ts +171 -18
  63. package/dist/server/mutations/code.js +7 -1
  64. package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
  65. package/dist/server/mutations/index.js +1 -1
  66. package/dist/server/mutations/invalidate.js +11 -1
  67. package/dist/server/mutations/oauth.js +25 -27
  68. package/dist/server/mutations/signin.js +6 -0
  69. package/dist/server/mutations/signout.js +5 -0
  70. package/dist/server/mutations/store.js +1 -1
  71. package/dist/server/oauth/factory.js +11 -3
  72. package/dist/server/passkey.js +126 -110
  73. package/dist/server/prefetch.js +8 -1
  74. package/dist/server/redirects.js +11 -3
  75. package/dist/server/refresh.js +6 -1
  76. package/dist/server/runtime.d.ts +68 -37
  77. package/dist/server/runtime.js +318 -36
  78. package/dist/server/services/group.js +4 -0
  79. package/dist/server/sessions.js +1 -0
  80. package/dist/server/signin.js +8 -6
  81. package/dist/server/sso/domain.d.ts +159 -16
  82. package/dist/server/sso/domain.js +1 -1
  83. package/dist/server/sso/http.js +144 -60
  84. package/dist/server/sso/oidc.js +28 -12
  85. package/dist/server/sso/policy.js +30 -14
  86. package/dist/server/sso/provision.js +1 -1
  87. package/dist/server/sso/saml.js +18 -9
  88. package/dist/server/sso/scim.js +12 -4
  89. package/dist/server/sso/shared.js +5 -5
  90. package/dist/server/telemetry.js +3 -0
  91. package/dist/server/tokens.js +10 -2
  92. package/dist/server/totp.js +127 -100
  93. package/dist/server/types.d.ts +224 -151
  94. package/dist/server/url.js +1 -1
  95. package/dist/server/users.js +93 -53
  96. package/dist/server/wellknown.d.ts +75 -0
  97. package/dist/server/wellknown.js +198 -0
  98. package/dist/shared/errors.js +0 -1
  99. package/package.json +36 -4
  100. package/dist/server/oauth/index.js +0 -12
  101. package/dist/server/utils/dispatch.js +0 -36
  102. package/dist/shared/authResults.d.ts +0 -16
  103. /package/dist/server/{componentContext.js → component/context.js} +0 -0
  104. /package/dist/server/{convexIdentity.js → identity/convex.js} +0 -0
@@ -52,7 +52,8 @@ var schema_default = defineSchema({
52
52
  }).index("account_id", ["accountId"]).index("code", ["code"]),
53
53
  AuthVerifier: defineTable({
54
54
  sessionId: v.optional(v.id("Session")),
55
- signature: v.optional(v.string())
55
+ signature: v.optional(v.string()),
56
+ expirationTime: v.optional(v.number())
56
57
  }).index("signature", ["signature"]),
57
58
  Passkey: defineTable({
58
59
  userId: v.id("User"),
@@ -1,9 +1,9 @@
1
1
  import { AuthAuthorizationConfig, ConvexAuthConfig, Doc, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "../server/types.js";
2
- import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory, AuthContextResolver, OptionalAuthContext, UserDoc } from "../server/auth-context.js";
3
- import { ComponentCtx, ComponentReadCtx } from "../server/componentContext.js";
2
+ import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory, AuthContextResolver, OptionalAuthContext, UserDoc } from "../server/facade.js";
3
+ import { ComponentCtx, ComponentReadCtx } from "../server/component/context.js";
4
4
  import { AuthProfile } from "../server/payloads.js";
5
5
  import "../component/index.js";
6
- import "../server/convexIdentity.js";
6
+ import "../server/identity/convex.js";
7
7
  import * as convex_values0 from "convex/values";
8
8
  import * as convex_server0 from "convex/server";
9
9
  import * as fluent_convex0 from "fluent-convex";
@@ -31,6 +31,11 @@ import * as fluent_convex0 from "fluent-convex";
31
31
  * import { auth } from "./auth-core";
32
32
  * export const authQuery = customQuery(query, auth.ctx());
33
33
  * ```
34
+ *
35
+ * @param component - The Convex Auth component reference from `components.auth`.
36
+ * @param config - Optional auth configuration. Provider config is intentionally
37
+ * omitted in this lightweight entrypoint.
38
+ * @returns The lightweight auth context facade.
34
39
  */
35
40
  declare function createAuthContext(component: ConvexAuthConfig["component"], config?: Omit<AuthConfig, "providers"> & {
36
41
  authorization?: AuthAuthorizationConfig;
@@ -1,5 +1,5 @@
1
- import "../server/convexIdentity.js";
2
- import { createAuthContextFacade } from "../server/auth-context.js";
1
+ import "../server/identity/convex.js";
2
+ import { createAuthContextFacade } from "../server/facade.js";
3
3
  import { configDefaults } from "../server/config.js";
4
4
  import { createCoreDomains } from "../server/core.js";
5
5
  import { callModifyAccount } from "../server/mutations/account.js";
@@ -30,6 +30,11 @@ import { callRetrieveAccountWithCredentials } from "../server/mutations/retrieve
30
30
  * import { auth } from "./auth-core";
31
31
  * export const authQuery = customQuery(query, auth.ctx());
32
32
  * ```
33
+ *
34
+ * @param component - The Convex Auth component reference from `components.auth`.
35
+ * @param config - Optional auth configuration. Provider config is intentionally
36
+ * omitted in this lightweight entrypoint.
37
+ * @returns The lightweight auth context facade.
33
38
  */
34
39
  function createAuthContext(component, config) {
35
40
  const fullConfig = configDefaults({
@@ -0,0 +1,21 @@
1
+ import { AuthApiRefs, ClientOptions, PlatformAuthClient } from "../client/core/types.js";
2
+ import "../client/index.js";
3
+ import * as AuthSession from "expo-auth-session";
4
+
5
+ //#region src/expo/index.d.ts
6
+ interface ExpoClientOptions<Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs> extends ClientOptions<Api> {
7
+ authSession?: AuthSession.AuthSessionRedirectUriOptions & {
8
+ redirectUri?: string;
9
+ preferEphemeralSession?: boolean;
10
+ };
11
+ }
12
+ /**
13
+ * Create an Expo-configured auth client.
14
+ *
15
+ * Native Expo defaults include SecureStore persistence, auth session launch,
16
+ * and native passkey support. Web falls back to the browser entrypoint.
17
+ */
18
+ declare function client<Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs>(options: ExpoClientOptions<Api>): PlatformAuthClient<Api>;
19
+ //#endregion
20
+ export { type AuthApiRefs, type PlatformAuthClient as AuthClient, ExpoClientOptions, client };
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,148 @@
1
+ import { LOG_LEVELS, logMessage } from "../shared/log.js";
2
+ import { ClientAdapterFactoriesLive, ClientAdaptersLive } from "../client/services/adapters.js";
3
+ import { ClientHttpLive } from "../client/services/http.js";
4
+ import { resolveClientServices } from "../client/services/resolve.js";
5
+ import { ClientRuntimeLive } from "../client/services/runtime.js";
6
+ import { client as client$1 } from "../client/index.js";
7
+ import { client as client$2 } from "../browser/index.js";
8
+ import { createExpoPasskeyClient } from "./passkey.js";
9
+ import { ConvexHttpClient } from "convex/browser";
10
+ import * as AuthSession from "expo-auth-session";
11
+ import * as SecureStore from "expo-secure-store";
12
+ import * as WebBrowser from "expo-web-browser";
13
+
14
+ //#region src/expo/index.ts
15
+ /**
16
+ * Expo-first auth client for `@robelest/convex-auth/expo`.
17
+ *
18
+ * This entrypoint wraps the framework-agnostic `client(...)` helper with
19
+ * Expo-native defaults such as SecureStore-backed token persistence, auth
20
+ * session launching, and native passkey support.
21
+ *
22
+ * OAuth in Expo uses direct mode only. Do not configure `proxyPath` for Expo
23
+ * OAuth flows because the proxy flow depends on browser cookies and HTML
24
+ * redirects.
25
+ *
26
+ * @module
27
+ */
28
+ const secureStoreStorage = {
29
+ async getItem(key) {
30
+ return await SecureStore.getItemAsync(key);
31
+ },
32
+ async setItem(key, value) {
33
+ await SecureStore.setItemAsync(key, value);
34
+ },
35
+ async removeItem(key) {
36
+ await SecureStore.deleteItemAsync(key);
37
+ }
38
+ };
39
+ /**
40
+ * Create an Expo-configured auth client.
41
+ *
42
+ * Native Expo defaults include SecureStore persistence, auth session launch,
43
+ * and native passkey support. Web falls back to the browser entrypoint.
44
+ */
45
+ function client(options) {
46
+ if (isWebRuntime()) return client$2(options);
47
+ const url = options.proxyPath === void 0 ? options.url ?? inferConvexUrl(options.convex) : void 0;
48
+ const services = resolveClientServices({
49
+ runtime: ClientRuntimeLive(mergeExpoRuntime(options.runtime)),
50
+ adapters: ClientAdaptersLive(options.adapters ?? {}),
51
+ adapterFactories: ClientAdapterFactoriesLive({
52
+ ...options.adapterFactories,
53
+ passkey: options.adapterFactories?.passkey ?? ((deps) => createExpoPasskeyClient(deps))
54
+ }),
55
+ http: ClientHttpLive(options.proxyPath !== void 0 ? null : options.httpClient ?? (url ? new ConvexHttpClient(url) : null))
56
+ });
57
+ const redirectUri = resolveRedirectUri(options.authSession);
58
+ const baseClient = client$1({
59
+ ...options,
60
+ storage: options.storage === void 0 && options.proxyPath !== void 0 ? null : options.storage,
61
+ runtime: services.runtime,
62
+ adapters: services.adapters,
63
+ adapterFactories: services.adapterFactories,
64
+ httpClient: services.httpClient
65
+ });
66
+ const initialize = async () => {
67
+ await baseClient.initialize();
68
+ };
69
+ const signIn = async (provider, ...args) => {
70
+ const params = args[0];
71
+ const nextParams = withRedirectTo(params, redirectUri);
72
+ const result = await baseClient.signIn(provider, nextParams);
73
+ if (result.kind !== "redirect") return result;
74
+ if (options.proxyPath !== void 0) throw new Error("Expo OAuth is not supported when `proxyPath` is set. Use direct mode with `api` and an Expo redirect URI.");
75
+ const authResult = await WebBrowser.openAuthSessionAsync(result.redirect.toString(), redirectUri, { preferEphemeralSession: options.authSession?.preferEphemeralSession });
76
+ if (authResult.type === "success") {
77
+ if ((await baseClient.completeOAuth(authResult.url)).handled) return { kind: "signedIn" };
78
+ }
79
+ return result;
80
+ };
81
+ const expoClient = {
82
+ get state() {
83
+ return baseClient.state;
84
+ },
85
+ initialize,
86
+ param: baseClient.param,
87
+ get invite() {
88
+ return baseClient.invite;
89
+ },
90
+ completeOAuth: baseClient.completeOAuth,
91
+ signIn,
92
+ signOut: baseClient.signOut,
93
+ onChange: baseClient.onChange,
94
+ destroy: baseClient.destroy,
95
+ ..."totp" in baseClient ? { totp: baseClient.totp } : {},
96
+ ..."device" in baseClient ? { device: baseClient.device } : {},
97
+ ..."passkey" in baseClient ? { passkey: baseClient.passkey } : {}
98
+ };
99
+ initialize().catch((error) => {
100
+ logMessage("convex-auth/expo", LOG_LEVELS.ERROR, ["[convex-auth] Expo client initialization failed:", error]);
101
+ });
102
+ return expoClient;
103
+ }
104
+ function isWebRuntime() {
105
+ return typeof window !== "undefined" && typeof document !== "undefined";
106
+ }
107
+ function mergeExpoRuntime(runtime) {
108
+ const defaults = {
109
+ environment: "client",
110
+ storage: secureStoreStorage
111
+ };
112
+ return {
113
+ ...defaults,
114
+ ...runtime,
115
+ environment: runtime?.environment ?? defaults.environment,
116
+ storage: runtime?.storage === void 0 ? defaults.storage : runtime.storage
117
+ };
118
+ }
119
+ function resolveRedirectUri(options) {
120
+ if (options?.redirectUri) return options.redirectUri;
121
+ return AuthSession.makeRedirectUri(options);
122
+ }
123
+ function withRedirectTo(params, redirectTo) {
124
+ if (params?.redirectTo !== void 0) return params;
125
+ return {
126
+ ...params,
127
+ redirectTo
128
+ };
129
+ }
130
+ function inferConvexUrl(convex) {
131
+ if (!convex || typeof convex !== "object") return;
132
+ const candidate = convex;
133
+ try {
134
+ if (typeof candidate.url === "string") return candidate.url;
135
+ } catch {
136
+ return;
137
+ }
138
+ try {
139
+ const client = typeof candidate.client === "object" && candidate.client !== null ? candidate.client : void 0;
140
+ return typeof client?.url === "string" ? client.url : void 0;
141
+ } catch {
142
+ return;
143
+ }
144
+ }
145
+
146
+ //#endregion
147
+ export { client };
148
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,174 @@
1
+ import { Passkey } from "react-native-passkey";
2
+
3
+ //#region src/expo/passkey.ts
4
+ function requireStringOption(value, name) {
5
+ if (typeof value !== "string" || value.length === 0) throw new Error(`Server did not return required passkey option \`${name}\``);
6
+ return value;
7
+ }
8
+ function toPublicKeyCredentialDescriptors(credentials) {
9
+ return (credentials ?? []).map((cred) => ({
10
+ type: cred.type ?? "public-key",
11
+ id: cred.id,
12
+ ...cred.transports === void 0 ? null : { transports: cred.transports }
13
+ }));
14
+ }
15
+ function wrapNativePasskeyError(e, cancelMessage) {
16
+ if (e instanceof Error) {
17
+ if (e.message.includes("cancel") || e.message.includes("Cancel")) return new Error(cancelMessage);
18
+ return e;
19
+ }
20
+ const message = typeof e === "object" && e !== null && "message" in e ? String(e.message) : String(e);
21
+ if (message.includes("cancel") || message.includes("Cancel")) return new Error(cancelMessage);
22
+ return new Error(message);
23
+ }
24
+ /** @internal */
25
+ function createExpoPasskeyClient(deps) {
26
+ const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } = deps;
27
+ const handleSignedInResult = async (result, flow) => {
28
+ if (result.kind !== "signedIn") return { kind: "started" };
29
+ return await setTokenAndMaybeWait(proxy ? {
30
+ shouldStore: false,
31
+ tokens: result.session === null ? null : { token: result.session.token },
32
+ waitForHandshake: true,
33
+ context: {
34
+ provider: "passkey",
35
+ flow
36
+ }
37
+ } : {
38
+ shouldStore: true,
39
+ tokens: result.session,
40
+ waitForHandshake: true,
41
+ context: {
42
+ provider: "passkey",
43
+ flow
44
+ }
45
+ }) ? { kind: "signedIn" } : { kind: "started" };
46
+ };
47
+ return {
48
+ isSupported: () => Passkey.isSupported(),
49
+ isAutofillSupported: async () => false,
50
+ register: async (opts) => {
51
+ const phase1Params = {
52
+ flow: "register",
53
+ email: opts?.email,
54
+ userName: opts?.userName,
55
+ userDisplayName: opts?.userDisplayName
56
+ };
57
+ let phase1Result;
58
+ if (proxy) phase1Result = await proxyFetch({
59
+ action: "auth:signIn",
60
+ args: {
61
+ provider: "passkey",
62
+ params: phase1Params
63
+ }
64
+ });
65
+ else phase1Result = await convex.action(requireApiRefs().signIn, {
66
+ provider: "passkey",
67
+ params: phase1Params
68
+ });
69
+ if (phase1Result.kind !== "passkeyOptions") throw new Error("Server did not return passkey registration options");
70
+ const options = phase1Result.options;
71
+ const createRequest = {
72
+ challenge: options.challenge,
73
+ rp: {
74
+ ...options.rp,
75
+ id: requireStringOption(options.rp.id, "rp.id")
76
+ },
77
+ user: options.user,
78
+ pubKeyCredParams: options.pubKeyCredParams,
79
+ timeout: options.timeout,
80
+ attestation: options.attestation,
81
+ authenticatorSelection: options.authenticatorSelection,
82
+ excludeCredentials: toPublicKeyCredentialDescriptors(options.excludeCredentials)
83
+ };
84
+ let credential;
85
+ try {
86
+ credential = await Passkey.create(createRequest);
87
+ } catch (e) {
88
+ throw wrapNativePasskeyError(e, "Passkey registration was cancelled");
89
+ }
90
+ const phase2Params = {
91
+ flow: "verify",
92
+ clientDataJSON: credential.response.clientDataJSON,
93
+ attestationObject: credential.response.attestationObject,
94
+ transports: credential.response.transports,
95
+ passkeyName: opts?.name,
96
+ email: opts?.email
97
+ };
98
+ let phase2Result;
99
+ if (proxy) phase2Result = await proxyFetch({
100
+ action: "auth:signIn",
101
+ args: {
102
+ provider: "passkey",
103
+ params: phase2Params,
104
+ verifier: phase1Result.verifier
105
+ }
106
+ });
107
+ else phase2Result = await convex.action(requireApiRefs().signIn, {
108
+ provider: "passkey",
109
+ params: phase2Params,
110
+ verifier: phase1Result.verifier
111
+ });
112
+ return handleSignedInResult(phase2Result, "verify");
113
+ },
114
+ signIn: async (opts) => {
115
+ const phase1Params = {
116
+ flow: "signIn",
117
+ email: opts?.email
118
+ };
119
+ let phase1Result;
120
+ if (proxy) phase1Result = await proxyFetch({
121
+ action: "auth:signIn",
122
+ args: {
123
+ provider: "passkey",
124
+ params: phase1Params
125
+ }
126
+ });
127
+ else phase1Result = await convex.action(requireApiRefs().signIn, {
128
+ provider: "passkey",
129
+ params: phase1Params
130
+ });
131
+ if (phase1Result.kind !== "passkeyOptions") throw new Error("Server did not return passkey authentication options");
132
+ const options = phase1Result.options;
133
+ const getRequest = {
134
+ challenge: options.challenge,
135
+ timeout: options.timeout,
136
+ rpId: requireStringOption(options.rpId, "rpId"),
137
+ userVerification: options.userVerification,
138
+ allowCredentials: toPublicKeyCredentialDescriptors(options.allowCredentials)
139
+ };
140
+ let credential;
141
+ try {
142
+ credential = await Passkey.get(getRequest);
143
+ } catch (e) {
144
+ throw wrapNativePasskeyError(e, "Passkey authentication was cancelled");
145
+ }
146
+ const phase2Params = {
147
+ flow: "verify",
148
+ credentialId: credential.rawId ?? credential.id,
149
+ clientDataJSON: credential.response.clientDataJSON,
150
+ authenticatorData: credential.response.authenticatorData,
151
+ signature: credential.response.signature
152
+ };
153
+ let phase2Result;
154
+ if (proxy) phase2Result = await proxyFetch({
155
+ action: "auth:signIn",
156
+ args: {
157
+ provider: "passkey",
158
+ params: phase2Params,
159
+ verifier: phase1Result.verifier
160
+ }
161
+ });
162
+ else phase2Result = await convex.action(requireApiRefs().signIn, {
163
+ provider: "passkey",
164
+ params: phase2Params,
165
+ verifier: phase1Result.verifier
166
+ });
167
+ return handleSignedInResult(phase2Result, "verify");
168
+ }
169
+ };
170
+ }
171
+
172
+ //#endregion
173
+ export { createExpoPasskeyClient };
174
+ //# sourceMappingURL=passkey.js.map
@@ -27,7 +27,7 @@ interface AppleConfig {
27
27
  keyId: string;
28
28
  /** Apple private key PEM contents or bytes. */
29
29
  privateKey: string | Uint8Array;
30
- /** Optional callback URL override. Defaults to `CUSTOM_AUTH_SITE_URL` or `CONVEX_SITE_URL` plus `/api/auth/callback/apple`. */
30
+ /** Optional callback URL override. Defaults to the auth site URL plus `/callback/apple`. */
31
31
  redirectUri?: string;
32
32
  /** Optional OAuth scopes. Defaults to `name email`. */
33
33
  scopes?: string[];
@@ -1,5 +1,5 @@
1
- import { envOptionalString, readConfigSync } from "../server/env.js";
2
1
  import { createArcticOAuthClient, createOAuthProvider } from "../server/oauth/factory.js";
2
+ import { defaultOAuthRedirectUri } from "./redirect.js";
3
3
  import { Apple } from "arctic";
4
4
 
5
5
  //#region src/providers/apple.ts
@@ -40,18 +40,16 @@ const DEFAULT_SCOPES = ["name", "email"];
40
40
  * ```
41
41
  */
42
42
  function apple(config) {
43
+ const privateKey = typeof config.privateKey === "string" ? config.privateKey : new TextDecoder().decode(config.privateKey);
44
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
45
+ const createProvider = () => new Apple(config.clientId, config.teamId, config.keyId, new TextEncoder().encode(privateKey), config.redirectUri ?? defaultOAuthRedirectUri("apple"));
43
46
  return createOAuthProvider({
44
47
  id: "apple",
45
- provider: createArcticOAuthClient(new Apple(config.clientId, config.teamId, config.keyId, typeof config.privateKey === "string" ? new TextEncoder().encode(config.privateKey) : config.privateKey, config.redirectUri ?? defaultRedirectUri("apple")), { pkce: "never" }),
46
- scopes: config.scopes ?? DEFAULT_SCOPES,
48
+ provider: createArcticOAuthClient(createProvider, { pkce: "never" }),
49
+ scopes,
47
50
  accountLinking: config.accountLinking
48
51
  });
49
52
  }
50
- function defaultRedirectUri(providerId) {
51
- const rootUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL")) ?? readConfigSync(envOptionalString("CONVEX_SITE_URL"));
52
- if (!rootUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
53
- return `${rootUrl}/api/auth/callback/${providerId}`;
54
- }
55
53
 
56
54
  //#endregion
57
55
  export { apple };
@@ -50,7 +50,7 @@ interface CustomOAuthConfig {
50
50
  clientId: string;
51
51
  /** Optional OAuth client secret. */
52
52
  clientSecret?: string | null;
53
- /** Optional callback URL override. Defaults to `CUSTOM_AUTH_SITE_URL` or `CONVEX_SITE_URL` plus `/api/auth/callback/<id>`. */
53
+ /** Optional callback URL override. Defaults to the auth site URL plus `/callback/<id>`. */
54
54
  redirectUri?: string;
55
55
  /** Optional default scopes requested during sign-in. */
56
56
  scopes?: string[];
@@ -1,5 +1,5 @@
1
- import { envOptionalString, readConfigSync } from "../server/env.js";
2
1
  import { createOAuthProvider } from "../server/oauth/factory.js";
2
+ import { defaultOAuthRedirectUri } from "./redirect.js";
3
3
  import { sha256 } from "@oslojs/crypto/sha2";
4
4
  import { encodeBase64urlNoPadding } from "@oslojs/encoding";
5
5
 
@@ -12,11 +12,6 @@ import { encodeBase64urlNoPadding } from "@oslojs/encoding";
12
12
  *
13
13
  * @module
14
14
  */
15
- function defaultRedirectUri(providerId) {
16
- const rootUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL")) ?? readConfigSync(envOptionalString("CONVEX_SITE_URL"));
17
- if (!rootUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
18
- return `${rootUrl}/api/auth/callback/${providerId}`;
19
- }
20
15
  function joinScopes(scopes, separator = " ") {
21
16
  return scopes.join(separator);
22
17
  }
@@ -24,14 +19,15 @@ function createCodeChallenge(codeVerifier) {
24
19
  return encodeBase64urlNoPadding(sha256(new TextEncoder().encode(codeVerifier)));
25
20
  }
26
21
  function createRuntimeClient(config) {
27
- const redirectUri = config.redirectUri ?? defaultRedirectUri(config.id);
28
22
  const authorization = config.authorization;
29
23
  const token = config.token;
30
24
  const pkce = authorization.pkce ?? "required";
31
25
  const scopes = [...config.scopes ?? []];
26
+ const getRedirectUri = () => config.redirectUri ?? defaultOAuthRedirectUri(config.id);
32
27
  return {
33
28
  pkce,
34
29
  createAuthorizationURL({ state, codeVerifier, scopes: requestedScopes, nonce }) {
30
+ const redirectUri = getRedirectUri();
35
31
  const url = new URL(authorization.url);
36
32
  const nextScopes = requestedScopes.length > 0 ? requestedScopes : scopes;
37
33
  url.searchParams.set("response_type", "code");
@@ -48,6 +44,7 @@ function createRuntimeClient(config) {
48
44
  return url;
49
45
  },
50
46
  async validateAuthorizationCode({ code, codeVerifier }) {
47
+ const redirectUri = getRedirectUri();
51
48
  const body = new URLSearchParams();
52
49
  body.set("grant_type", "authorization_code");
53
50
  body.set("code", code);
@@ -21,7 +21,7 @@ interface GitHubConfig {
21
21
  clientId: string;
22
22
  /** OAuth app client secret from GitHub. */
23
23
  clientSecret: string;
24
- /** Optional callback URL override. Defaults to `CUSTOM_AUTH_SITE_URL` or `CONVEX_SITE_URL` plus `/api/auth/callback/github`. */
24
+ /** Optional callback URL override. Defaults to the auth site URL plus `/callback/github`. */
25
25
  redirectUri?: string;
26
26
  /** Optional OAuth scopes. Defaults to `user:email`. */
27
27
  scopes?: string[];
@@ -1,5 +1,5 @@
1
- import { envOptionalString, readConfigSync } from "../server/env.js";
2
1
  import { createArcticOAuthClient, createOAuthProvider } from "../server/oauth/factory.js";
2
+ import { defaultOAuthRedirectUri } from "./redirect.js";
3
3
  import { GitHub } from "arctic";
4
4
 
5
5
  //#region src/providers/github.ts
@@ -39,10 +39,12 @@ const DEFAULT_SCOPES = ["user:email"];
39
39
  * ```
40
40
  */
41
41
  function github(config) {
42
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
43
+ const createProvider = () => new GitHub(config.clientId, config.clientSecret, config.redirectUri ?? defaultOAuthRedirectUri("github"));
42
44
  return createOAuthProvider({
43
45
  id: "github",
44
- provider: createArcticOAuthClient(new GitHub(config.clientId, config.clientSecret, config.redirectUri ?? defaultRedirectUri("github")), { pkce: "never" }),
45
- scopes: config.scopes ?? DEFAULT_SCOPES,
46
+ provider: createArcticOAuthClient(createProvider, { pkce: "never" }),
47
+ scopes,
46
48
  accountLinking: config.accountLinking,
47
49
  profile: async (tokens) => {
48
50
  if (!tokens.accessToken) throw new Error("GitHub OAuth response is missing access_token.");
@@ -64,11 +66,6 @@ function github(config) {
64
66
  }
65
67
  });
66
68
  }
67
- function defaultRedirectUri(providerId) {
68
- const rootUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL")) ?? readConfigSync(envOptionalString("CONVEX_SITE_URL"));
69
- if (!rootUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
70
- return `${rootUrl}/api/auth/callback/${providerId}`;
71
- }
72
69
 
73
70
  //#endregion
74
71
  export { github };
@@ -21,7 +21,7 @@ interface GoogleConfig {
21
21
  clientId: string;
22
22
  /** OAuth client secret from the Google Cloud console. */
23
23
  clientSecret: string;
24
- /** Optional callback URL override. Defaults to `CUSTOM_AUTH_SITE_URL` or `CONVEX_SITE_URL` plus `/api/auth/callback/google`. */
24
+ /** Optional callback URL override. Defaults to the auth site URL plus `/callback/google`. */
25
25
  redirectUri?: string;
26
26
  /** Optional OAuth scopes. Defaults to `openid profile email`. */
27
27
  scopes?: string[];
@@ -1,5 +1,5 @@
1
- import { envOptionalString, readConfigSync } from "../server/env.js";
2
1
  import { createArcticOAuthClient, createOAuthProvider } from "../server/oauth/factory.js";
2
+ import { defaultOAuthRedirectUri } from "./redirect.js";
3
3
  import { Google } from "arctic";
4
4
 
5
5
  //#region src/providers/google.ts
@@ -43,18 +43,15 @@ const DEFAULT_SCOPES = [
43
43
  * ```
44
44
  */
45
45
  function google(config) {
46
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
47
+ const createProvider = () => new Google(config.clientId, config.clientSecret, config.redirectUri ?? defaultOAuthRedirectUri("google"));
46
48
  return createOAuthProvider({
47
49
  id: "google",
48
- provider: createArcticOAuthClient(new Google(config.clientId, config.clientSecret, config.redirectUri ?? defaultRedirectUri("google")), { pkce: "required" }),
49
- scopes: config.scopes ?? DEFAULT_SCOPES,
50
+ provider: createArcticOAuthClient(createProvider, { pkce: "required" }),
51
+ scopes,
50
52
  accountLinking: config.accountLinking
51
53
  });
52
54
  }
53
- function defaultRedirectUri(providerId) {
54
- const rootUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL")) ?? readConfigSync(envOptionalString("CONVEX_SITE_URL"));
55
- if (!rootUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
56
- return `${rootUrl}/api/auth/callback/${providerId}`;
57
- }
58
55
 
59
56
  //#endregion
60
57
  export { google };
@@ -24,7 +24,7 @@ interface MicrosoftConfig {
24
24
  clientId: string;
25
25
  /** OAuth client secret for confidential clients, when required. */
26
26
  clientSecret?: string | null;
27
- /** Optional callback URL override. Defaults to `CUSTOM_AUTH_SITE_URL` or `CONVEX_SITE_URL` plus `/api/auth/callback/microsoft`. */
27
+ /** Optional callback URL override. Defaults to the auth site URL plus `/callback/microsoft`. */
28
28
  redirectUri?: string;
29
29
  /** Optional OAuth scopes. Defaults to `openid profile email`. */
30
30
  scopes?: string[];
@@ -1,5 +1,5 @@
1
- import { envOptionalString, readConfigSync } from "../server/env.js";
2
1
  import { createArcticOAuthClient, createOAuthProvider } from "../server/oauth/factory.js";
2
+ import { defaultOAuthRedirectUri } from "./redirect.js";
3
3
  import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
4
4
  import { MicrosoftEntraId } from "arctic";
5
5
 
@@ -45,13 +45,14 @@ const DEFAULT_SCOPES = [
45
45
  * ```
46
46
  */
47
47
  function microsoft(config) {
48
- const provider = new MicrosoftEntraId(config.tenant, config.clientId, config.clientSecret ?? null, config.redirectUri ?? defaultRedirectUri("microsoft"));
48
+ const scopes = config.scopes ?? DEFAULT_SCOPES;
49
+ const createProvider = () => new MicrosoftEntraId(config.tenant, config.clientId, config.clientSecret ?? null, config.redirectUri ?? defaultOAuthRedirectUri("microsoft"));
49
50
  const issuer = `https://login.microsoftonline.com/${config.tenant}/v2.0`;
50
51
  const jwks = createRemoteJWKSet(new URL(`${issuer}/discovery/v2.0/keys`));
51
52
  return createOAuthProvider({
52
53
  id: "microsoft",
53
- provider: createArcticOAuthClient(provider, { pkce: "required" }),
54
- scopes: config.scopes ?? DEFAULT_SCOPES,
54
+ provider: createArcticOAuthClient(createProvider, { pkce: "required" }),
55
+ scopes,
55
56
  nonce: true,
56
57
  accountLinking: config.accountLinking,
57
58
  validateTokens: async (tokens, ctx) => {
@@ -90,11 +91,6 @@ function microsoft(config) {
90
91
  }
91
92
  });
92
93
  }
93
- function defaultRedirectUri(providerId) {
94
- const rootUrl = readConfigSync(envOptionalString("CUSTOM_AUTH_SITE_URL")) ?? readConfigSync(envOptionalString("CONVEX_SITE_URL"));
95
- if (!rootUrl) throw new Error(`Missing CONVEX_SITE_URL while configuring ${providerId} OAuth provider. Set CONVEX_SITE_URL or pass redirectUri explicitly.`);
96
- return `${rootUrl}/api/auth/callback/${providerId}`;
97
- }
98
94
 
99
95
  //#endregion
100
96
  export { microsoft };