use-convex 1.3.0 → 2.0.0
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/README.md +104 -59
- package/dist/module.d.mts +62 -39
- package/dist/module.mjs +68 -17
- package/dist/runtime/auth/oauth.d.ts +96 -0
- package/dist/runtime/auth/oauth.js +170 -0
- package/dist/runtime/auth/sessionRoutes.d.ts +37 -0
- package/dist/runtime/auth/sessionRoutes.js +41 -0
- package/dist/runtime/composables/useAuth.d.ts +81 -48
- package/dist/runtime/composables/useAuth.js +69 -461
- package/dist/runtime/composables/useAuthToken.d.ts +2 -2
- package/dist/runtime/composables/useAuthToken.js +4 -0
- package/dist/runtime/composables/useConvexAuth.js +1 -1
- package/dist/runtime/composables/useConvexPaginatedQuery.js +6 -1
- package/dist/runtime/composables/useConvexQuery.js +6 -1
- package/dist/runtime/plugin.auth.client.d.ts +8 -3
- package/dist/runtime/plugin.auth.client.js +207 -31
- package/dist/runtime/plugin.server.js +8 -2
- package/dist/runtime/server/auth.d.ts +7 -2
- package/dist/runtime/server/auth.js +14 -3
- package/dist/runtime/server/convexAuth/refresh.d.ts +3 -0
- package/dist/runtime/server/convexAuth/refresh.js +5 -0
- package/dist/runtime/server/convexAuth/session.middleware.d.ts +10 -0
- package/dist/runtime/server/convexAuth/session.middleware.js +37 -0
- package/dist/runtime/server/convexAuth/setup.d.ts +21 -0
- package/dist/runtime/server/convexAuth/setup.js +90 -0
- package/dist/runtime/server/convexAuth/signin.d.ts +3 -0
- package/dist/runtime/server/convexAuth/signin.js +5 -0
- package/dist/runtime/server/convexAuth/signout.d.ts +3 -0
- package/dist/runtime/server/convexAuth/signout.js +5 -0
- package/dist/runtime/server/convexConfig.d.ts +2 -6
- package/dist/runtime/server/fetch.d.ts +2 -1
- package/dist/runtime/server/fetch.js +3 -8
- package/dist/runtime/utils/authClientOptions.d.ts +33 -40
- package/dist/runtime/utils/authClientOptions.js +1 -64
- package/dist/runtime/utils/authCookie.d.ts +8 -14
- package/dist/runtime/utils/authCookie.js +23 -27
- package/dist/runtime/utils/authStorage.d.ts +76 -79
- package/dist/runtime/utils/authStorage.js +60 -141
- package/dist/runtime/utils/context.d.ts +40 -3
- package/dist/types.d.mts +4 -2
- package/package.json +10 -5
- package/dist/runtime/server/api/convex/auth/session.d.ts +0 -36
- package/dist/runtime/server/api/convex/auth/session.js +0 -90
- package/dist/runtime/server/authCookies.d.ts +0 -9
- package/dist/runtime/server/authCookies.js +0 -64
- package/dist/runtime/server/sameOrigin.d.ts +0 -14
- package/dist/runtime/server/sameOrigin.js +0 -29
- package/dist/runtime/utils/authMutex.d.ts +0 -15
- package/dist/runtime/utils/authMutex.js +0 -47
- package/dist/runtime/utils/oauthRedirect.d.ts +0 -5
- package/dist/runtime/utils/oauthRedirect.js +0 -13
|
@@ -1,476 +1,84 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { computed, nextTick } from "vue";
|
|
4
|
-
import { useRuntimeConfig, useState } from "nuxt/app";
|
|
1
|
+
import { computed, ref, shallowRef } from "vue";
|
|
2
|
+
import { useConvexContext } from "../utils/context.js";
|
|
5
3
|
import { useConvexAuth } from "./useConvexAuth.js";
|
|
6
|
-
import { missingConvexUrlError, unreachableConvexUrlError } from "../utils/errors.js";
|
|
7
|
-
import { withRefreshMutex } from "../utils/authMutex.js";
|
|
8
|
-
import { useAuthJwtCookie, useAuthPresentCookie } from "../utils/authCookie.js";
|
|
9
|
-
import { getConvexAuthClientOptions, setConvexAuthModuleDefaults } from "../utils/authClientOptions.js";
|
|
10
4
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
OAUTH_ACTIONS_KEY,
|
|
6
|
+
OAUTH_FLOW_ERROR_KEY,
|
|
7
|
+
OAUTH_SETUP_ID
|
|
8
|
+
} from "../auth/oauth.js";
|
|
9
|
+
function useBinding(name) {
|
|
10
|
+
const ctx = useConvexContext();
|
|
11
|
+
if (!import.meta.server && !ctx.convexAuth) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`[use-convex] ${name} requires \`convex.auth.provider: 'convex-auth'\` in nuxt.config.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return ctx.convexAuth ?? null;
|
|
17
|
+
}
|
|
18
|
+
function requireBinding(name) {
|
|
19
|
+
const binding = useConvexContext().convexAuth;
|
|
20
|
+
if (!binding) {
|
|
21
|
+
throw new Error(`[use-convex] ${name} is browser-only and requires Convex Auth.`);
|
|
22
|
+
}
|
|
23
|
+
return binding;
|
|
24
|
+
}
|
|
31
25
|
export function useAuth() {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
isLoading: computed(() => session.isLoading.value),
|
|
36
|
-
isAuthenticated: computed(() => false),
|
|
37
|
-
isRefreshing: computed(() => false),
|
|
38
|
-
hasSsrSession: computed(() => false),
|
|
39
|
-
showAuthedUi: computed(() => false),
|
|
40
|
-
error: session.error,
|
|
41
|
-
pending: session.pending,
|
|
42
|
-
signIn,
|
|
43
|
-
signOut
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const convexAuth = useConvexAuth();
|
|
26
|
+
const binding = useBinding("useAuth");
|
|
27
|
+
const auth = useConvexAuth();
|
|
47
28
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
showAuthedUi: convexAuth.showAuthedUi,
|
|
53
|
-
error: session.error,
|
|
54
|
-
pending: session.pending,
|
|
55
|
-
signIn,
|
|
56
|
-
signOut
|
|
29
|
+
...auth,
|
|
30
|
+
token: computed(() => binding?.state.value.token ?? null),
|
|
31
|
+
setSession: (session) => requireBinding("setSession").setSession(session),
|
|
32
|
+
signOut: () => signOut()
|
|
57
33
|
};
|
|
58
34
|
}
|
|
59
|
-
export async function signIn(provider, params) {
|
|
60
|
-
const session = useAuthSession();
|
|
61
|
-
const convexUrl = session.convexUrl;
|
|
62
|
-
if (!convexUrl) {
|
|
63
|
-
throw missingConvexUrlError("signIn");
|
|
64
|
-
}
|
|
65
|
-
session.pending.value = true;
|
|
66
|
-
session.error.value = null;
|
|
67
|
-
try {
|
|
68
|
-
const flatParams = flattenSignInParams(params);
|
|
69
|
-
const existingVerifier = await readVerifierAsync(session.verifierKey.value) ?? void 0;
|
|
70
|
-
await writeVerifier(session.verifierKey.value, null);
|
|
71
|
-
const result = await callSignIn(convexUrl, {
|
|
72
|
-
provider,
|
|
73
|
-
params: flatParams,
|
|
74
|
-
verifier: existingVerifier
|
|
75
|
-
});
|
|
76
|
-
if (result.redirect !== void 0) {
|
|
77
|
-
const url = parseOAuthRedirect(result.redirect);
|
|
78
|
-
if (result.verifier) {
|
|
79
|
-
await writeVerifier(session.verifierKey.value, result.verifier);
|
|
80
|
-
}
|
|
81
|
-
if (inBrowser()) {
|
|
82
|
-
window.location.href = url.toString();
|
|
83
|
-
}
|
|
84
|
-
return { signingIn: false, redirect: url };
|
|
85
|
-
}
|
|
86
|
-
if (result.tokens !== void 0) {
|
|
87
|
-
await persistTokens(session, result.tokens);
|
|
88
|
-
return { signingIn: result.tokens !== null };
|
|
89
|
-
}
|
|
90
|
-
return { signingIn: false };
|
|
91
|
-
} catch (cause) {
|
|
92
|
-
const message = isNetworkError(cause) && convexUrl ? unreachableConvexUrlError(convexUrl).message : cause instanceof Error ? cause.message : "Authentication failed";
|
|
93
|
-
session.error.value = message;
|
|
94
|
-
throw cause;
|
|
95
|
-
} finally {
|
|
96
|
-
session.pending.value = false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
35
|
export async function signOut() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
await http.action(authSignOut, {});
|
|
113
|
-
} catch {
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
await persistTokens(session, null);
|
|
117
|
-
} finally {
|
|
118
|
-
session.pending.value = false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
export function hydrateAuthFromStorage() {
|
|
122
|
-
const session = useAuthSession();
|
|
123
|
-
if (!session.convexUrl) {
|
|
124
|
-
session.isLoading.value = false;
|
|
125
|
-
session.hasSession.value = false;
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (session.httpOnly) {
|
|
129
|
-
const present = useAuthPresentCookie();
|
|
130
|
-
if (present?.value === "1") {
|
|
131
|
-
session.hasSession.value = true;
|
|
132
|
-
session.isLoading.value = false;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
session.isLoading.value = true;
|
|
136
|
-
void fetchAuthSession().then((result) => {
|
|
137
|
-
session.hasSession.value = result.hasSession;
|
|
138
|
-
session.isLoading.value = false;
|
|
139
|
-
if (result.hasSession) {
|
|
140
|
-
const marker = useAuthPresentCookie();
|
|
141
|
-
if (marker) {
|
|
142
|
-
marker.value = "1";
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const stored = readStored(session.jwtKey.value);
|
|
149
|
-
const applyToken = (token) => {
|
|
150
|
-
session.hasSession.value = token !== null;
|
|
151
|
-
session.isLoading.value = false;
|
|
152
|
-
if (token) {
|
|
153
|
-
setJwtCookie(token);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
if (stored instanceof Promise) {
|
|
157
|
-
session.isLoading.value = true;
|
|
158
|
-
void stored.then(applyToken).catch(() => {
|
|
159
|
-
session.hasSession.value = false;
|
|
160
|
-
session.isLoading.value = false;
|
|
161
|
-
});
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
applyToken(stored);
|
|
165
|
-
}
|
|
166
|
-
export async function getAuthToken({
|
|
167
|
-
forceRefreshToken
|
|
168
|
-
}) {
|
|
169
|
-
const session = useAuthSession();
|
|
170
|
-
const convexUrl = session.convexUrl;
|
|
171
|
-
if (!convexUrl) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
if (session.httpOnly) {
|
|
175
|
-
return withRefreshMutex(session.refreshKey.value, async () => {
|
|
176
|
-
if (!forceRefreshToken) {
|
|
177
|
-
const current = await fetchAuthSessionToken();
|
|
178
|
-
if (current.token) {
|
|
179
|
-
session.hasSession.value = true;
|
|
180
|
-
return current.token;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
const refreshed = await refreshAuthSession();
|
|
184
|
-
if (!refreshed) {
|
|
185
|
-
await clearAuthSession();
|
|
186
|
-
session.hasSession.value = false;
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
session.hasSession.value = true;
|
|
190
|
-
return refreshed;
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
if (!forceRefreshToken) {
|
|
194
|
-
return await readLocalAsync(session.jwtKey.value);
|
|
195
|
-
}
|
|
196
|
-
return withRefreshMutex(session.refreshKey.value, async () => {
|
|
197
|
-
const refreshToken = await readLocalAsync(session.refreshKey.value);
|
|
198
|
-
if (!refreshToken) {
|
|
199
|
-
await persistTokens(session, null);
|
|
200
|
-
return null;
|
|
36
|
+
await requireBinding("signOut").signOut();
|
|
37
|
+
}
|
|
38
|
+
export function useConvexSignIn(fn, options = {}) {
|
|
39
|
+
useBinding("useConvexSignIn");
|
|
40
|
+
const ctx = useConvexContext();
|
|
41
|
+
const pending = ref(false);
|
|
42
|
+
const run = async (args) => {
|
|
43
|
+
const binding = ctx.convexAuth;
|
|
44
|
+
if (!binding) {
|
|
45
|
+
throw new Error("[use-convex] useConvexSignIn().run is browser-only.");
|
|
201
46
|
}
|
|
47
|
+
pending.value = true;
|
|
202
48
|
try {
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
await
|
|
207
|
-
return null;
|
|
49
|
+
const call = options.kind === "action" ? binding.signInApi.action : binding.signInApi.mutation;
|
|
50
|
+
const result = await call(fn, args);
|
|
51
|
+
if (result?.status === "complete" && result.tokens) {
|
|
52
|
+
await binding.setSession(result.tokens);
|
|
208
53
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
function inBrowser() {
|
|
218
|
-
if (import.meta.client) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
return import.meta.client !== false && typeof window !== "undefined";
|
|
222
|
-
}
|
|
223
|
-
export function hasPendingOAuthCallback() {
|
|
224
|
-
if (!inBrowser()) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
const session = useAuthSession();
|
|
228
|
-
if (!session.convexUrl) {
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
const code = new URLSearchParams(window.location.search).get("code");
|
|
232
|
-
if (!code) {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
const gate = getConvexAuthClientOptions().shouldHandleCode;
|
|
236
|
-
if (gate === false || typeof gate === "function" && !gate()) {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
const verifier = readVerifier(session.verifierKey.value);
|
|
240
|
-
if (verifier instanceof Promise) {
|
|
241
|
-
return true;
|
|
242
|
-
}
|
|
243
|
-
return shouldConsumeOAuthCode({
|
|
244
|
-
code,
|
|
245
|
-
verifier,
|
|
246
|
-
shouldHandleCode: gate
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
export async function consumeOAuthCodeFromUrl() {
|
|
250
|
-
if (!inBrowser()) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
const session = useAuthSession();
|
|
254
|
-
if (!session.convexUrl) {
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
const code = new URLSearchParams(window.location.search).get("code");
|
|
258
|
-
const verifier = await readVerifierAsync(session.verifierKey.value);
|
|
259
|
-
const gate = getConvexAuthClientOptions().shouldHandleCode;
|
|
260
|
-
if (!shouldConsumeOAuthCode({
|
|
261
|
-
code,
|
|
262
|
-
verifier,
|
|
263
|
-
shouldHandleCode: gate
|
|
264
|
-
})) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
const url = new URL(window.location.href);
|
|
268
|
-
url.searchParams.delete("code");
|
|
269
|
-
const relativeUrl = url.pathname + url.search + url.hash;
|
|
270
|
-
const replaceURL = getConvexAuthClientOptions().replaceURL;
|
|
271
|
-
try {
|
|
272
|
-
if (replaceURL) {
|
|
273
|
-
await replaceURL(relativeUrl);
|
|
274
|
-
} else {
|
|
275
|
-
window.history.replaceState({}, "", relativeUrl);
|
|
54
|
+
return result;
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
return { status: "error", userError: { error: "OTHER_ERROR", cause } };
|
|
57
|
+
} finally {
|
|
58
|
+
pending.value = false;
|
|
276
59
|
}
|
|
277
|
-
} catch (cause) {
|
|
278
|
-
session.error.value = cause instanceof Error ? cause.message : "OAuth callback failed";
|
|
279
|
-
session.isLoading.value = false;
|
|
280
|
-
session.pending.value = false;
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
session.pending.value = true;
|
|
284
|
-
session.error.value = null;
|
|
285
|
-
session.isLoading.value = true;
|
|
286
|
-
try {
|
|
287
|
-
await writeVerifier(session.verifierKey.value, null);
|
|
288
|
-
const result = await callSignInWithRetry(session.convexUrl, {
|
|
289
|
-
params: { code },
|
|
290
|
-
verifier
|
|
291
|
-
});
|
|
292
|
-
await persistTokens(session, result.tokens ?? null);
|
|
293
|
-
return result.tokens != null;
|
|
294
|
-
} catch (cause) {
|
|
295
|
-
const message = cause instanceof Error ? cause.message : "OAuth callback failed";
|
|
296
|
-
session.error.value = message;
|
|
297
|
-
await persistTokens(session, null);
|
|
298
|
-
return false;
|
|
299
|
-
} finally {
|
|
300
|
-
session.pending.value = false;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
export function useAuthProviderState() {
|
|
304
|
-
const session = useAuthSession();
|
|
305
|
-
return {
|
|
306
|
-
isLoading: computed(() => session.isLoading.value),
|
|
307
|
-
hasSession: computed(() => session.hasSession.value)
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function useAuthSession() {
|
|
311
|
-
const config = useRuntimeConfig();
|
|
312
|
-
const convexConfig = config.public.convex;
|
|
313
|
-
const convexUrl = convexConfig?.url;
|
|
314
|
-
const authConfig = convexConfig?.auth;
|
|
315
|
-
const httpOnly = isHttpOnlyAuth(authConfig);
|
|
316
|
-
if (authConfig?.provider === "convex-auth") {
|
|
317
|
-
setConvexAuthModuleDefaults({
|
|
318
|
-
storageNamespace: authConfig.storageNamespace,
|
|
319
|
-
storage: authConfig.storage,
|
|
320
|
-
shouldHandleCode: authConfig.shouldHandleCode
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
const isLoading = useState("convex-auth-loading", () => true);
|
|
324
|
-
const hasSession = useState("convex-auth-has-session", () => false);
|
|
325
|
-
const error = useState("convex-auth-error", () => null);
|
|
326
|
-
const pending = useState("convex-auth-pending", () => false);
|
|
327
|
-
const namespace = getConvexAuthClientOptions().storageNamespace ?? authConfig?.storageNamespace ?? convexUrl;
|
|
328
|
-
const jwtKey = computed(
|
|
329
|
-
() => namespace ? storageKey(JWT_STORAGE_KEY, namespace) : JWT_STORAGE_KEY
|
|
330
|
-
);
|
|
331
|
-
const refreshKey = computed(
|
|
332
|
-
() => namespace ? storageKey(REFRESH_TOKEN_STORAGE_KEY, namespace) : REFRESH_TOKEN_STORAGE_KEY
|
|
333
|
-
);
|
|
334
|
-
const verifierKey = computed(
|
|
335
|
-
() => namespace ? storageKey(VERIFIER_STORAGE_KEY, namespace) : VERIFIER_STORAGE_KEY
|
|
336
|
-
);
|
|
337
|
-
return {
|
|
338
|
-
convexUrl,
|
|
339
|
-
httpOnly,
|
|
340
|
-
jwtKey,
|
|
341
|
-
refreshKey,
|
|
342
|
-
verifierKey,
|
|
343
|
-
isLoading,
|
|
344
|
-
hasSession,
|
|
345
|
-
error,
|
|
346
|
-
pending
|
|
347
60
|
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
method: "GET"
|
|
368
|
-
});
|
|
369
|
-
} catch {
|
|
370
|
-
return { hasSession: false };
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
async function fetchAuthSessionToken() {
|
|
374
|
-
try {
|
|
375
|
-
return await $fetch(AUTH_SESSION_PATH, {
|
|
376
|
-
method: "POST",
|
|
377
|
-
body: { getToken: true }
|
|
378
|
-
});
|
|
379
|
-
} catch {
|
|
380
|
-
return { hasSession: false, token: null };
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
async function refreshAuthSession() {
|
|
384
|
-
try {
|
|
385
|
-
const result = await $fetch(AUTH_SESSION_PATH, {
|
|
386
|
-
method: "POST",
|
|
387
|
-
body: { refresh: true }
|
|
388
|
-
});
|
|
389
|
-
return result.token;
|
|
390
|
-
} catch {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
async function clearAuthSession() {
|
|
395
|
-
try {
|
|
396
|
-
await $fetch(AUTH_SESSION_PATH, { method: "DELETE" });
|
|
397
|
-
} catch {
|
|
398
|
-
}
|
|
399
|
-
setPresentCookie(null);
|
|
400
|
-
}
|
|
401
|
-
async function writeHttpOnlySession(tokens) {
|
|
402
|
-
if (tokens === null) {
|
|
403
|
-
await clearAuthSession();
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
await $fetch(AUTH_SESSION_PATH, {
|
|
407
|
-
method: "POST",
|
|
408
|
-
body: {
|
|
409
|
-
token: tokens.token,
|
|
410
|
-
refreshToken: tokens.refreshToken
|
|
61
|
+
return { run, pending };
|
|
62
|
+
}
|
|
63
|
+
export function useOAuth() {
|
|
64
|
+
const binding = useBinding("useOAuth");
|
|
65
|
+
if (!binding) {
|
|
66
|
+
return { flowError: shallowRef(null) };
|
|
67
|
+
}
|
|
68
|
+
const value = binding.ambientValue(OAUTH_SETUP_ID, OAUTH_FLOW_ERROR_KEY);
|
|
69
|
+
return { flowError: computed(() => value.value ?? null) };
|
|
70
|
+
}
|
|
71
|
+
export function useOAuthSignIn(refs) {
|
|
72
|
+
const binding = useBinding("useOAuthSignIn");
|
|
73
|
+
const actions = binding?.ambientValue(OAUTH_SETUP_ID, OAUTH_ACTIONS_KEY);
|
|
74
|
+
const signIn = (async (options) => {
|
|
75
|
+
const current = actions?.value;
|
|
76
|
+
if (!current) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"[use-convex] OAuth is not registered. Include convexOAuth() in configureConvexAuth({ ambientSignIns })."
|
|
79
|
+
);
|
|
411
80
|
}
|
|
81
|
+
return await current.signIn(refs, options);
|
|
412
82
|
});
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
async function persistTokens(session, tokens) {
|
|
416
|
-
if (session.httpOnly) {
|
|
417
|
-
if (tokens === null) {
|
|
418
|
-
await writeHttpOnlySession(null);
|
|
419
|
-
await writeLocalAsync(session.jwtKey.value, null);
|
|
420
|
-
await writeLocalAsync(session.refreshKey.value, null);
|
|
421
|
-
session.hasSession.value = false;
|
|
422
|
-
session.isLoading.value = false;
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
session.hasSession.value = true;
|
|
426
|
-
session.isLoading.value = false;
|
|
427
|
-
await writeHttpOnlySession(tokens);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (tokens === null) {
|
|
431
|
-
await writeLocalAsync(session.jwtKey.value, null);
|
|
432
|
-
await writeLocalAsync(session.refreshKey.value, null);
|
|
433
|
-
setJwtCookie(null);
|
|
434
|
-
session.hasSession.value = false;
|
|
435
|
-
session.isLoading.value = false;
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
await writeLocalAsync(session.jwtKey.value, tokens.token);
|
|
439
|
-
await writeLocalAsync(session.refreshKey.value, tokens.refreshToken);
|
|
440
|
-
session.hasSession.value = true;
|
|
441
|
-
session.isLoading.value = false;
|
|
442
|
-
nextTick(() => {
|
|
443
|
-
setJwtCookie(tokens.token);
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
async function callSignIn(convexUrl, args) {
|
|
447
|
-
const http = new ConvexHttpClient(convexUrl);
|
|
448
|
-
return await http.action(authSignIn, args);
|
|
449
|
-
}
|
|
450
|
-
async function callSignInWithRetry(convexUrl, args) {
|
|
451
|
-
let lastError;
|
|
452
|
-
let retry = 0;
|
|
453
|
-
while (retry < RETRY_BACKOFF.length) {
|
|
454
|
-
try {
|
|
455
|
-
return await callSignIn(convexUrl, args);
|
|
456
|
-
} catch (e) {
|
|
457
|
-
lastError = e;
|
|
458
|
-
if (!isNetworkError(e)) {
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
const wait = RETRY_BACKOFF[retry] + RETRY_JITTER * Math.random();
|
|
462
|
-
retry++;
|
|
463
|
-
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
throw lastError;
|
|
467
|
-
}
|
|
468
|
-
function isNetworkError(error) {
|
|
469
|
-
if (error instanceof TypeError) {
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
if (error instanceof Error && /network|fetch|Failed to fetch/i.test(error.message)) {
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
return false;
|
|
83
|
+
return { signIn };
|
|
476
84
|
}
|
|
@@ -4,8 +4,8 @@ import { type ComputedRef } from 'vue';
|
|
|
4
4
|
* Current JWT for authenticated HTTP calls (React `@convex-dev/auth`
|
|
5
5
|
* `useAuthToken` parity).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* (
|
|
7
|
+
* Uses the Convex Auth client token when Convex Auth is enabled; otherwise
|
|
8
|
+
* `ConvexClient.getAuth()`, then the configured BYO `fetchToken`. Returns `null` when signed out or unavailable.
|
|
9
9
|
*/
|
|
10
10
|
export declare function useAuthToken(): ComputedRef<string | null>;
|
|
11
11
|
/**
|
|
@@ -9,6 +9,10 @@ export function useAuthToken() {
|
|
|
9
9
|
return computed(() => null);
|
|
10
10
|
}
|
|
11
11
|
const auth = useConvexAuth();
|
|
12
|
+
const binding = ctx.convexAuth;
|
|
13
|
+
if (binding) {
|
|
14
|
+
return computed(() => binding.state.value.token);
|
|
15
|
+
}
|
|
12
16
|
const refresh = async () => {
|
|
13
17
|
if (!ctx.client) {
|
|
14
18
|
token.value = null;
|
|
@@ -61,7 +61,7 @@ export function useConvexAuth(setup) {
|
|
|
61
61
|
);
|
|
62
62
|
};
|
|
63
63
|
watch(
|
|
64
|
-
() =>
|
|
64
|
+
[() => toValue(setup.isLoading) ?? false, () => toValue(setup.isAuthenticated) ?? true],
|
|
65
65
|
syncFromProvider,
|
|
66
66
|
{ immediate: true }
|
|
67
67
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionKeyFromToken } from "../utils/authStorage.js";
|
|
1
2
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
2
3
|
import { useAsyncData, useRuntimeConfig } from "nuxt/app";
|
|
3
4
|
import {
|
|
@@ -60,7 +61,11 @@ export async function useConvexPaginatedQuery(query, args = {}, options) {
|
|
|
60
61
|
server,
|
|
61
62
|
watch: [
|
|
62
63
|
() => toValue(args),
|
|
63
|
-
()
|
|
64
|
+
// Session identity (JWT iss + sub), not the raw token: Convex Auth
|
|
65
|
+
// rotates the access token every minute without changing session,
|
|
66
|
+
// but switching accounts must re-run the snapshot (`live: false`
|
|
67
|
+
// and BYO cookies included).
|
|
68
|
+
() => sessionKeyFromToken(options.token ?? ctx.ssrToken.value),
|
|
64
69
|
() => requireAuth && ctx.auth.isAuthenticated.value && !!(options.token ?? ctx.ssrToken.value)
|
|
65
70
|
]
|
|
66
71
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionKeyFromToken } from "../utils/authStorage.js";
|
|
1
2
|
import { convexToJson, jsonToConvex } from "convex/values";
|
|
2
3
|
import { useAsyncData, useRuntimeConfig } from "nuxt/app";
|
|
3
4
|
import {
|
|
@@ -47,7 +48,11 @@ export async function useConvexQuery(query, args = {}, options = {}) {
|
|
|
47
48
|
() => toValue(args),
|
|
48
49
|
// Re-run HttpClient snapshot when the SSR JWT cookie / token changes
|
|
49
50
|
// (e.g. after sign-in with live:false, or Refresh).
|
|
50
|
-
()
|
|
51
|
+
// Session identity (JWT iss + sub), not the raw token: Convex Auth
|
|
52
|
+
// rotates the access token every minute without changing session,
|
|
53
|
+
// but switching accounts must re-run the snapshot (`live: false`
|
|
54
|
+
// and BYO cookies included).
|
|
55
|
+
() => sessionKeyFromToken(options.token ?? ctx.ssrToken.value),
|
|
51
56
|
// Re-run when auth confirms *and* a JWT is available for HttpClient
|
|
52
57
|
// (readable cookie / SSR). Avoids anonymous client refetches with HttpOnly.
|
|
53
58
|
() => requireAuth && ctx.auth.isAuthenticated.value && !!(options.token ?? ctx.ssrToken.value)
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* First-party Convex Auth wiring (
|
|
2
|
+
* First-party Convex Auth v2 wiring (`convex.auth.provider: 'convex-auth'`).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Builds an `AuthClient` from `@convex-dev/auth/browser` in SSR mode (refresh
|
|
5
|
+
* token in an httpOnly cookie, sign-ins through the Nitro auth proxy) or SPA
|
|
6
|
+
* mode (tokens in browser storage, sign-ins straight to the deployment),
|
|
7
|
+
* then hands its token to `useConvexAuth` and watches `isAuthenticated` so a
|
|
8
|
+
* session revoked server-side signs the client out.
|
|
9
|
+
*
|
|
10
|
+
* Setup stays synchronous so `useConvexAuth` keeps the Nuxt app context.
|
|
6
11
|
*/
|
|
7
12
|
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
8
13
|
export default _default;
|