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.
Files changed (51) hide show
  1. package/README.md +104 -59
  2. package/dist/module.d.mts +62 -39
  3. package/dist/module.mjs +68 -17
  4. package/dist/runtime/auth/oauth.d.ts +96 -0
  5. package/dist/runtime/auth/oauth.js +170 -0
  6. package/dist/runtime/auth/sessionRoutes.d.ts +37 -0
  7. package/dist/runtime/auth/sessionRoutes.js +41 -0
  8. package/dist/runtime/composables/useAuth.d.ts +81 -48
  9. package/dist/runtime/composables/useAuth.js +69 -461
  10. package/dist/runtime/composables/useAuthToken.d.ts +2 -2
  11. package/dist/runtime/composables/useAuthToken.js +4 -0
  12. package/dist/runtime/composables/useConvexAuth.js +1 -1
  13. package/dist/runtime/composables/useConvexPaginatedQuery.js +6 -1
  14. package/dist/runtime/composables/useConvexQuery.js +6 -1
  15. package/dist/runtime/plugin.auth.client.d.ts +8 -3
  16. package/dist/runtime/plugin.auth.client.js +207 -31
  17. package/dist/runtime/plugin.server.js +8 -2
  18. package/dist/runtime/server/auth.d.ts +7 -2
  19. package/dist/runtime/server/auth.js +14 -3
  20. package/dist/runtime/server/convexAuth/refresh.d.ts +3 -0
  21. package/dist/runtime/server/convexAuth/refresh.js +5 -0
  22. package/dist/runtime/server/convexAuth/session.middleware.d.ts +10 -0
  23. package/dist/runtime/server/convexAuth/session.middleware.js +37 -0
  24. package/dist/runtime/server/convexAuth/setup.d.ts +21 -0
  25. package/dist/runtime/server/convexAuth/setup.js +90 -0
  26. package/dist/runtime/server/convexAuth/signin.d.ts +3 -0
  27. package/dist/runtime/server/convexAuth/signin.js +5 -0
  28. package/dist/runtime/server/convexAuth/signout.d.ts +3 -0
  29. package/dist/runtime/server/convexAuth/signout.js +5 -0
  30. package/dist/runtime/server/convexConfig.d.ts +2 -6
  31. package/dist/runtime/server/fetch.d.ts +2 -1
  32. package/dist/runtime/server/fetch.js +3 -8
  33. package/dist/runtime/utils/authClientOptions.d.ts +33 -40
  34. package/dist/runtime/utils/authClientOptions.js +1 -64
  35. package/dist/runtime/utils/authCookie.d.ts +8 -14
  36. package/dist/runtime/utils/authCookie.js +23 -27
  37. package/dist/runtime/utils/authStorage.d.ts +76 -79
  38. package/dist/runtime/utils/authStorage.js +60 -141
  39. package/dist/runtime/utils/context.d.ts +40 -3
  40. package/dist/types.d.mts +4 -2
  41. package/package.json +10 -5
  42. package/dist/runtime/server/api/convex/auth/session.d.ts +0 -36
  43. package/dist/runtime/server/api/convex/auth/session.js +0 -90
  44. package/dist/runtime/server/authCookies.d.ts +0 -9
  45. package/dist/runtime/server/authCookies.js +0 -64
  46. package/dist/runtime/server/sameOrigin.d.ts +0 -14
  47. package/dist/runtime/server/sameOrigin.js +0 -29
  48. package/dist/runtime/utils/authMutex.d.ts +0 -15
  49. package/dist/runtime/utils/authMutex.js +0 -47
  50. package/dist/runtime/utils/oauthRedirect.d.ts +0 -5
  51. package/dist/runtime/utils/oauthRedirect.js +0 -13
@@ -1,5 +1,6 @@
1
1
  import type { AuthTokenFetcher, ConvexClient, ConvexHttpClient } from 'convex/browser';
