@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.
- package/dist/bin.js +125 -36
- package/dist/browser/index.d.ts +3 -13
- package/dist/browser/index.js +47 -12
- package/dist/browser/navigation.js +1 -1
- package/dist/browser/passkey.js +7 -7
- package/dist/browser/runtime.js +13 -15
- package/dist/client/core/types.d.ts +179 -63
- package/dist/client/core/types.js +6 -0
- package/dist/client/factors/totp.js +1 -1
- package/dist/client/index.d.ts +5 -4
- package/dist/client/index.js +115 -56
- package/dist/client/runtime/mutex.js +3 -2
- package/dist/component/_generated/component.d.ts +40 -0
- package/dist/component/http.js +9 -0
- package/dist/component/index.d.ts +1 -1
- package/dist/component/model.d.ts +27 -27
- package/dist/component/model.js +2 -1
- package/dist/component/modules.js +1 -0
- package/dist/component/public/factors/passkeys.js +31 -1
- package/dist/component/public/identity/codes.js +1 -1
- package/dist/component/public/identity/tokens.js +2 -1
- package/dist/component/public/identity/verifiers.js +15 -5
- package/dist/component/public.js +2 -2
- package/dist/component/schema.d.ts +287 -285
- package/dist/component/schema.js +2 -1
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.js +7 -2
- package/dist/expo/index.d.ts +21 -0
- package/dist/expo/index.js +148 -0
- package/dist/expo/passkey.js +174 -0
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.js +6 -8
- package/dist/providers/custom.d.ts +1 -1
- package/dist/providers/custom.js +4 -7
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/github.js +5 -8
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +5 -8
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/microsoft.js +5 -9
- package/dist/providers/password.d.ts +18 -37
- package/dist/providers/password.js +170 -115
- package/dist/providers/redirect.d.ts +1 -0
- package/dist/providers/redirect.js +20 -0
- package/dist/server/auth.d.ts +6 -7
- package/dist/server/auth.js +3 -2
- package/dist/server/{ctxCache.js → cache/context.js} +2 -2
- package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
- package/dist/server/context.js +3 -10
- package/dist/server/contract.d.ts +2 -87
- package/dist/server/contract.js +1 -1
- package/dist/server/cookies.js +25 -1
- package/dist/server/core.js +1 -1
- package/dist/server/errors.js +24 -1
- package/dist/server/{auth-context.d.ts → facade.d.ts} +3 -45
- package/dist/server/{auth-context.js → facade.js} +11 -12
- package/dist/server/http.d.ts +7 -7
- package/dist/server/http.js +47 -7
- package/dist/server/{convexIdentity.d.ts → identity/convex.d.ts} +3 -3
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.js +3 -2
- package/dist/server/mounts.d.ts +171 -18
- package/dist/server/mutations/code.js +7 -1
- package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
- package/dist/server/mutations/index.js +1 -1
- package/dist/server/mutations/invalidate.js +11 -1
- package/dist/server/mutations/oauth.js +25 -27
- package/dist/server/mutations/signin.js +6 -0
- package/dist/server/mutations/signout.js +5 -0
- package/dist/server/mutations/store.js +1 -1
- package/dist/server/oauth/factory.js +11 -3
- package/dist/server/passkey.js +126 -110
- package/dist/server/prefetch.js +8 -1
- package/dist/server/redirects.js +11 -3
- package/dist/server/refresh.js +6 -1
- package/dist/server/runtime.d.ts +68 -37
- package/dist/server/runtime.js +318 -36
- package/dist/server/services/group.js +4 -0
- package/dist/server/sessions.js +1 -0
- package/dist/server/signin.js +8 -6
- package/dist/server/sso/domain.d.ts +159 -16
- package/dist/server/sso/domain.js +1 -1
- package/dist/server/sso/http.js +144 -60
- package/dist/server/sso/oidc.js +28 -12
- package/dist/server/sso/policy.js +30 -14
- package/dist/server/sso/provision.js +1 -1
- package/dist/server/sso/saml.js +18 -9
- package/dist/server/sso/scim.js +12 -4
- package/dist/server/sso/shared.js +5 -5
- package/dist/server/telemetry.js +3 -0
- package/dist/server/tokens.js +10 -2
- package/dist/server/totp.js +127 -100
- package/dist/server/types.d.ts +224 -151
- package/dist/server/url.js +1 -1
- package/dist/server/users.js +93 -53
- package/dist/server/wellknown.d.ts +75 -0
- package/dist/server/wellknown.js +198 -0
- package/dist/shared/errors.js +0 -1
- package/package.json +36 -4
- package/dist/server/oauth/index.js +0 -12
- package/dist/server/utils/dispatch.js +0 -36
- package/dist/shared/authResults.d.ts +0 -16
- /package/dist/server/{componentContext.js → component/context.js} +0 -0
- /package/dist/server/{convexIdentity.js → identity/convex.js} +0 -0
package/dist/component/schema.js
CHANGED
|
@@ -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"),
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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/
|
|
3
|
-
import { ComponentCtx, ComponentReadCtx } from "../server/
|
|
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/
|
|
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;
|
package/dist/core/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../server/
|
|
2
|
-
import { createAuthContextFacade } from "../server/
|
|
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
|
|
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[];
|
package/dist/providers/apple.js
CHANGED
|
@@ -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(
|
|
46
|
-
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
|
|
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[];
|
package/dist/providers/custom.js
CHANGED
|
@@ -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
|
|
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[];
|
package/dist/providers/github.js
CHANGED
|
@@ -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(
|
|
45
|
-
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
|
|
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[];
|
package/dist/providers/google.js
CHANGED
|
@@ -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(
|
|
49
|
-
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
|
|
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
|
|
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(
|
|
54
|
-
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 };
|