use-convex 1.2.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 +120 -41
- package/dist/module.d.mts +73 -31
- package/dist/module.mjs +73 -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 -398
- 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/useConvexQueries.d.ts +36 -7
- package/dist/runtime/composables/useConvexQueries.js +37 -13
- 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 +208 -23
- 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 +9 -1
- package/dist/runtime/server/fetch.js +7 -10
- package/dist/runtime/utils/authClientOptions.d.ts +54 -0
- package/dist/runtime/utils/authClientOptions.js +10 -0
- package/dist/runtime/utils/authCookie.d.ts +8 -14
- package/dist/runtime/utils/authCookie.js +23 -27
- package/dist/runtime/utils/authStorage.d.ts +80 -42
- package/dist/runtime/utils/authStorage.js +56 -66
- package/dist/runtime/utils/context.d.ts +42 -6
- package/dist/runtime/utils/http.d.ts +7 -0
- package/dist/runtime/utils/http.js +5 -2
- package/dist/types.d.mts +6 -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,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;
|
|
@@ -1,41 +1,226 @@
|
|
|
1
|
+
import { AuthClient, InMemoryStorage, defaultStorage } from "@convex-dev/auth/browser";
|
|
2
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
3
|
+
import { makeFunctionReference } from "convex/server";
|
|
1
4
|
import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
|
|
5
|
+
import { readonly, shallowRef, watch } from "vue";
|
|
2
6
|
import { useConvexAuth } from "./composables/useConvexAuth.js";
|
|
3
7
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "./
|
|
10
|
-
import {
|
|
8
|
+
OAUTH_CODE_PARAM,
|
|
9
|
+
OAUTH_ERROR_PARAM,
|
|
10
|
+
convexOAuth
|
|
11
|
+
} from "./auth/oauth.js";
|
|
12
|
+
import { postAuth, retryingTokenFetcher } from "./auth/sessionRoutes.js";
|
|
13
|
+
import { getConvexAuthClientOptions } from "./utils/authClientOptions.js";
|
|
14
|
+
import { useConvexAuthTokenState } from "./utils/authCookie.js";
|
|
15
|
+
import {
|
|
16
|
+
isConvexAuth,
|
|
17
|
+
resolveAuthApiNames,
|
|
18
|
+
resolveAuthMode,
|
|
19
|
+
resolveAuthRoutes
|
|
20
|
+
} from "./utils/authStorage.js";
|
|
21
|
+
import {
|
|
22
|
+
tryUseConvexContext
|
|
23
|
+
} from "./utils/context.js";
|
|
24
|
+
function sessionStorageOrUndefined() {
|
|
25
|
+
try {
|
|
26
|
+
return typeof window !== "undefined" ? window.sessionStorage : void 0;
|
|
27
|
+
} catch {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
11
31
|
export default defineNuxtPlugin({
|
|
12
32
|
name: "convex-nuxt-auth",
|
|
13
33
|
dependsOn: ["convex-nuxt-client"],
|
|
14
|
-
setup() {
|
|
34
|
+
setup(nuxtApp) {
|
|
15
35
|
const config = useRuntimeConfig();
|
|
16
36
|
const convexConfig = config.public.convex;
|
|
17
|
-
|
|
37
|
+
const auth = convexConfig?.auth;
|
|
38
|
+
if (!convexConfig?.url || !isConvexAuth(auth)) {
|
|
18
39
|
return;
|
|
19
40
|
}
|
|
20
|
-
|
|
41
|
+
const ctx = tryUseConvexContext();
|
|
42
|
+
const convex = ctx?.client;
|
|
43
|
+
if (!ctx || !convex) {
|
|
21
44
|
return;
|
|
22
45
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
const mode = resolveAuthMode(auth);
|
|
47
|
+
const apiNames = resolveAuthApiNames(auth);
|
|
48
|
+
const baseURL = config.app?.baseURL ?? "/";
|
|
49
|
+
const routes = resolveAuthRoutes(auth);
|
|
50
|
+
const route = (path) => baseURL.replace(/\/+$/, "") + path;
|
|
51
|
+
const options = getConvexAuthClientOptions();
|
|
52
|
+
const tokenState = useConvexAuthTokenState();
|
|
53
|
+
const storage = options.storage ?? (auth?.storage === "inMemory" ? new InMemoryStorage() : defaultStorage());
|
|
54
|
+
const storageNamespace = auth?.storageNamespace ?? ctx.url;
|
|
55
|
+
const replaceUrl = options.replaceUrl ?? ((url) => {
|
|
56
|
+
window.history.replaceState(window.history.state, "", url);
|
|
57
|
+
const router = nuxtApp.$router;
|
|
58
|
+
void router?.isReady().then(() => {
|
|
59
|
+
const current = router.currentRoute.value;
|
|
60
|
+
if (!(OAUTH_CODE_PARAM in current.query) && !(OAUTH_ERROR_PARAM in current.query)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const query = { ...current.query };
|
|
64
|
+
delete query[OAUTH_CODE_PARAM];
|
|
65
|
+
delete query[OAUTH_ERROR_PARAM];
|
|
66
|
+
void router.replace({ path: current.path, query, hash: current.hash });
|
|
67
|
+
});
|
|
32
68
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
const flowStorage = !options.storage && auth?.storage === "inMemory" ? sessionStorageOrUndefined() : void 0;
|
|
70
|
+
const signIns = options.ambientSignIns ?? [
|
|
71
|
+
convexOAuth(flowStorage ? { flowStorage, namespace: storageNamespace } : {})
|
|
72
|
+
];
|
|
73
|
+
const convexMutationApi = {
|
|
74
|
+
mutation: (fn, args) => convex.mutation(fn, args)
|
|
75
|
+
};
|
|
76
|
+
let authClient;
|
|
77
|
+
let signInApi;
|
|
78
|
+
if (mode === "ssr") {
|
|
79
|
+
const proxy = new ConvexHttpClient(`${route(routes.signIn)}?path=`, {
|
|
80
|
+
skipConvexDeploymentUrlCheck: true
|
|
81
|
+
});
|
|
82
|
+
const withAuth = () => {
|
|
83
|
+
const token = authClient.getAccessToken();
|
|
84
|
+
if (token !== null) {
|
|
85
|
+
proxy.setAuth(token);
|
|
86
|
+
} else {
|
|
87
|
+
proxy.clearAuth();
|
|
88
|
+
}
|
|
89
|
+
return proxy;
|
|
90
|
+
};
|
|
91
|
+
signInApi = {
|
|
92
|
+
mutation: (fn, args) => withAuth().mutation(fn, args),
|
|
93
|
+
action: (fn, args) => withAuth().action(fn, args)
|
|
94
|
+
};
|
|
95
|
+
authClient = new AuthClient({
|
|
96
|
+
mode: "ssr",
|
|
97
|
+
authApi: {
|
|
98
|
+
refreshSession: async () => (await postAuth(route(routes.refresh))).tokens,
|
|
99
|
+
signOut: async () => {
|
|
100
|
+
await postAuth(route(routes.signOut));
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
storage,
|
|
104
|
+
storageNamespace,
|
|
105
|
+
// The server refreshed on our behalf, so this is the freshest token.
|
|
106
|
+
initialAccessToken: tokenState.value,
|
|
107
|
+
verbose: auth?.verbose,
|
|
108
|
+
ambientSignIns: {
|
|
109
|
+
signIns,
|
|
110
|
+
signInApi,
|
|
111
|
+
convex: convexMutationApi,
|
|
112
|
+
replaceUrl
|
|
37
113
|
}
|
|
38
114
|
});
|
|
115
|
+
} else {
|
|
116
|
+
const http = new ConvexHttpClient(ctx.url);
|
|
117
|
+
const refreshSession = makeFunctionReference(apiNames.refreshSession);
|
|
118
|
+
const signOutRef = makeFunctionReference(apiNames.signOut);
|
|
119
|
+
signInApi = {
|
|
120
|
+
mutation: (fn, args) => convex.mutation(fn, args),
|
|
121
|
+
action: (fn, args) => convex.action(fn, args)
|
|
122
|
+
};
|
|
123
|
+
authClient = new AuthClient({
|
|
124
|
+
mode: "spa",
|
|
125
|
+
authApi: {
|
|
126
|
+
refreshSession: (refreshToken) => http.mutation(refreshSession, { refreshToken }),
|
|
127
|
+
signOut: async (refreshToken) => {
|
|
128
|
+
await http.mutation(signOutRef, { refreshToken });
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
storage,
|
|
132
|
+
storageNamespace,
|
|
133
|
+
verbose: auth?.verbose,
|
|
134
|
+
ambientSignIns: {
|
|
135
|
+
signIns,
|
|
136
|
+
signInApi,
|
|
137
|
+
convex: convexMutationApi,
|
|
138
|
+
replaceUrl
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const state = shallowRef(authClient.getSnapshot());
|
|
143
|
+
authClient.subscribe(() => {
|
|
144
|
+
state.value = authClient.getSnapshot();
|
|
145
|
+
});
|
|
146
|
+
const syncTokenState = () => {
|
|
147
|
+
if (!state.value.isLoading) {
|
|
148
|
+
tokenState.value = state.value.token;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
if (nuxtApp.isHydrating) {
|
|
152
|
+
nuxtApp.hooks.hookOnce("app:suspense:resolve", () => {
|
|
153
|
+
syncTokenState();
|
|
154
|
+
watch(state, syncTokenState);
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
watch(state, syncTokenState, { immediate: true });
|
|
39
158
|
}
|
|
159
|
+
const ambientCache = /* @__PURE__ */ new Map();
|
|
160
|
+
const ambientValue = (id, key) => {
|
|
161
|
+
const cacheKey = `${id}:${key}`;
|
|
162
|
+
let cached = ambientCache.get(cacheKey);
|
|
163
|
+
if (!cached) {
|
|
164
|
+
const reader = authClient.ambientSignInValues(id);
|
|
165
|
+
const value = shallowRef(reader.get(key));
|
|
166
|
+
reader.subscribe(key, () => {
|
|
167
|
+
value.value = reader.get(key);
|
|
168
|
+
});
|
|
169
|
+
cached = readonly(value);
|
|
170
|
+
ambientCache.set(cacheKey, cached);
|
|
171
|
+
}
|
|
172
|
+
return cached;
|
|
173
|
+
};
|
|
174
|
+
const binding = {
|
|
175
|
+
mode,
|
|
176
|
+
state,
|
|
177
|
+
signInApi,
|
|
178
|
+
setSession: (session) => authClient.setSession(session),
|
|
179
|
+
signOut: () => authClient.signOut(),
|
|
180
|
+
ambientValue
|
|
181
|
+
};
|
|
182
|
+
ctx.convexAuth = binding;
|
|
183
|
+
const convexAuth = useConvexAuth({
|
|
184
|
+
// A refresh that fails (outage, proxy error) is retried with backoff
|
|
185
|
+
// rather than thrown into ConvexClient or treated as signed out.
|
|
186
|
+
fetchToken: retryingTokenFetcher((args) => authClient.fetchAccessToken(args), {
|
|
187
|
+
hasSession: () => authClient.getAccessToken() !== null,
|
|
188
|
+
onRetry: (error, delayMs) => {
|
|
189
|
+
if (import.meta.dev || auth?.verbose) {
|
|
190
|
+
console.warn(`[use-convex] Session refresh failed; retrying in ${delayMs}ms.`, error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}),
|
|
194
|
+
isLoading: () => state.value.isLoading,
|
|
195
|
+
isAuthenticated: () => state.value.isAuthenticated
|
|
196
|
+
});
|
|
197
|
+
const isAuthenticatedRef = makeFunctionReference(
|
|
198
|
+
apiNames.isAuthenticated
|
|
199
|
+
);
|
|
200
|
+
let stopWatching = null;
|
|
201
|
+
watch(
|
|
202
|
+
() => convexAuth.isAuthenticated.value,
|
|
203
|
+
(authenticated) => {
|
|
204
|
+
stopWatching?.();
|
|
205
|
+
stopWatching = null;
|
|
206
|
+
if (!authenticated) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const unsubscribe = convex.onUpdate(
|
|
210
|
+
isAuthenticatedRef,
|
|
211
|
+
{},
|
|
212
|
+
(hasLiveSession) => {
|
|
213
|
+
if (hasLiveSession === false) {
|
|
214
|
+
void authClient.signOut();
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
() => {
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
stopWatching = () => unsubscribe();
|
|
221
|
+
},
|
|
222
|
+
{ immediate: true }
|
|
223
|
+
);
|
|
224
|
+
void authClient.init();
|
|
40
225
|
}
|
|
41
226
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
|
|
2
|
-
import { useSsrTokenRef } from "./utils/authCookie.js";
|
|
1
|
+
import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig } from "nuxt/app";
|
|
2
|
+
import { useConvexAuthTokenState, useSsrTokenRef } from "./utils/authCookie.js";
|
|
3
|
+
import { isSsrConvexAuth } from "./utils/authStorage.js";
|
|
3
4
|
import { createHttpClient } from "./utils/http.js";
|
|
4
5
|
import { convexNuxtKey, createAuthContext } from "./utils/context.js";
|
|
5
6
|
import { warnMissingConvexUrl } from "./utils/errors.js";
|
|
@@ -15,6 +16,11 @@ export default defineNuxtPlugin({
|
|
|
15
16
|
}
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
19
|
+
if (isSsrConvexAuth(convexConfig?.auth)) {
|
|
20
|
+
const event = useRequestEvent();
|
|
21
|
+
const token = event?.context?.convexAuthToken;
|
|
22
|
+
useConvexAuthTokenState().value = token ?? null;
|
|
23
|
+
}
|
|
18
24
|
const ssrToken = useSsrTokenRef();
|
|
19
25
|
const auth = createAuthContext();
|
|
20
26
|
if (ssrToken.value) {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
/**
|
|
3
|
-
* Read the Convex JWT
|
|
4
|
-
*
|
|
3
|
+
* Read the Convex JWT for this request.
|
|
4
|
+
*
|
|
5
|
+
* - Convex Auth (SSR mode): the access token the session middleware
|
|
6
|
+
* refreshed for this request, else the access-token cookie while valid.
|
|
7
|
+
* - BYO providers: the configured `convex.auth.cookie`.
|
|
8
|
+
*
|
|
9
|
+
* Returns `undefined` when there is no session or auth cookies are disabled.
|
|
5
10
|
*/
|
|
6
11
|
export declare function getConvexToken(event: H3Event): string | undefined;
|
|
7
12
|
/**
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { createError, getCookie } from "h3";
|
|
2
|
-
import { resolveAuthCookieName } from "../utils/authStorage.js";
|
|
2
|
+
import { isJwtExpiring, isSsrConvexAuth, resolveAuthCookieName } from "../utils/authStorage.js";
|
|
3
3
|
import { resolveFetchToken } from "../utils/fetchToken.js";
|
|
4
4
|
import { readConvexConfig } from "./convexConfig.js";
|
|
5
5
|
export function getConvexToken(event) {
|
|
6
|
-
const
|
|
6
|
+
const auth = readConvexConfig(event)?.auth;
|
|
7
|
+
if (isSsrConvexAuth(auth)) {
|
|
8
|
+
const fromMiddleware = event.context.convexAuthToken;
|
|
9
|
+
if (fromMiddleware !== void 0) {
|
|
10
|
+
return fromMiddleware ?? void 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const cookieName = resolveAuthCookieName(auth);
|
|
7
14
|
if (!cookieName) {
|
|
8
15
|
return void 0;
|
|
9
16
|
}
|
|
10
|
-
|
|
17
|
+
const token = resolveFetchToken({
|
|
11
18
|
cookieName,
|
|
12
19
|
cookieValue: getCookie(event, cookieName)
|
|
13
20
|
});
|
|
21
|
+
if (token && isSsrConvexAuth(auth) && isJwtExpiring(token, 0)) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
return token;
|
|
14
25
|
}
|
|
15
26
|
export function requireConvexAuth(event) {
|
|
16
27
|
const token = getConvexToken(event);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the Convex Auth access token for this request (SSR mode).
|
|
3
|
+
*
|
|
4
|
+
* Access tokens live ~60s, so most page loads find an expired one: refresh
|
|
5
|
+
* from the httpOnly refresh cookie, rotate both cookies on the response, and
|
|
6
|
+
* leave the fresh token on `event.context.convexAuthToken` for SSR queries,
|
|
7
|
+
* `getConvexToken`, and the Nitro `fetch*` helpers.
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineEventHandler, getCookie, getRequestURL } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import {
|
|
4
|
+
CONVEX_AUTH_JWT_COOKIE,
|
|
5
|
+
CONVEX_AUTH_REFRESH_COOKIE,
|
|
6
|
+
DEFAULT_AUTH_ROUTE_PREFIX,
|
|
7
|
+
isJwtExpiring
|
|
8
|
+
} from "../../utils/authStorage.js";
|
|
9
|
+
import { createServerAuthSession } from "./setup.js";
|
|
10
|
+
export default defineEventHandler(async (event) => {
|
|
11
|
+
const context = event.context;
|
|
12
|
+
if (context.convexAuthToken !== void 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const pathname = getRequestURL(event).pathname;
|
|
16
|
+
const config = useRuntimeConfig(event);
|
|
17
|
+
const baseURL = (config.app?.baseURL ?? "/").replace(/\/+$/, "");
|
|
18
|
+
const auth = config.public?.convex?.auth;
|
|
19
|
+
const prefix = baseURL + (auth?.routePrefix ?? DEFAULT_AUTH_ROUTE_PREFIX).replace(/\/+$/, "");
|
|
20
|
+
if (pathname.startsWith(prefix + "/") || // Build assets and dev tooling never need a session. Other paths with a
|
|
21
|
+
// dot (`/users/jane.doe`, `/api/report.json`) are pages and endpoints
|
|
22
|
+
// that may. Files in `public/` are served by Nitro before middleware.
|
|
23
|
+
pathname.startsWith(`${baseURL}/_nuxt/`) || pathname.startsWith(`${baseURL}/__nuxt_devtools__`) || pathname.startsWith(`${baseURL}/__convex_devtools`) || pathname === `${baseURL}/favicon.ico`) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const jwt = getCookie(event, CONVEX_AUTH_JWT_COOKIE) || null;
|
|
27
|
+
const refresh = getCookie(event, CONVEX_AUTH_REFRESH_COOKIE) || null;
|
|
28
|
+
if (refresh === null) {
|
|
29
|
+
context.convexAuthToken = jwt !== null && !isJwtExpiring(jwt, 0) ? jwt : null;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
context.convexAuthToken = await createServerAuthSession(event).getToken();
|
|
34
|
+
} catch {
|
|
35
|
+
context.convexAuthToken = jwt !== null && !isJwtExpiring(jwt, 0) ? jwt : null;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ServerAuthSession, setupConvexAuthServer, type AuthCookieOptions, type CookieStore } from '@convex-dev/auth/ssr';
|
|
2
|
+
import type { H3Event } from 'h3';
|
|
3
|
+
import { type ConvexAuthServerRuntimeConfig } from '../../utils/authStorage.js';
|
|
4
|
+
export type { ConvexAuthServerRuntimeConfig };
|
|
5
|
+
export declare function resolveCookieOptions(event: H3Event, server: ConvexAuthServerRuntimeConfig): AuthCookieOptions;
|
|
6
|
+
type Handlers = ReturnType<typeof setupConvexAuthServer>;
|
|
7
|
+
/**
|
|
8
|
+
* `setupConvexAuthServer` built from runtime config, cached per
|
|
9
|
+
* deployment + cookie attributes (`secure` differs on localhost).
|
|
10
|
+
*/
|
|
11
|
+
export declare function useConvexAuthServer(event: H3Event): Handlers;
|
|
12
|
+
/** Run a WHATWG handler from `@convex-dev/auth/ssr` on an H3 event. */
|
|
13
|
+
export declare function runWebHandler(event: H3Event, handler: (request: Request) => Promise<Response>): Promise<void>;
|
|
14
|
+
/** `CookieStore` over H3 request/response cookies. */
|
|
15
|
+
export declare function h3CookieStore(event: H3Event): CookieStore;
|
|
16
|
+
/**
|
|
17
|
+
* A `ServerAuthSession` for this request: reads the access/refresh cookies,
|
|
18
|
+
* refreshes when the access token is missing or near expiry, and writes the
|
|
19
|
+
* rotated cookies onto the response.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createServerAuthSession(event: H3Event): ServerAuthSession;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServerAuthSession,
|
|
3
|
+
setupConvexAuthServer
|
|
4
|
+
} from "@convex-dev/auth/ssr";
|
|
5
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
6
|
+
import { makeFunctionReference } from "convex/server";
|
|
7
|
+
import {
|
|
8
|
+
createError,
|
|
9
|
+
deleteCookie,
|
|
10
|
+
getCookie,
|
|
11
|
+
getRequestHeader,
|
|
12
|
+
sendWebResponse,
|
|
13
|
+
setCookie,
|
|
14
|
+
toWebRequest
|
|
15
|
+
} from "h3";
|
|
16
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
17
|
+
import { MISSING_URL_HINT } from "../../utils/errors.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveAuthApiNames
|
|
20
|
+
} from "../../utils/authStorage.js";
|
|
21
|
+
function resolveConfig(event) {
|
|
22
|
+
const config = useRuntimeConfig(event);
|
|
23
|
+
const pub = config.public?.convex;
|
|
24
|
+
const convexUrl = pub?.url ?? process.env.NUXT_PUBLIC_CONVEX_URL;
|
|
25
|
+
if (!convexUrl) {
|
|
26
|
+
throw createError({ statusCode: 500, message: MISSING_URL_HINT });
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
convexUrl,
|
|
30
|
+
auth: pub?.auth,
|
|
31
|
+
server: config.convexAuth ?? {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function isLocalHost(event) {
|
|
35
|
+
const host = getRequestHeader(event, "host") ?? "";
|
|
36
|
+
return host.startsWith("localhost") || host.startsWith("127.0.0.1") || host.startsWith("[::1]");
|
|
37
|
+
}
|
|
38
|
+
export function resolveCookieOptions(event, server) {
|
|
39
|
+
return {
|
|
40
|
+
secure: server.cookie?.secure ?? !isLocalHost(event),
|
|
41
|
+
...server.cookie?.domain ? { domain: server.cookie.domain } : {},
|
|
42
|
+
...server.cookie?.path ? { path: server.cookie.path } : {}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const cache = /* @__PURE__ */ new Map();
|
|
46
|
+
export function useConvexAuthServer(event) {
|
|
47
|
+
const { convexUrl, auth, server } = resolveConfig(event);
|
|
48
|
+
const cookieOptions = resolveCookieOptions(event, server);
|
|
49
|
+
const api = resolveAuthApiNames(auth);
|
|
50
|
+
const key = JSON.stringify([convexUrl, api, server, cookieOptions]);
|
|
51
|
+
let handlers = cache.get(key);
|
|
52
|
+
if (!handlers) {
|
|
53
|
+
handlers = setupConvexAuthServer({
|
|
54
|
+
convexUrl,
|
|
55
|
+
refreshSession: makeFunctionReference(api.refreshSession),
|
|
56
|
+
signOut: makeFunctionReference(api.signOut),
|
|
57
|
+
signIn: (server.signIn ?? []).map((name) => makeFunctionReference(name)),
|
|
58
|
+
cookieOptions,
|
|
59
|
+
...server.allowedOrigins?.length ? { allowedOrigins: server.allowedOrigins } : {}
|
|
60
|
+
});
|
|
61
|
+
cache.set(key, handlers);
|
|
62
|
+
}
|
|
63
|
+
return handlers;
|
|
64
|
+
}
|
|
65
|
+
export async function runWebHandler(event, handler) {
|
|
66
|
+
const response = await handler(toWebRequest(event));
|
|
67
|
+
return sendWebResponse(event, response);
|
|
68
|
+
}
|
|
69
|
+
export function h3CookieStore(event) {
|
|
70
|
+
return {
|
|
71
|
+
get: (name) => getCookie(event, name) || void 0,
|
|
72
|
+
set: (name, value, options) => {
|
|
73
|
+
setCookie(event, name, value, options);
|
|
74
|
+
},
|
|
75
|
+
delete: (name, options) => {
|
|
76
|
+
deleteCookie(event, name, options);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function createServerAuthSession(event) {
|
|
81
|
+
const { convexUrl, auth, server } = resolveConfig(event);
|
|
82
|
+
const api = resolveAuthApiNames(auth);
|
|
83
|
+
const http = new ConvexHttpClient(convexUrl);
|
|
84
|
+
const refreshSession = makeFunctionReference(api.refreshSession);
|
|
85
|
+
return new ServerAuthSession({
|
|
86
|
+
refreshSession: (refreshToken) => http.mutation(refreshSession, { refreshToken }),
|
|
87
|
+
cookies: h3CookieStore(event),
|
|
88
|
+
cookieOptions: resolveCookieOptions(event, server)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
+
import type { ConvexAuthConfig } from '../utils/authStorage.js';
|
|
2
3
|
export interface ConvexPublicConfig {
|
|
3
4
|
url?: string;
|
|
4
|
-
auth?:
|
|
5
|
-
provider?: string;
|
|
6
|
-
cookie?: string;
|
|
7
|
-
httpOnly?: boolean;
|
|
8
|
-
presentCookie?: string;
|
|
9
|
-
};
|
|
5
|
+
auth?: ConvexAuthConfig;
|
|
10
6
|
}
|
|
11
7
|
/** Read `runtimeConfig.public.convex`, or `undefined` outside a Nitro request. */
|
|
12
8
|
export declare function readConvexConfig(event?: H3Event): ConvexPublicConfig | undefined;
|
|
@@ -8,9 +8,17 @@ export interface ConvexFetchOptions {
|
|
|
8
8
|
url?: string;
|
|
9
9
|
/**
|
|
10
10
|
* JWT for this call. Prefer per-request tokens — never put JWTs in public config.
|
|
11
|
-
* When omitted and `event` is passed, falls back to
|
|
11
|
+
* When omitted and `event` is passed, falls back to the request's session
|
|
12
|
+
* (Convex Auth SSR mode) or `convex.auth.cookie` (BYO).
|
|
13
|
+
* Ignored when {@link adminToken} is set (admin auth clears user JWT).
|
|
12
14
|
*/
|
|
13
15
|
token?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Deploy / admin key for privileged server tooling (Next.js `fetchQuery`
|
|
18
|
+
* parity). Calls `ConvexHttpClient.setAdminAuth`. Never put this in public
|
|
19
|
+
* runtime config — pass per request from a server secret.
|
|
20
|
+
*/
|
|
21
|
+
adminToken?: string;
|
|
14
22
|
/**
|
|
15
23
|
* Nitro/H3 event. Used to resolve runtimeConfig and the optional auth cookie.
|
|
16
24
|
*/
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { getCookie } from "h3";
|
|
2
1
|
import { createHttpClient } from "../utils/http.js";
|
|
3
|
-
import {
|
|
2
|
+
import { getConvexToken } from "./auth.js";
|
|
4
3
|
import { resolveFetchToken } from "../utils/fetchToken.js";
|
|
5
4
|
import { missingConvexUrlError } from "../utils/errors.js";
|
|
6
5
|
import { readConvexConfig } from "./convexConfig.js";
|
|
@@ -22,19 +21,17 @@ export function resolveToken(options) {
|
|
|
22
21
|
if (options.token !== void 0) {
|
|
23
22
|
return resolveFetchToken({ token: options.token });
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
if (!cookieName || !options.event) {
|
|
24
|
+
if (!options.event) {
|
|
27
25
|
return void 0;
|
|
28
26
|
}
|
|
29
|
-
return
|
|
30
|
-
cookieName,
|
|
31
|
-
cookieValue: getCookie(options.event, cookieName)
|
|
32
|
-
});
|
|
27
|
+
return getConvexToken(options.event);
|
|
33
28
|
}
|
|
34
29
|
function setupClient(options = {}) {
|
|
30
|
+
const token = options.adminToken ? void 0 : resolveToken(options);
|
|
35
31
|
return createHttpClient(resolveUrl(options), {
|
|
36
|
-
token:
|
|
37
|
-
|
|
32
|
+
...token ? { token } : {},
|
|
33
|
+
...options.adminToken ? { adminToken: options.adminToken } : {},
|
|
34
|
+
...options.skipConvexDeploymentUrlCheck !== void 0 ? { skipConvexDeploymentUrlCheck: options.skipConvexDeploymentUrlCheck } : {}
|
|
38
35
|
});
|
|
39
36
|
}
|
|
40
37
|
export async function fetchQuery(query, args, options) {
|