2
- import type { ComputedRef, InjectionKey, Ref } from 'vue';
2
+ import type { FunctionReference } from 'convex/server';
3
+ import type { ComputedRef, InjectionKey, Ref, ShallowRef } from 'vue';
3
4
  import type { CreateHttpClientOptions } from './http.js';
4
5
  export interface ConvexAuthContext {
5
6
  /**
@@ -19,6 +20,36 @@ export interface ConvexAuthContext {
19
20
  isAuthenticated: Ref<boolean>;
20
21
  isRefreshing: Ref<boolean>;
21
22
  }
23
+ /** Snapshot of the Convex Auth client (`AuthState` in `@convex-dev/auth`). */
24
+ export interface ConvexAuthSnapshot {
25
+ isLoading: boolean;
26
+ isAuthenticated: boolean;
27
+ token: string | null;
28
+ }
29
+ /**
30
+ * Runs a method's sign-in function. Direct to the deployment in SPA mode,
31
+ * through the Nitro auth proxy in SSR mode (which moves the refresh token
32
+ * into an httpOnly cookie).
33
+ */
34
+ export interface ConvexAuthSignInApi {
35
+ mutation: (fn: FunctionReference<'mutation'>, args: Record<string, unknown>) => Promise<any>;
36
+ action: (fn: FunctionReference<'action'>, args: Record<string, unknown>) => Promise<any>;
37
+ }
38
+ /** First-party Convex Auth v2 binding, set by the auth client plugin. */
39
+ export interface ConvexAuthBinding {
40
+ mode: 'ssr' | 'spa';
41
+ state: ShallowRef<ConvexAuthSnapshot>;
42
+ signInApi: ConvexAuthSignInApi;
43
+ /** Adopt a session returned by a method's sign-in function. */
44
+ setSession: (session: {
45
+ accessToken: string;
46
+ userId: string;
47
+ [key: string]: unknown;
48
+ }) => Promise<void>;
49
+ signOut: () => Promise<void>;
50
+ /** Values an ambient sign-in (e.g. OAuth) publishes, read reactively. */
51
+ ambientValue: <T>(id: string, key: string) => Readonly<Ref<T | undefined>>;
52
+ }
22
53
  export interface ConvexNuxtContext {
23
54
  url: string;
24
55
  /**
@@ -26,14 +57,20 @@ export interface ConvexNuxtContext {
26
57
  */
27
58
  client: ConvexClient | null;
28
59
  /**
29
- * JWT from the optional auth cookie. Reactive so sign-in/out updates
30
- * HttpClient refreshes and `hasSsrSession`.
60
+ * JWT for HttpClient snapshots: the SSR session token (Convex Auth SSR
61
+ * mode or a BYO cookie), then the live Convex Auth access token on the
62
+ * client. Reactive so sign-in/out re-runs HttpClient fetches.
31
63
  */
32
64
  ssrToken: ComputedRef<string | undefined>;
33
65
  /**
34
66
  * Shared auth state for useConvexAuth.
35
67
  */
36
68
  auth: ConvexAuthContext;
69
+ /**
70
+ * Convex Auth v2 client binding (`convex.auth.provider: 'convex-auth'`,
71
+ * browser only). `null` on the server and for BYO providers.
72
+ */
73
+ convexAuth?: ConvexAuthBinding | null;
37
74
  /**
38
75
  * Create a fresh HttpClient for one-shot fetches (SSR / Nitro / refresh).
39
76
  * Prefer a new client per call — HttpClient is stateful.
package/dist/types.d.mts CHANGED
@@ -4,7 +4,7 @@ declare module '@nuxt/schema' {
4
4
  interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
5
5
  }
6
6
 
7
- export { type AuthTokens, type SignInResult, type UseAuthReturn } from '../dist/runtime/composables/useAuth.js'
7
+ export { type ConvexAuthSession, type ConvexSignInResult, type OAuthCompleteResult, type OAuthFlowError, type OAuthProviderRefs, type OAuthSignInOptions, type OAuthStartResult, type UseAuthReturn } from '../dist/runtime/composables/useAuth.js'
8
8
 
9
9
  export { type ConvexQueryArgs, type UseConvexQueryOptions, type UseConvexQueryReturn } from '../dist/runtime/composables/useConvexQuery.js'
10
10
 
@@ -30,7 +30,9 @@ export { type ConnectionState } from '../dist/runtime/composables/useConvexConne
30
30
 
31
31
  export { type ConvexGate } from '../dist/runtime/composables/useConvexGate.js'
32
32
 
33
- export { type ConvexAuthClientOptions, type ConvexAuthStorageMode, type ConvexAuthTokenStorage, type configureConvexAuth } from '../dist/runtime/utils/authClientOptions.js'
33
+ export { type ConvexAmbientSignIn, type ConvexAuthClientOptions, type ConvexAuthStorageMode, type ConvexAuthTokenStorage, type configureConvexAuth } from '../dist/runtime/utils/authClientOptions.js'
34
+
35
+ export { type ConvexAuthServerRuntimeConfig } from '../dist/runtime/utils/authStorage.js'
34
36
 
35
37
  export { default } from './module.mjs'
36
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-convex",
3
- "version": "1.3.0",
3
+ "version": "2.0.0",
4
4
  "description": "First-class Convex integration for Nuxt",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,13 +44,12 @@
44
44
  "h3": "^1.15.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@auth/core": "0.41.1",
48
- "@convex-dev/auth": "^0.0.95",
47
+ "@convex-dev/auth": "^2.0.0",
49
48
  "@nuxt/module-builder": "^1.0.0",
50
49
  "@nuxt/schema": "^4.0.0",
51
50
  "@nuxt/test-utils": "^3.23.0",
52
51
  "@types/node": "^26.5.0",
53
- "convex": "^1.46.0",
52
+ "convex": "^1.48.0",
54
53
  "convex-test": "^0.0.56",
55
54
  "happy-dom": "^20.14.0",
56
55
  "nitropack": "^2.13.0",
@@ -63,10 +62,16 @@
63
62
  "vue-tsc": "^3.0.0"
64
63
  },
65
64
  "peerDependencies": {
66
- "convex": "^1.46.0",
65
+ "@convex-dev/auth": "^2.0.0",
66
+ "convex": "^1.48.0",
67
67
  "nuxt": "^4.0.0",
68
68
  "vue": "^3.5.0"
69
69
  },
70
+ "peerDependenciesMeta": {
71
+ "@convex-dev/auth": {
72
+ "optional": true
73
+ }
74
+ },
70
75
  "packageManager": "pnpm@10.0.0",
71
76
  "pnpm": {
72
77
  "overrides": {
@@ -1,36 +0,0 @@
1
- /**
2
- * Persist / read / refresh HttpOnly auth cookies.
3
- *
4
- * - GET → `{ hasSession }` only (no JWT in the response body)
5
- * - POST `{ getToken: true }` → `{ hasSession, token }` for ConvexClient.setAuth
6
- * - POST `{ refresh: true }` → refresh cookies, return `{ token }`
7
- * - POST `{ token, refreshToken }` → set cookies
8
- * - DELETE → clear cookies
9
- *
10
- * All methods require same-origin (fail-closed). Returning the JWT only via
11
- * POST reduces accidental leakage via prefetch/logs; XSS can still obtain a
12
- * token once ConvexClient needs it in memory — HttpOnly mitigates cookie theft,
13
- * not XSS session theft.
14
- */
15
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
16
- hasSession: boolean;
17
- ok?: undefined;
18
- token?: undefined;
19
- } | {
20
- ok: boolean;
21
- hasSession?: undefined;
22
- token?: undefined;
23
- } | {
24
- hasSession: boolean;
25
- token: string | null;
26
- ok?: undefined;
27
- } | {
28
- token: null;
29
- hasSession?: undefined;
30
- ok?: undefined;
31
- } | {
32
- token: string;
33
- hasSession?: undefined;
34
- ok?: undefined;
35
- }>>;
36
- export default _default;
@@ -1,90 +0,0 @@
1
- import { ConvexHttpClient } from "convex/browser";
2
- import { makeFunctionReference } from "convex/server";
3
- import { createError, defineEventHandler, readBody } from "h3";
4
- import { useRuntimeConfig } from "nitropack/runtime";
5
- import { readHttpOnlyJwt, readHttpOnlyRefresh, setAuthCookies } from "../../../authCookies.js";
6
- import { isHttpOnlyAuth } from "../../../../utils/authStorage.js";
7
- import { assertSameOrigin } from "../../../sameOrigin.js";
8
- import { MISSING_URL_HINT } from "../../../../utils/errors.js";
9
- const authSignIn = makeFunctionReference(
10
- "auth:signIn"
11
- );
12
- function convexUrl(event) {
13
- const config = useRuntimeConfig(event);
14
- const url = config.public?.convex?.url ?? process.env.NUXT_PUBLIC_CONVEX_URL;
15
- if (!url) {
16
- throw createError({
17
- statusCode: 500,
18
- message: MISSING_URL_HINT
19
- });
20
- }
21
- return url;
22
- }
23
- function requireHttpOnly(event) {
24
- const config = useRuntimeConfig(event);
25
- const auth = config.public?.convex?.auth;
26
- if (!isHttpOnlyAuth(auth)) {
27
- throw createError({
28
- statusCode: 404,
29
- message: "HttpOnly auth is not enabled"
30
- });
31
- }
32
- }
33
- export default defineEventHandler(async (event) => {
34
- requireHttpOnly(event);
35
- assertSameOrigin(event);
36
- if (event.method === "GET") {
37
- const token = readHttpOnlyJwt(event);
38
- return {
39
- hasSession: !!token
40
- };
41
- }
42
- if (event.method === "DELETE") {
43
- setAuthCookies(event, { token: null });
44
- return { ok: true };
45
- }
46
- if (event.method === "POST") {
47
- const body = await readBody(event);
48
- if (body?.getToken) {
49
- const token = readHttpOnlyJwt(event);
50
- return {
51
- hasSession: !!token,
52
- token: token ?? null
53
- };
54
- }
55
- if (body?.refresh) {
56
- const refreshToken = readHttpOnlyRefresh(event);
57
- if (!refreshToken) {
58
- setAuthCookies(event, { token: null });
59
- return { token: null };
60
- }
61
- const http = new ConvexHttpClient(convexUrl(event));
62
- try {
63
- const result = await http.action(authSignIn, { refreshToken });
64
- const tokens = result.tokens ?? null;
65
- if (!tokens) {
66
- setAuthCookies(event, { token: null });
67
- return { token: null };
68
- }
69
- setAuthCookies(event, {
70
- token: tokens.token,
71
- refreshToken: tokens.refreshToken
72
- });
73
- return { token: tokens.token };
74
- } catch {
75
- setAuthCookies(event, { token: null });
76
- return { token: null };
77
- }
78
- }
79
- if (!body?.token) {
80
- setAuthCookies(event, { token: null });
81
- return { ok: true };
82
- }
83
- setAuthCookies(event, {
84
- token: body.token,
85
- refreshToken: body.refreshToken ?? null
86
- });
87
- return { ok: true };
88
- }
89
- throw createError({ statusCode: 405, message: "Method not allowed" });
90
- });
@@ -1,9 +0,0 @@
1
- import type { H3Event } from 'h3';
2
- export interface AuthCookieSet {
3
- token: string | null;
4
- refreshToken?: string | null;
5
- }
6
- /** Persist or clear HttpOnly auth cookies + readable presence marker. */
7
- export declare function setAuthCookies(event: H3Event, tokens: AuthCookieSet): void;
8
- export declare function readHttpOnlyJwt(event: H3Event): string | undefined;
9
- export declare function readHttpOnlyRefresh(event: H3Event): string | undefined;
@@ -1,64 +0,0 @@
1
- import { deleteCookie, getCookie, getRequestHeader, setCookie } from "h3";
2
- import {
3
- AUTH_JWT_COOKIE_MAX_AGE,
4
- isHttpOnlyAuth,
5
- resolveAuthCookieName,
6
- resolveAuthPresentCookieName,
7
- resolveAuthRefreshCookieName
8
- } from "../utils/authStorage.js";
9
- import { readConvexConfig } from "./convexConfig.js";
10
- function readAuthConfig(event) {
11
- return readConvexConfig(event)?.auth;
12
- }
13
- function isLocalHost(event) {
14
- const host = getRequestHeader(event, "host") ?? "";
15
- return host.startsWith("localhost") || host.startsWith("127.0.0.1") || host.startsWith("[::1]");
16
- }
17
- function cookieOptions(event, httpOnly) {
18
- const local = isLocalHost(event);
19
- return {
20
- httpOnly,
21
- secure: !local,
22
- sameSite: "lax",
23
- path: "/",
24
- maxAge: AUTH_JWT_COOKIE_MAX_AGE
25
- };
26
- }
27
- export function setAuthCookies(event, tokens) {
28
- const auth = readAuthConfig(event);
29
- if (!isHttpOnlyAuth(auth)) {
30
- throw new Error("[use-convex] setAuthCookies requires convex.auth.httpOnly: true");
31
- }
32
- const jwtName = resolveAuthCookieName(auth);
33
- const refreshName = resolveAuthRefreshCookieName(auth);
34
- const presentName = resolveAuthPresentCookieName(auth);
35
- const httpOpts = cookieOptions(event, true);
36
- const presentOpts = cookieOptions(event, false);
37
- if (!tokens.token) {
38
- deleteCookie(event, jwtName, httpOpts);
39
- deleteCookie(event, refreshName, httpOpts);
40
- deleteCookie(event, presentName, presentOpts);
41
- return;
42
- }
43
- setCookie(event, jwtName, tokens.token, httpOpts);
44
- if (tokens.refreshToken) {
45
- setCookie(event, refreshName, tokens.refreshToken, httpOpts);
46
- }
47
- setCookie(event, presentName, "1", presentOpts);
48
- }
49
- export function readHttpOnlyJwt(event) {
50
- const auth = readAuthConfig(event);
51
- const name = resolveAuthCookieName(auth);
52
- if (!name) {
53
- return void 0;
54
- }
55
- return getCookie(event, name) || void 0;
56
- }
57
- export function readHttpOnlyRefresh(event) {
58
- const auth = readAuthConfig(event);
59
- const name = resolveAuthRefreshCookieName(auth);
60
- if (!name) {
61
- return void 0;
62
- }
63
- return getCookie(event, name) || void 0;
64
- }
@@ -1,14 +0,0 @@
1
- import { type H3Event } from 'h3';
2
- /**
3
- * Fail-closed same-origin guard for cookie-bearing auth routes.
4
- *
5
- * Accepts the request when:
6
- * - `Sec-Fetch-Site` is `same-origin` or `none` (user-initiated / same origin), or
7
- * - `Origin` matches the trusted request origin (scheme + host), using
8
- * `Host` and `getRequestProtocol` (honors `X-Forwarded-Proto` behind proxies).
9
- *
10
- * Rejects when `Sec-Fetch-Site` is `cross-site` / `same-site`, or when neither
11
- * signal can confirm same-origin. Missing both headers is rejected.
12
- * Scheme-mismatched Origins (e.g. `http://` vs HTTPS request) are rejected.
13
- */
14
- export declare function assertSameOrigin(event: H3Event): void;
@@ -1,29 +0,0 @@
1
- import { createError, getRequestHeader, getRequestProtocol } from "h3";
2
- export function assertSameOrigin(event) {
3
- const secFetchSite = getRequestHeader(event, "sec-fetch-site");
4
- if (secFetchSite === "same-origin" || secFetchSite === "none") {
5
- return;
6
- }
7
- if (secFetchSite === "cross-site" || secFetchSite === "same-site") {
8
- throw createError({
9
- statusCode: 403,
10
- message: "Forbidden"
11
- });
12
- }
13
- const origin = getRequestHeader(event, "origin");
14
- const host = getRequestHeader(event, "host");
15
- if (origin && host) {
16
- try {
17
- const originUrl = new URL(origin);
18
- const protocol = getRequestProtocol(event, { xForwardedProto: true });
19
- if (originUrl.protocol === `${protocol}:` && originUrl.host === host) {
20
- return;
21
- }
22
- } catch {
23
- }
24
- }
25
- throw createError({
26
- statusCode: 403,
27
- message: "Forbidden"
28
- });
29
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Single-flight mutex for token refresh (mirrors @convex-dev/auth/react).
3
- * Prefers `navigator.locks` when available; falls back to an in-memory queue.
4
- */
5
- type MutexState = {
6
- currentlyRunning: Promise<void> | null;
7
- waiting: Array<() => Promise<void>>;
8
- };
9
- declare global {
10
- var __convexNuxtAuthMutexes: Record<string, MutexState> | undefined;
11
- }
12
- export declare function withRefreshMutex<T>(key: string, callback: () => Promise<T>): Promise<T>;
13
- /** Test helper — clear in-memory mutex state between tests. */
14
- export declare function resetAuthMutexesForTests(): void;
15
- export {};
@@ -1,47 +0,0 @@
1
- export async function withRefreshMutex(key, callback) {
2
- const lockManager = typeof navigator !== "undefined" ? navigator.locks : void 0;
3
- if (lockManager !== void 0) {
4
- return await lockManager.request(key, callback);
5
- }
6
- return await manualMutex(key, callback);
7
- }
8
- async function manualMutex(key, callback) {
9
- return await new Promise((resolve, reject) => {
10
- const wrapped = () => callback().then(resolve).catch(reject).then(() => void 0);
11
- void enqueueCallbackForMutex(key, wrapped);
12
- });
13
- }
14
- function getMutexValue(key) {
15
- if (globalThis.__convexNuxtAuthMutexes === void 0) {
16
- globalThis.__convexNuxtAuthMutexes = {};
17
- }
18
- let mutex = globalThis.__convexNuxtAuthMutexes[key];
19
- if (mutex === void 0) {
20
- mutex = { currentlyRunning: null, waiting: [] };
21
- globalThis.__convexNuxtAuthMutexes[key] = mutex;
22
- }
23
- return mutex;
24
- }
25
- async function enqueueCallbackForMutex(key, callback) {
26
- const mutex = getMutexValue(key);
27
- if (mutex.currentlyRunning === null) {
28
- globalThis.__convexNuxtAuthMutexes[key] = {
29
- currentlyRunning: callback().finally(() => {
30
- const next = getMutexValue(key).waiting.shift();
31
- getMutexValue(key).currentlyRunning = null;
32
- if (next !== void 0) {
33
- void enqueueCallbackForMutex(key, next);
34
- }
35
- }),
36
- waiting: []
37
- };
38
- } else {
39
- globalThis.__convexNuxtAuthMutexes[key] = {
40
- ...mutex,
41
- waiting: [...mutex.waiting, callback]
42
- };
43
- }
44
- }
45
- export function resetAuthMutexesForTests() {
46
- globalThis.__convexNuxtAuthMutexes = void 0;
47
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Validate an OAuth redirect from Convex `auth:signIn`.
3
- * Only http(s) absolute URLs are allowed (blocks javascript:/data: etc.).
4
- */
5
- export declare function parseOAuthRedirect(redirect: string): URL;
@@ -1,13 +0,0 @@
1
- import { useConvexError } from "./errors.js";
2
- export function parseOAuthRedirect(redirect) {
3
- let url;
4
- try {
5
- url = new URL(redirect);
6
- } catch {
7
- throw useConvexError("Invalid OAuth redirect URL");
8
- }
9
- if (url.protocol !== "http:" && url.protocol !== "https:") {
10
- throw useConvexError("OAuth redirect must be http(s)");
11
- }
12
- return url;
13
- }