nuxt-convex-module 0.0.0 → 0.9.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/LICENSE +1 -1
- package/README.md +51 -157
- package/dist/chunks/index.mjs +1 -1
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/71bSMC-_.js +2 -0
- package/dist/devtools-client/_nuxt/{CZbAGcNC.js → BAaUCl_m.js} +1 -1
- package/dist/devtools-client/_nuxt/BhmjAr2Q.js +1 -0
- package/dist/devtools-client/_nuxt/BmBQNDID.js +1 -0
- package/dist/devtools-client/_nuxt/C6YdTgGl.js +2 -0
- package/dist/devtools-client/_nuxt/{DPWXUmcI.js → CKakiU_h.js} +1 -1
- package/dist/devtools-client/_nuxt/Cm7YqZQ-.js +1 -0
- package/dist/devtools-client/_nuxt/{dy_xXcv9.js → DQmVr-EY.js} +1 -1
- package/dist/devtools-client/_nuxt/D_Rij7MD.js +1 -0
- package/dist/devtools-client/_nuxt/EZ4-8Xzm.js +3 -0
- package/dist/devtools-client/_nuxt/Kba0XpjH.js +1 -0
- package/dist/devtools-client/_nuxt/OVE7s6ty.js +1 -0
- package/dist/devtools-client/_nuxt/_Lf3mfPR.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/7dfea6da-3eca-4a35-ba06-5e069ac7e8df.json +1 -0
- package/dist/devtools-client/_nuxt/entry.HF8C6P11.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CLlwiMp-.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.Bxr6fpDU.css +1 -0
- package/dist/devtools-client/_nuxt/queries.DNBUjWk2.css +1 -0
- package/dist/devtools-client/_nuxt/vZZlEX5A.js +1 -0
- package/dist/devtools-client/auth/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/logs/index.html +1 -1
- package/dist/devtools-client/queries/index.html +1 -1
- package/dist/module.d.mts +129 -0
- package/dist/module.json +2 -2
- package/dist/module.mjs +576 -0
- package/dist/runtime/auth0/vue/index.d.ts +3 -3
- package/dist/runtime/better-auth/nuxt/middleware.d.ts +4 -2
- package/dist/runtime/better-auth/nuxt/middleware.js +15 -12
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +18 -2
- package/dist/runtime/better-auth/nuxt/proxy.js +7 -7
- package/dist/runtime/better-auth/nuxt/server.d.ts +6 -6
- package/dist/runtime/better-auth/nuxt/server.js +15 -0
- package/dist/runtime/better-auth/vue/auth-boundary.d.ts +3 -1
- package/dist/runtime/better-auth/vue/client.d.ts +1 -1
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +4 -5
- package/dist/runtime/better-auth/vue/cross-domain.js +11 -4
- package/dist/runtime/better-auth/vue/hydration.d.ts +1 -1
- package/dist/runtime/better-auth/vue/index.d.ts +2 -1
- package/dist/runtime/better-auth/vue/index.js +2 -1
- package/dist/runtime/better-auth/vue/plugin.client.d.ts +1 -5
- package/dist/runtime/better-auth/vue/plugin.client.js +6 -10
- package/dist/runtime/better-auth/vue/plugin.server.d.ts +5 -16
- package/dist/runtime/better-auth/vue/plugin.server.js +3 -6
- package/dist/runtime/better-auth/vue/redirect.d.ts +33 -0
- package/dist/runtime/better-auth/vue/redirect.js +14 -0
- package/dist/runtime/better-auth/vue/{use-auth.d.ts → use-better-auth.d.ts} +31 -11
- package/dist/runtime/better-auth/vue/{use-auth.js → use-better-auth.js} +4 -3
- package/dist/runtime/clerk/vue/index.d.ts +4 -4
- package/dist/runtime/devtools/bridge.js +1 -1
- package/dist/runtime/nuxt/app.d.ts +48 -0
- package/dist/runtime/nuxt/app.js +8 -0
- package/dist/runtime/nuxt/composables/use-async-paginated-query.d.ts +88 -0
- package/dist/runtime/nuxt/composables/use-async-paginated-query.js +90 -0
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +7 -16
- package/dist/runtime/nuxt/composables/use-async-query.js +14 -9
- package/dist/runtime/nuxt/config.d.ts +11 -0
- package/dist/runtime/nuxt/config.js +1 -0
- package/dist/runtime/nuxt/csp.d.ts +44 -0
- package/dist/runtime/nuxt/csp.js +46 -0
- package/dist/runtime/nuxt/index.d.ts +8 -8
- package/dist/runtime/nuxt/security.d.ts +33 -0
- package/dist/runtime/nuxt/security.js +14 -0
- package/dist/runtime/polar/vue/components.d.ts +8 -1
- package/dist/runtime/polar/vue/components.js +3 -3
- package/dist/runtime/vue/auth/index.d.ts +4 -4
- package/dist/runtime/vue/auth/index.js +6 -2
- package/dist/runtime/vue/client.d.ts +5 -55
- package/dist/runtime/vue/client.js +4 -0
- package/dist/runtime/vue/components/convex-image.d.ts +50 -0
- package/dist/runtime/vue/components/convex-image.js +21 -0
- package/dist/runtime/vue/composables/use-connection-state.d.ts +1 -0
- package/dist/runtime/vue/composables/use-paginated-query.d.ts +5 -1
- package/dist/runtime/vue/composables/use-paginated-query.js +2 -2
- package/dist/runtime/vue/composables/use-query.d.ts +2 -0
- package/dist/runtime/vue/composables/use-subscription.d.ts +1 -15
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +2 -3
- package/dist/runtime/vue/hydration.d.ts +3 -24
- package/dist/runtime/vue/index.d.ts +2 -2
- package/dist/runtime/vue/index.js +1 -0
- package/dist/runtime/vue/plugin.js +1 -1
- package/dist/runtime/vue/provide.d.ts +5 -5
- package/dist/types.d.mts +3 -0
- package/package.json +103 -56
- package/dist/devtools-client/_nuxt/B29GGJ5n.js +0 -1
- package/dist/devtools-client/_nuxt/BSDEpQet.js +0 -1
- package/dist/devtools-client/_nuxt/BY23OQOo.js +0 -1
- package/dist/devtools-client/_nuxt/BZYRaL-G.js +0 -1
- package/dist/devtools-client/_nuxt/Byw6FxnC.js +0 -1
- package/dist/devtools-client/_nuxt/CJJ_hcZN.js +0 -1
- package/dist/devtools-client/_nuxt/CS6WMFOx.js +0 -2
- package/dist/devtools-client/_nuxt/Cr6_aklh.js +0 -2
- package/dist/devtools-client/_nuxt/DNfpyBVU.js +0 -3
- package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +0 -1
- package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +0 -1
- package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +0 -1
- package/dist/devtools-client/_nuxt/ncgEdbEt.js +0 -1
- package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +0 -1
- package/dist/module.d.ts +0 -189
- package/dist/module.js +0 -489
- package/dist/types.d.ts +0 -3
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Proxy all
|
|
3
|
-
* This keeps auth cookies on the same origin (no CORS issues).
|
|
2
|
+
* Proxy all `${convex.authRoute}/**` requests (default `/api/auth/**`) to the
|
|
3
|
+
* Convex site URL. This keeps auth cookies on the same origin (no CORS issues).
|
|
4
|
+
*
|
|
5
|
+
* The site URL is `convexAuth`'s to resolve: the private `runtimeConfig.convex.siteUrl`,
|
|
6
|
+
* then the public one, then the environment — so a container built without it and
|
|
7
|
+
* started with `NUXT_PUBLIC_CONVEX_SITE_URL` works. Reading only the private key here
|
|
8
|
+
* used to fail every auth request in exactly that deployment.
|
|
9
|
+
*
|
|
10
|
+
* The method list matches the nuxt-security route rule the module registers on this
|
|
11
|
+
* route; enforcing it here too keeps the proxy closed to `PUT`/`DELETE` probes when
|
|
12
|
+
* nuxt-security is not installed.
|
|
13
|
+
*
|
|
14
|
+
* The path check: h3 routes the request's raw path, and the handler forwards the
|
|
15
|
+
* URL-normalised one. `/api/auth/../../x` (or its `%2e%2e` spelling) matches this
|
|
16
|
+
* route and normalises to `/x`, which the proxy would send to the site origin —
|
|
17
|
+
* any HTTP action on the deployment, reached through the app's origin with the
|
|
18
|
+
* app's cookies attached. Next.js normalises before routing, so upstream's
|
|
19
|
+
* handler never sees such a path; Nitro does not, so it is refused here.
|
|
4
20
|
*/
|
|
5
21
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Response>>;
|
|
6
22
|
export default _default;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineEventHandler } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "#imports";
|
|
1
|
+
import { assertMethod, createError, defineEventHandler, toWebRequest } from "h3";
|
|
3
2
|
import { convexAuth } from "./server.js";
|
|
4
3
|
export default defineEventHandler(async (event) => {
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
assertMethod(event, ["GET", "HEAD", "POST", "OPTIONS"]);
|
|
5
|
+
const routed = event.path.split("?")[0];
|
|
6
|
+
const forwarded = new URL(toWebRequest(event).url).pathname;
|
|
7
|
+
if (forwarded !== routed) {
|
|
8
|
+
throw createError({ statusCode: 400, statusMessage: "Bad Request" });
|
|
9
9
|
}
|
|
10
|
-
return convexAuth(event
|
|
10
|
+
return convexAuth(event).handler();
|
|
11
11
|
});
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-side Better Auth + Convex integration for Nuxt.
|
|
3
3
|
*
|
|
4
|
-
* Provides {@link convexAuth} for Nuxt server handlers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* {@link H3Event} that is available in every Nuxt server route, API handler
|
|
8
|
-
* and server middleware.
|
|
4
|
+
* Provides {@link convexAuth} for Nuxt server handlers: it takes the H3
|
|
5
|
+
* `H3Event` every Nuxt server route, API handler and server middleware
|
|
6
|
+
* receives, and returns a request-scoped, authenticated Convex client.
|
|
9
7
|
*
|
|
10
8
|
* @module better-auth/server
|
|
11
9
|
*/
|
|
@@ -30,7 +28,9 @@ export type ConvexAuthOptions = GetTokenOptions & {
|
|
|
30
28
|
*/
|
|
31
29
|
convexUrl?: string;
|
|
32
30
|
/**
|
|
33
|
-
* Override the Convex site URL. Defaults to `
|
|
31
|
+
* Override the Convex site URL. Defaults to `runtimeConfig.convex.siteUrl`,
|
|
32
|
+
* then `runtimeConfig.public.convex.siteUrl`, then the
|
|
33
|
+
* `NUXT_PUBLIC_CONVEX_SITE_URL` process variable.
|
|
34
34
|
*/
|
|
35
35
|
convexSiteUrl?: string;
|
|
36
36
|
};
|
|
@@ -11,6 +11,21 @@ const parseConvexSiteUrl = (url) => {
|
|
|
11
11
|
if (url.endsWith(".convex.cloud")) {
|
|
12
12
|
throw new Error(
|
|
13
13
|
`NUXT_PUBLIC_CONVEX_SITE_URL should be set to your Convex Site URL, which ends in .convex.site.
|
|
14
|
+
Currently set to ${url}.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
let protocol;
|
|
18
|
+
try {
|
|
19
|
+
protocol = new URL(url).protocol;
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`NUXT_PUBLIC_CONVEX_SITE_URL must be an absolute URL, including the scheme.
|
|
23
|
+
Currently set to ${url}.`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (protocol !== "https:" && protocol !== "http:") {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`NUXT_PUBLIC_CONVEX_SITE_URL must use http: or https:.
|
|
14
29
|
Currently set to ${url}.`
|
|
15
30
|
);
|
|
16
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PropType, type VNode } from 'vue';
|
|
2
2
|
import type { FunctionReference } from 'convex/server';
|
|
3
|
-
import type { EmptyObject } from 'convex-helpers';
|
|
4
3
|
import type { AuthClient } from '#convex/auth-client';
|
|
4
|
+
type EmptyObject = Record<string, never>;
|
|
5
5
|
/**
|
|
6
6
|
* _Experimental_
|
|
7
7
|
*
|
|
@@ -43,6 +43,7 @@ import type { AuthClient } from '#convex/auth-client';
|
|
|
43
43
|
* @param props.isAuthError - Function to check if the error is auth related.
|
|
44
44
|
*
|
|
45
45
|
* @public
|
|
46
|
+
* @experimental May change in a minor release — see STABILITY.md.
|
|
46
47
|
*/
|
|
47
48
|
export declare const AuthBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
48
49
|
/**
|
|
@@ -129,3 +130,4 @@ export declare const AuthBoundary: import("vue").DefineComponent<import("vue").E
|
|
|
129
130
|
}>> & Readonly<{}>, {
|
|
130
131
|
renderFallback: () => VNode | null;
|
|
131
132
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
133
|
+
export {};
|
|
@@ -172,7 +172,7 @@ export declare const authClient: import("better-auth/vue").VueAuthClient<{
|
|
|
172
172
|
metadata: {
|
|
173
173
|
isAction: false;
|
|
174
174
|
};
|
|
175
|
-
}, import("better-auth/plugins").OIDCMetadata>;
|
|
175
|
+
}, import("better-auth/plugins/oidc-provider").OIDCMetadata>;
|
|
176
176
|
getJwks: import("better-auth").StrictEndpoint<"/convex/jwks", {
|
|
177
177
|
method: "GET";
|
|
178
178
|
metadata: {
|
|
@@ -11,11 +11,10 @@ export interface ConsumeCrossDomainOneTimeTokenOptions {
|
|
|
11
11
|
*
|
|
12
12
|
* The cross-domain protocol cannot bind the token to the browser that
|
|
13
13
|
* started the sign-in flow (magic-link flows legitimately finish in another
|
|
14
|
-
* browsing context), so without this restriction a
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* predictable URL.
|
|
14
|
+
* browsing context), so without this restriction a token completes sign-in
|
|
15
|
+
* on whatever page receives it. Restricting consumption to the one route
|
|
16
|
+
* your sign-in `callbackURL`s point at keeps that to a single predictable
|
|
17
|
+
* URL.
|
|
19
18
|
*
|
|
20
19
|
* In Nuxt, set `convex.betterAuth.crossDomainCallbackRoute` instead — the
|
|
21
20
|
* client plugin forwards it here.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { authClient } from "#convex/auth-client";
|
|
2
2
|
const normalizePathname = (path) => {
|
|
3
3
|
const slashed = path.startsWith("/") ? path : `/${path}`;
|
|
4
|
-
|
|
4
|
+
let end = slashed.length;
|
|
5
|
+
while (end > 0 && slashed[end - 1] === "/") end--;
|
|
6
|
+
const trimmed = slashed.slice(0, end);
|
|
5
7
|
return trimmed === "" ? "/" : trimmed;
|
|
6
8
|
};
|
|
7
9
|
export async function consumeCrossDomainOneTimeToken(options = {}) {
|
|
@@ -19,10 +21,15 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
|
|
|
19
21
|
}
|
|
20
22
|
if (options.callbackRoute && normalizePathname(url.pathname) !== normalizePathname(options.callbackRoute)) {
|
|
21
23
|
console.warn(
|
|
22
|
-
`[nuxt-convex-module]
|
|
24
|
+
`[nuxt-convex-module] Ignoring cross-domain one-time token on "${url.pathname}" \u2014 consumption is restricted to "${options.callbackRoute}" (convex.betterAuth.crossDomainCallbackRoute).`
|
|
23
25
|
);
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
28
|
+
if (import.meta.dev && !options.callbackRoute) {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[nuxt-convex-module] Exchanged a cross-domain one-time token on "${url.pathname}" with no \`convex.betterAuth.crossDomainCallbackRoute\` set \u2014 any page can complete sign-in. Restrict it to your callback route.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
26
33
|
try {
|
|
27
34
|
const result = await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
|
|
28
35
|
token
|
|
@@ -36,10 +43,10 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
|
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
});
|
|
39
|
-
authClientWithCrossDomain.updateSession();
|
|
46
|
+
await authClientWithCrossDomain.updateSession();
|
|
40
47
|
}
|
|
41
48
|
} catch (error) {
|
|
42
|
-
console.warn("[nuxt-convex-module]
|
|
49
|
+
console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", error);
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
}
|
|
@@ -2,7 +2,7 @@ import type { FunctionReference } from 'convex/server';
|
|
|
2
2
|
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
3
3
|
import { type Preloaded } from '../../vue/hydration.js';
|
|
4
4
|
/**
|
|
5
|
-
* Auth-aware version of {@link usePreloadedQuery} for payloads returned by
|
|
5
|
+
* Auth-aware version of {@link client!usePreloadedQuery | usePreloadedQuery} for payloads returned by
|
|
6
6
|
* `convexAuth(event).preloadAuthQuery(...)`.
|
|
7
7
|
*
|
|
8
8
|
* A Vue/Nuxt port of `@convex-dev/better-auth`'s `usePreloadedAuthQuery`
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module better-auth/client
|
|
6
6
|
*/
|
|
7
|
-
export {
|
|
7
|
+
export { useBetterAuth, type UseBetterAuthReturn, type BetterAuthSession, type BetterAuthUser } from './use-better-auth.js';
|
|
8
8
|
export { authClient, type AuthClient } from '#convex/auth-client';
|
|
9
9
|
export { usePreloadedAuthQuery } from './hydration.js';
|
|
10
10
|
export { consumeCrossDomainOneTimeToken, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
|
|
11
|
+
export { resolveAuthRedirect } from './redirect.js';
|
|
11
12
|
export { AuthBoundary } from './auth-boundary.js';
|
|
12
13
|
export { convexClient, crossDomainClient } from '@convex-dev/better-auth/client/plugins';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { useBetterAuth } from "./use-better-auth.js";
|
|
2
2
|
export { authClient } from "#convex/auth-client";
|
|
3
3
|
export { usePreloadedAuthQuery } from "./hydration.js";
|
|
4
4
|
export { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
|
|
5
|
+
export { resolveAuthRedirect } from "./redirect.js";
|
|
5
6
|
export { AuthBoundary } from "./auth-boundary.js";
|
|
6
7
|
export { convexClient, crossDomainClient } from "@convex-dev/better-auth/client/plugins";
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
type ConvexNuxtInjection = {
|
|
3
|
-
convex?: ConvexVueClient;
|
|
4
|
-
};
|
|
5
|
-
declare const _default: import("nuxt/app").Plugin<ConvexNuxtInjection> & import("nuxt/app").ObjectPlugin<ConvexNuxtInjection>;
|
|
1
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
6
2
|
export default _default;
|
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRuntimeConfig, useState } from "#app";
|
|
2
2
|
import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
|
|
3
3
|
import { ConvexAuthStateKey, createScopedConvexAuthState } from "../../vue/auth/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
|
|
5
|
+
import { useBetterAuth } from "./use-better-auth.js";
|
|
5
6
|
import { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
|
|
6
7
|
export default defineNuxtPlugin({
|
|
7
8
|
name: "nuxt-convex-module:better-auth:client",
|
|
8
9
|
async setup(nuxtApp) {
|
|
9
10
|
const { url, crossDomainCallbackRoute } = useRuntimeConfig().public.convex;
|
|
10
11
|
if (!url) {
|
|
11
|
-
console.warn("[nuxt-convex-module] No Convex URL configured
|
|
12
|
-
return
|
|
12
|
+
console.warn("[nuxt-convex-module] No Convex deployment URL configured \u2014 client and auth not created. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.");
|
|
13
|
+
return;
|
|
13
14
|
}
|
|
14
15
|
const client = new ConvexVueClient(url);
|
|
15
16
|
nuxtApp.vueApp.provide(ConvexClientKey, client);
|
|
16
17
|
await consumeCrossDomainOneTimeToken({
|
|
17
18
|
callbackRoute: crossDomainCallbackRoute || void 0
|
|
18
19
|
});
|
|
19
|
-
const initialToken = useState(
|
|
20
|
+
const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
|
|
20
21
|
const { state, scope } = createScopedConvexAuthState({
|
|
21
22
|
client,
|
|
22
|
-
useAuth: () =>
|
|
23
|
+
useAuth: () => useBetterAuth(initialToken.value)
|
|
23
24
|
});
|
|
24
25
|
nuxtApp.vueApp.provide(ConvexAuthStateKey, state);
|
|
25
26
|
void scope;
|
|
26
|
-
return {
|
|
27
|
-
provide: {
|
|
28
|
-
convex: client
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
27
|
}
|
|
32
28
|
});
|
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
import { useState, useRequestEvent } from '#app';
|
|
2
|
-
import { ConvexVueClient } from '../../vue/client.js';
|
|
3
|
-
type ConvexNuxtInjection = {
|
|
4
|
-
convex?: ConvexVueClient;
|
|
5
|
-
};
|
|
6
|
-
/**
|
|
7
|
-
* Prefetch the Convex JWT for SSR and stash it in `initialToken`.
|
|
8
|
-
*
|
|
9
|
-
* Exported for unit testing.
|
|
10
|
-
*/
|
|
11
|
-
export declare function prefetchAuthToken(event: ReturnType<typeof useRequestEvent>, initialToken: ReturnType<typeof useState<string | null>>): Promise<void>;
|
|
12
1
|
/**
|
|
13
2
|
* Server-side Better Auth bootstrap.
|
|
14
3
|
*
|
|
15
4
|
* Mirrors the Next.js parity layer that calls `getToken()` from the root
|
|
16
5
|
* server layout and passes it as `initialToken` to `ConvexBetterAuthProvider`.
|
|
17
6
|
*
|
|
18
|
-
* The token is stashed into a Nuxt `useState(
|
|
19
|
-
* client plugin can hand it to `
|
|
20
|
-
* Convex `setAuth` call — avoiding an extra Better Auth round-trip on
|
|
21
|
-
* paint.
|
|
7
|
+
* The token is stashed into a Nuxt `useState` (`CONVEX_INITIAL_TOKEN_KEY`) so
|
|
8
|
+
* the client plugin can hand it to `useBetterAuth(initialToken)` before the
|
|
9
|
+
* first Convex `setAuth` call — avoiding an extra Better Auth round-trip on
|
|
10
|
+
* first paint.
|
|
22
11
|
*/
|
|
23
|
-
declare const _default: import("nuxt/app").Plugin<
|
|
12
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
24
13
|
export default _default;
|
|
@@ -2,11 +2,9 @@ import { computed } from "vue";
|
|
|
2
2
|
import { setResponseHeader } from "h3";
|
|
3
3
|
import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent } from "#app";
|
|
4
4
|
import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
|
|
5
|
+
import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
|
|
5
6
|
import { convexAuth } from "../nuxt/server.js";
|
|
6
7
|
import { ConvexAuthStateKey } from "../../vue/auth/index.js";
|
|
7
|
-
function buildProvide(ssrClient) {
|
|
8
|
-
return ssrClient ? { provide: { convex: ssrClient } } : { provide: {} };
|
|
9
|
-
}
|
|
10
8
|
export async function prefetchAuthToken(event, initialToken) {
|
|
11
9
|
try {
|
|
12
10
|
const token = await convexAuth(event).getToken();
|
|
@@ -21,7 +19,7 @@ export async function prefetchAuthToken(event, initialToken) {
|
|
|
21
19
|
export default defineNuxtPlugin({
|
|
22
20
|
name: "nuxt-convex-module:better-auth:server",
|
|
23
21
|
async setup(nuxtApp) {
|
|
24
|
-
const initialToken = useState(
|
|
22
|
+
const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
|
|
25
23
|
const url = useRuntimeConfig().public.convex.url;
|
|
26
24
|
const ssrClient = url ? new ConvexVueClient(url) : void 0;
|
|
27
25
|
if (ssrClient) {
|
|
@@ -34,9 +32,8 @@ export default defineNuxtPlugin({
|
|
|
34
32
|
};
|
|
35
33
|
nuxtApp.vueApp.provide(ConvexAuthStateKey, ssrAuthState);
|
|
36
34
|
const event = useRequestEvent();
|
|
37
|
-
if (event) {
|
|
35
|
+
if (event && !import.meta.prerender) {
|
|
38
36
|
await prefetchAuthToken(event, initialToken);
|
|
39
37
|
}
|
|
40
|
-
return buildProvide(ssrClient);
|
|
41
38
|
}
|
|
42
39
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a post-sign-in redirect destination, returning `fallback` unless it
|
|
3
|
+
* is a same-origin path.
|
|
4
|
+
*
|
|
5
|
+
* Use it wherever a login page consumes the `?redirect=` query the `auth`
|
|
6
|
+
* middleware sets — never navigate to that value directly.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```vue
|
|
10
|
+
* <script setup lang="ts">
|
|
11
|
+
* const route = useRoute()
|
|
12
|
+
* const { client } = useBetterAuth()
|
|
13
|
+
*
|
|
14
|
+
* async function onSubmit() {
|
|
15
|
+
* await client.signIn.email({ email, password })
|
|
16
|
+
* // '/dashboard' survives; 'https://evil.example' becomes '/'.
|
|
17
|
+
* await navigateTo(resolveAuthRedirect(route.query.redirect))
|
|
18
|
+
* }
|
|
19
|
+
* </script>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param value - Candidate destination, typically `route.query.redirect`. A
|
|
23
|
+
* repeated query parameter arrives as an array and is rejected: which entry
|
|
24
|
+
* the app meant is ambiguous, and an attacker chooses the other one.
|
|
25
|
+
* @param fallback - Where to send the visitor when `value` is not a safe
|
|
26
|
+
* same-origin path. Defaults to `/`, and is returned as given — pass a path
|
|
27
|
+
* you control, not user input.
|
|
28
|
+
*
|
|
29
|
+
* @returns The normalized `pathname + search + hash`, or `fallback`.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveAuthRedirect(value: unknown, fallback?: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const SENTINEL_ORIGIN = "http://redirect.invalid";
|
|
2
|
+
export function resolveAuthRedirect(value, fallback = "/") {
|
|
3
|
+
if (typeof value !== "string" || value === "") return fallback;
|
|
4
|
+
if (!value.startsWith("/")) return fallback;
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(value, SENTINEL_ORIGIN);
|
|
7
|
+
if (url.origin !== SENTINEL_ORIGIN) return fallback;
|
|
8
|
+
const path = `${url.pathname}${url.search}${url.hash}`;
|
|
9
|
+
if (new URL(path, SENTINEL_ORIGIN).origin !== SENTINEL_ORIGIN) return fallback;
|
|
10
|
+
return path;
|
|
11
|
+
} catch {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -27,7 +27,9 @@ declare const useClientSession: () => Readonly<import("vue").Ref<{
|
|
|
27
27
|
readonly isPending: boolean;
|
|
28
28
|
readonly isRefetching: boolean;
|
|
29
29
|
readonly error: import("better-auth/client").BetterFetchError | null;
|
|
30
|
-
readonly refetch: (queryParams
|
|
30
|
+
readonly refetch: (queryParams
|
|
31
|
+
/** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
|
|
32
|
+
?: {
|
|
31
33
|
query?: import("better-auth").SessionQueryParams;
|
|
32
34
|
} | undefined) => Promise<void>;
|
|
33
35
|
}, {
|
|
@@ -56,37 +58,55 @@ declare const useClientSession: () => Readonly<import("vue").Ref<{
|
|
|
56
58
|
readonly isPending: boolean;
|
|
57
59
|
readonly isRefetching: boolean;
|
|
58
60
|
readonly error: import("better-auth/client").BetterFetchError | null;
|
|
59
|
-
readonly refetch: (queryParams
|
|
61
|
+
readonly refetch: (queryParams
|
|
62
|
+
/** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
|
|
63
|
+
?: {
|
|
60
64
|
query?: import("better-auth").SessionQueryParams;
|
|
61
65
|
} | undefined) => Promise<void>;
|
|
62
66
|
}>>;
|
|
63
|
-
export type
|
|
67
|
+
export type BetterAuthSession = ReturnType<typeof useClientSession>;
|
|
64
68
|
/** The signed-in user (loose — exact fields depend on your auth schema). */
|
|
65
|
-
export type
|
|
69
|
+
export type BetterAuthUser = {
|
|
66
70
|
id: string;
|
|
67
71
|
email: string;
|
|
68
72
|
name: string;
|
|
69
73
|
} & Record<string, unknown>;
|
|
70
|
-
|
|
74
|
+
/** What `useBetterAuth()` returns. */
|
|
75
|
+
export interface UseBetterAuthReturn {
|
|
76
|
+
/** `true` until the session is known and no prefetched token covers the gap. */
|
|
71
77
|
isLoading: ComputedRef<boolean>;
|
|
78
|
+
/** `true` when Better Auth reports a session, or a token is cached while it reloads. */
|
|
72
79
|
isAuthenticated: ComputedRef<boolean>;
|
|
80
|
+
/** Convex's token fetcher: the Better Auth JWT for the current session, or `null`. */
|
|
73
81
|
fetchAccessToken: AuthTokenFetcher;
|
|
82
|
+
/** The Better Auth client from `#convex/auth-client` — sign-in, sign-out and every plugin flow live here. */
|
|
74
83
|
client: AuthClient;
|
|
75
|
-
|
|
84
|
+
/** Better Auth's own `useSession()` ref: `{ data, isPending, error }`. */
|
|
85
|
+
session: BetterAuthSession;
|
|
76
86
|
/** The current user, or `null` when signed out / still loading. */
|
|
77
|
-
user: ComputedRef<
|
|
87
|
+
user: ComputedRef<BetterAuthUser | null>;
|
|
88
|
+
/** The session id (or user id); changes when the signed-in identity changes. */
|
|
78
89
|
authVersion: ComputedRef<string | null>;
|
|
79
90
|
}
|
|
80
91
|
/**
|
|
81
|
-
*
|
|
92
|
+
* The Better Auth service for the Vue/Nuxt runtime.
|
|
82
93
|
*
|
|
83
94
|
* Returns the full Better Auth client, the reactive session wrapper, and the
|
|
84
95
|
* Convex-compatible auth state used by the packaged auth plugin.
|
|
85
96
|
*
|
|
86
97
|
* @param initialToken - Optional preloaded token, used once per app lifetime
|
|
87
98
|
* to avoid a round-trip on initial load (e.g. from SSR).
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```vue
|
|
102
|
+
* <script setup lang="ts">
|
|
103
|
+
* const { user, client, isAuthenticated } = useBetterAuth()
|
|
104
|
+
*
|
|
105
|
+
* const signOut = () => client.signOut()
|
|
106
|
+
* </script>
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @public
|
|
88
110
|
*/
|
|
89
|
-
export declare function
|
|
90
|
-
/** Reset module-level token cache — intended for tests only. */
|
|
91
|
-
export declare function __resetUseAuthForTests(): void;
|
|
111
|
+
export declare function useBetterAuth(initialToken?: string | null): UseBetterAuthReturn;
|
|
92
112
|
export {};
|
|
@@ -7,7 +7,7 @@ let pendingTokenVersion = null;
|
|
|
7
7
|
let initialTokenUsed = false;
|
|
8
8
|
const PENDING_AUTH_VERSION = "__pending__";
|
|
9
9
|
const useClientSession = () => authClient.useSession();
|
|
10
|
-
export function
|
|
10
|
+
export function useBetterAuth(initialToken) {
|
|
11
11
|
if (!initialTokenUsed && initialToken) {
|
|
12
12
|
cachedToken.value = initialToken;
|
|
13
13
|
cachedTokenVersion = PENDING_AUTH_VERSION;
|
|
@@ -55,6 +55,7 @@ export function useAuth(initialToken) {
|
|
|
55
55
|
};
|
|
56
56
|
return {
|
|
57
57
|
isLoading: computed(() => session.value.isPending && !cachedToken.value),
|
|
58
|
+
// PARITY: D-03
|
|
58
59
|
// Diverges from upstream's `Boolean(session?.session) || cachedToken !== null`:
|
|
59
60
|
// a settled signed-out session must read unauthenticated immediately (Vue has
|
|
60
61
|
// no re-render to run upstream's cache-clearing effect first), and Better Auth
|
|
@@ -63,7 +64,7 @@ export function useAuth(initialToken) {
|
|
|
63
64
|
() => !!session.value.data || session.value.isPending && cachedToken.value !== null
|
|
64
65
|
),
|
|
65
66
|
fetchAccessToken,
|
|
66
|
-
// Vue-only service extensions
|
|
67
|
+
// PARITY: A-06 — Vue-only service extensions.
|
|
67
68
|
client,
|
|
68
69
|
session,
|
|
69
70
|
user: computed(() => {
|
|
@@ -73,7 +74,7 @@ export function useAuth(initialToken) {
|
|
|
73
74
|
authVersion
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
export function
|
|
77
|
+
export function __resetUseBetterAuthForTests() {
|
|
77
78
|
cachedToken.value = null;
|
|
78
79
|
cachedTokenVersion = null;
|
|
79
80
|
pendingToken = null;
|
|
@@ -16,15 +16,15 @@ export type UseAuth = () => {
|
|
|
16
16
|
* Options for {@link provideConvexAuthFromClerk} / `<ConvexProviderWithClerk>`.
|
|
17
17
|
*/
|
|
18
18
|
export interface ConvexProviderWithClerkOptions {
|
|
19
|
-
/** Convex client to authenticate. Defaults to the
|
|
19
|
+
/** Convex client to authenticate. Defaults to the client `useConvex()` returns. */
|
|
20
20
|
client?: IConvexVueClient;
|
|
21
21
|
/** Clerk's `useAuth` composable. Defaults to `useAuth` from `@clerk/vue`. */
|
|
22
22
|
useAuth?: UseAuth;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* A composable which provides a {@link ConvexVueClient}
|
|
25
|
+
* A composable which provides a {@link client!ConvexVueClient | ConvexVueClient}
|
|
26
26
|
* authenticated with Clerk, exposing the reactive auth state to descendants
|
|
27
|
-
* via {@link useConvexAuth} — the Vue translation of wrapping the app in
|
|
27
|
+
* via {@link client!useConvexAuth | useConvexAuth} — the Vue translation of wrapping the app in
|
|
28
28
|
* `convex/react-clerk`'s `<ConvexProviderWithClerk>`.
|
|
29
29
|
*
|
|
30
30
|
* Call it in a top-level component's `setup`. The app must be wrapped by a
|
|
@@ -47,7 +47,7 @@ export interface ConvexProviderWithClerkOptions {
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function provideConvexAuthFromClerk(options?: ConvexProviderWithClerkOptions): ConvexAuthState;
|
|
49
49
|
/**
|
|
50
|
-
* A wrapper Vue component which provides a {@link ConvexVueClient}
|
|
50
|
+
* A wrapper Vue component which provides a {@link client!ConvexVueClient | ConvexVueClient}
|
|
51
51
|
* authenticated with Clerk — the component form of
|
|
52
52
|
* {@link provideConvexAuthFromClerk}, kept as a drop-in parity port of
|
|
53
53
|
* `convex/react-clerk`'s `<ConvexProviderWithClerk>`. Renders its default
|
|
@@ -17,7 +17,7 @@ function toPlainJson(value) {
|
|
|
17
17
|
try {
|
|
18
18
|
return convexToJson(value);
|
|
19
19
|
} catch {
|
|
20
|
-
return String(value);
|
|
20
|
+
return typeof value === "object" && value !== null ? `[unserializable ${Array.isArray(value) ? "array" : "object"}]` : String(value);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
function renderLogArg(arg) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composables that need the Nuxt app context (`#app`) — the half of the
|
|
3
|
+
* package that runs in a Nuxt app but not in plain Vue.
|
|
4
|
+
*
|
|
5
|
+
* This module contains {@link useAsyncQuery} and its paginated form
|
|
6
|
+
* {@link useAsyncPaginatedQuery}: server-rendered Convex data that upgrades
|
|
7
|
+
* to a live subscription after hydration.
|
|
8
|
+
*
|
|
9
|
+
* ## Usage
|
|
10
|
+
*
|
|
11
|
+
* **Default — auto-imported.** In any page, component or composable, call it
|
|
12
|
+
* with no import; its types are auto-imported too:
|
|
13
|
+
*
|
|
14
|
+
* ```vue
|
|
15
|
+
* <script setup lang="ts">
|
|
16
|
+
* import { api } from '#convex/api'
|
|
17
|
+
*
|
|
18
|
+
* const { data: messages, status } = useAsyncQuery(api.messages.list, {})
|
|
19
|
+
* // `AsyncQueryStatus`, `AsyncQueryReturn`, … resolve without an import as well:
|
|
20
|
+
* const label = (s: AsyncQueryStatus) => s === 'pending' ? 'Loading…' : ''
|
|
21
|
+
* </script>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* **Explicit import — this subpath.** For a project that turns auto-imports
|
|
25
|
+
* off, a file outside the app's auto-import scope, or an editor that wants
|
|
26
|
+
* the import spelled out:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { useAsyncQuery } from 'nuxt-convex-module/app'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* **Types.** From `#imports` (Nuxt's auto-import barrel) or from this subpath,
|
|
33
|
+
* whichever your file already uses:
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* import type { AsyncQueryReturn } from '#imports'
|
|
37
|
+
* import type { AsyncQueryReturn } from 'nuxt-convex-module/app'
|
|
38
|
+
*
|
|
39
|
+
* const messages: AsyncQueryReturn<Doc<'messages'>[]> = useAsyncQuery(api.messages.list, {})
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Before 1.0 the composable was auto-imported but reachable from no subpath,
|
|
43
|
+
* so neither the explicit form nor the types could be written at all.
|
|
44
|
+
*
|
|
45
|
+
* @module app
|
|
46
|
+
*/
|
|
47
|
+
export { useAsyncQuery, useConvexAsyncQuery, type AsyncQueryData, type AsyncQueryOptions, type AsyncQueryReturn, type AsyncQueryStatus, } from './composables/use-async-query.js';
|
|
48
|
+
export { useAsyncPaginatedQuery, useConvexAsyncPaginatedQuery, type AsyncPaginatedQueryData, type AsyncPaginatedQueryOptions, type AsyncPaginatedQueryReturn, } from './composables/use-async-paginated-query.js';
|