@convex-dev/better-auth 0.6.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 +201 -0
- package/README.md +44 -0
- package/dist/commonjs/client/adapter.d.ts +4 -0
- package/dist/commonjs/client/adapter.d.ts.map +1 -0
- package/dist/commonjs/client/adapter.js +189 -0
- package/dist/commonjs/client/adapter.js.map +1 -0
- package/dist/commonjs/client/cors.d.ts +72 -0
- package/dist/commonjs/client/cors.d.ts.map +1 -0
- package/dist/commonjs/client/cors.js +281 -0
- package/dist/commonjs/client/cors.js.map +1 -0
- package/dist/commonjs/client/index.d.ts +302 -0
- package/dist/commonjs/client/index.d.ts.map +1 -0
- package/dist/commonjs/client/index.js +232 -0
- package/dist/commonjs/client/index.js.map +1 -0
- package/dist/commonjs/client/plugins/index.d.ts +3 -0
- package/dist/commonjs/client/plugins/index.d.ts.map +1 -0
- package/dist/commonjs/client/plugins/index.js +3 -0
- package/dist/commonjs/client/plugins/index.js.map +1 -0
- package/dist/commonjs/component/_generated/api.d.ts +12 -0
- package/dist/commonjs/component/_generated/api.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/api.js +22 -0
- package/dist/commonjs/component/_generated/api.js.map +1 -0
- package/dist/commonjs/component/_generated/server.d.ts +64 -0
- package/dist/commonjs/component/_generated/server.d.ts.map +1 -0
- package/dist/commonjs/component/_generated/server.js +74 -0
- package/dist/commonjs/component/_generated/server.js.map +1 -0
- package/dist/commonjs/component/convex.config.d.ts +3 -0
- package/dist/commonjs/component/convex.config.d.ts.map +1 -0
- package/dist/commonjs/component/convex.config.js +4 -0
- package/dist/commonjs/component/convex.config.js.map +1 -0
- package/dist/commonjs/component/lib.d.ts +584 -0
- package/dist/commonjs/component/lib.d.ts.map +1 -0
- package/dist/commonjs/component/lib.js +323 -0
- package/dist/commonjs/component/lib.js.map +1 -0
- package/dist/commonjs/component/schema.d.ts +116 -0
- package/dist/commonjs/component/schema.d.ts.map +1 -0
- package/dist/commonjs/component/schema.js +68 -0
- package/dist/commonjs/component/schema.js.map +1 -0
- package/dist/commonjs/component/util.d.ts +394 -0
- package/dist/commonjs/component/util.d.ts.map +1 -0
- package/dist/commonjs/component/util.js +4 -0
- package/dist/commonjs/component/util.js.map +1 -0
- package/dist/commonjs/nextjs/index.d.ts +10 -0
- package/dist/commonjs/nextjs/index.d.ts.map +1 -0
- package/dist/commonjs/nextjs/index.js +23 -0
- package/dist/commonjs/nextjs/index.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/plugins/convex/client.d.ts +6 -0
- package/dist/commonjs/plugins/convex/client.d.ts.map +1 -0
- package/dist/commonjs/plugins/convex/client.js +7 -0
- package/dist/commonjs/plugins/convex/client.js.map +1 -0
- package/dist/commonjs/plugins/convex/index.d.ts +280 -0
- package/dist/commonjs/plugins/convex/index.d.ts.map +1 -0
- package/dist/commonjs/plugins/convex/index.js +253 -0
- package/dist/commonjs/plugins/convex/index.js.map +1 -0
- package/dist/commonjs/plugins/cross-domain/client.d.ts +123 -0
- package/dist/commonjs/plugins/cross-domain/client.d.ts.map +1 -0
- package/dist/commonjs/plugins/cross-domain/client.js +164 -0
- package/dist/commonjs/plugins/cross-domain/client.js.map +1 -0
- package/dist/commonjs/plugins/cross-domain/index.d.ts +81 -0
- package/dist/commonjs/plugins/cross-domain/index.d.ts.map +1 -0
- package/dist/commonjs/plugins/cross-domain/index.js +135 -0
- package/dist/commonjs/plugins/cross-domain/index.js.map +1 -0
- package/dist/commonjs/plugins/index.d.ts +3 -0
- package/dist/commonjs/plugins/index.d.ts.map +1 -0
- package/dist/commonjs/plugins/index.js +3 -0
- package/dist/commonjs/plugins/index.js.map +1 -0
- package/dist/commonjs/react/client.d.ts +31 -0
- package/dist/commonjs/react/client.d.ts.map +1 -0
- package/dist/commonjs/react/client.js +102 -0
- package/dist/commonjs/react/client.js.map +1 -0
- package/dist/commonjs/react/index.d.ts +9 -0
- package/dist/commonjs/react/index.d.ts.map +1 -0
- package/dist/commonjs/react/index.js +15 -0
- package/dist/commonjs/react/index.js.map +1 -0
- package/dist/commonjs/react-start/index.d.ts +10 -0
- package/dist/commonjs/react-start/index.d.ts.map +1 -0
- package/dist/commonjs/react-start/index.js +32 -0
- package/dist/commonjs/react-start/index.js.map +1 -0
- package/dist/esm/client/adapter.d.ts +4 -0
- package/dist/esm/client/adapter.d.ts.map +1 -0
- package/dist/esm/client/adapter.js +189 -0
- package/dist/esm/client/adapter.js.map +1 -0
- package/dist/esm/client/cors.d.ts +72 -0
- package/dist/esm/client/cors.d.ts.map +1 -0
- package/dist/esm/client/cors.js +281 -0
- package/dist/esm/client/cors.js.map +1 -0
- package/dist/esm/client/index.d.ts +302 -0
- package/dist/esm/client/index.d.ts.map +1 -0
- package/dist/esm/client/index.js +232 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/plugins/index.d.ts +3 -0
- package/dist/esm/client/plugins/index.d.ts.map +1 -0
- package/dist/esm/client/plugins/index.js +3 -0
- package/dist/esm/client/plugins/index.js.map +1 -0
- package/dist/esm/component/_generated/api.d.ts +12 -0
- package/dist/esm/component/_generated/api.d.ts.map +1 -0
- package/dist/esm/component/_generated/api.js +22 -0
- package/dist/esm/component/_generated/api.js.map +1 -0
- package/dist/esm/component/_generated/server.d.ts +64 -0
- package/dist/esm/component/_generated/server.d.ts.map +1 -0
- package/dist/esm/component/_generated/server.js +74 -0
- package/dist/esm/component/_generated/server.js.map +1 -0
- package/dist/esm/component/convex.config.d.ts +3 -0
- package/dist/esm/component/convex.config.d.ts.map +1 -0
- package/dist/esm/component/convex.config.js +4 -0
- package/dist/esm/component/convex.config.js.map +1 -0
- package/dist/esm/component/lib.d.ts +584 -0
- package/dist/esm/component/lib.d.ts.map +1 -0
- package/dist/esm/component/lib.js +323 -0
- package/dist/esm/component/lib.js.map +1 -0
- package/dist/esm/component/schema.d.ts +116 -0
- package/dist/esm/component/schema.d.ts.map +1 -0
- package/dist/esm/component/schema.js +68 -0
- package/dist/esm/component/schema.js.map +1 -0
- package/dist/esm/component/util.d.ts +394 -0
- package/dist/esm/component/util.d.ts.map +1 -0
- package/dist/esm/component/util.js +4 -0
- package/dist/esm/component/util.js.map +1 -0
- package/dist/esm/nextjs/index.d.ts +10 -0
- package/dist/esm/nextjs/index.d.ts.map +1 -0
- package/dist/esm/nextjs/index.js +23 -0
- package/dist/esm/nextjs/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plugins/convex/client.d.ts +6 -0
- package/dist/esm/plugins/convex/client.d.ts.map +1 -0
- package/dist/esm/plugins/convex/client.js +7 -0
- package/dist/esm/plugins/convex/client.js.map +1 -0
- package/dist/esm/plugins/convex/index.d.ts +280 -0
- package/dist/esm/plugins/convex/index.d.ts.map +1 -0
- package/dist/esm/plugins/convex/index.js +253 -0
- package/dist/esm/plugins/convex/index.js.map +1 -0
- package/dist/esm/plugins/cross-domain/client.d.ts +123 -0
- package/dist/esm/plugins/cross-domain/client.d.ts.map +1 -0
- package/dist/esm/plugins/cross-domain/client.js +164 -0
- package/dist/esm/plugins/cross-domain/client.js.map +1 -0
- package/dist/esm/plugins/cross-domain/index.d.ts +81 -0
- package/dist/esm/plugins/cross-domain/index.d.ts.map +1 -0
- package/dist/esm/plugins/cross-domain/index.js +135 -0
- package/dist/esm/plugins/cross-domain/index.js.map +1 -0
- package/dist/esm/plugins/index.d.ts +3 -0
- package/dist/esm/plugins/index.d.ts.map +1 -0
- package/dist/esm/plugins/index.js +3 -0
- package/dist/esm/plugins/index.js.map +1 -0
- package/dist/esm/react/client.d.ts +31 -0
- package/dist/esm/react/client.d.ts.map +1 -0
- package/dist/esm/react/client.js +102 -0
- package/dist/esm/react/client.js.map +1 -0
- package/dist/esm/react/index.d.ts +9 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +15 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/react-start/index.d.ts +10 -0
- package/dist/esm/react-start/index.d.ts.map +1 -0
- package/dist/esm/react-start/index.js +32 -0
- package/dist/esm/react-start/index.js.map +1 -0
- package/package.json +161 -0
- package/plugins/package.json +5 -0
- package/react/package.json +5 -0
- package/src/client/adapter.ts +236 -0
- package/src/client/cors.ts +403 -0
- package/src/client/index.ts +381 -0
- package/src/client/plugins/index.ts +2 -0
- package/src/component/_generated/api.d.ts +313 -0
- package/src/component/_generated/api.js +23 -0
- package/src/component/_generated/dataModel.d.ts +60 -0
- package/src/component/_generated/server.d.ts +149 -0
- package/src/component/_generated/server.js +90 -0
- package/src/component/convex.config.ts +5 -0
- package/src/component/lib.ts +391 -0
- package/src/component/schema.ts +74 -0
- package/src/component/util.ts +4 -0
- package/src/nextjs/index.ts +30 -0
- package/src/plugins/convex/client.ts +9 -0
- package/src/plugins/convex/index.ts +296 -0
- package/src/plugins/cross-domain/client.ts +209 -0
- package/src/plugins/cross-domain/index.ts +156 -0
- package/src/plugins/index.ts +2 -0
- package/src/react/client.tsx +184 -0
- package/src/react/index.tsx +38 -0
- package/src/react-start/index.ts +51 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { setSessionCookie } from "better-auth/cookies";
|
|
2
|
+
import { generateRandomString } from "better-auth/crypto";
|
|
3
|
+
import {
|
|
4
|
+
BetterAuthPlugin,
|
|
5
|
+
createAuthEndpoint,
|
|
6
|
+
createAuthMiddleware,
|
|
7
|
+
oneTimeToken as oneTimeTokenPlugin,
|
|
8
|
+
} from "better-auth/plugins";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
export const crossDomain = ({ siteUrl }: { siteUrl: string }) => {
|
|
12
|
+
const oneTimeToken = oneTimeTokenPlugin();
|
|
13
|
+
|
|
14
|
+
const rewriteCallbackURL = (callbackURL?: string) => {
|
|
15
|
+
if (callbackURL && !callbackURL.startsWith("/")) {
|
|
16
|
+
return callbackURL;
|
|
17
|
+
}
|
|
18
|
+
const relativeCallbackURL = callbackURL || "/";
|
|
19
|
+
return new URL(relativeCallbackURL, siteUrl).toString();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
id: "cross-domain",
|
|
24
|
+
hooks: {
|
|
25
|
+
before: [
|
|
26
|
+
{
|
|
27
|
+
matcher(context) {
|
|
28
|
+
return Boolean(
|
|
29
|
+
context.request?.headers.get("better-auth-cookie") ||
|
|
30
|
+
context.headers?.get("better-auth-cookie")
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
handler: createAuthMiddleware(async (c) => {
|
|
34
|
+
const existingHeaders = (c.request?.headers ||
|
|
35
|
+
c.headers) as Headers;
|
|
36
|
+
const headers = new Headers({
|
|
37
|
+
...Object.fromEntries(existingHeaders?.entries()),
|
|
38
|
+
});
|
|
39
|
+
// Skip if the request has an authorization header
|
|
40
|
+
if (headers.get("authorization")) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const cookie = headers.get("better-auth-cookie");
|
|
44
|
+
if (!cookie) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
headers.append("cookie", cookie);
|
|
48
|
+
return {
|
|
49
|
+
context: {
|
|
50
|
+
headers,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
matcher: (ctx) => {
|
|
57
|
+
return (
|
|
58
|
+
ctx.path.startsWith("/link-social") ||
|
|
59
|
+
ctx.path.startsWith("/send-verification-email") ||
|
|
60
|
+
ctx.path.startsWith("/verify-email") ||
|
|
61
|
+
ctx.path.startsWith("/sign-in/email") ||
|
|
62
|
+
ctx.path.startsWith("/sign-in/social") ||
|
|
63
|
+
ctx.path.startsWith("/sign-in/magic-link") ||
|
|
64
|
+
ctx.path.startsWith("/delete-user") ||
|
|
65
|
+
ctx.path.startsWith("/change-email")
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
69
|
+
const isSignIn = ctx.path.startsWith("/sign-in");
|
|
70
|
+
ctx.body.callbackURL = rewriteCallbackURL(ctx.body.callbackURL);
|
|
71
|
+
if (isSignIn && ctx.body.newUserCallbackURL) {
|
|
72
|
+
ctx.body.newUserCallbackURL = rewriteCallbackURL(
|
|
73
|
+
ctx.body.newUserCallbackURL
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (isSignIn && ctx.body.errorCallbackURL) {
|
|
77
|
+
ctx.body.errorCallbackURL = rewriteCallbackURL(
|
|
78
|
+
ctx.body.errorCallbackURL
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return { context: ctx };
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
after: [
|
|
86
|
+
{
|
|
87
|
+
matcher() {
|
|
88
|
+
return true;
|
|
89
|
+
},
|
|
90
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
91
|
+
const setCookie = ctx.context.responseHeaders?.get("set-cookie");
|
|
92
|
+
if (!setCookie) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
ctx.context.responseHeaders?.delete("set-cookie");
|
|
96
|
+
ctx.setHeader("Set-Better-Auth-Cookie", setCookie);
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
matcher: (ctx) => {
|
|
101
|
+
return (
|
|
102
|
+
ctx.path?.startsWith("/callback") ||
|
|
103
|
+
ctx.path?.startsWith("/oauth2/callback") ||
|
|
104
|
+
ctx.path?.startsWith("/magic-link/verify")
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
108
|
+
// Mostly copied from the one-time-token plugin
|
|
109
|
+
const session = ctx.context.newSession;
|
|
110
|
+
if (!session) {
|
|
111
|
+
console.error("No session found");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const token = generateRandomString(32);
|
|
115
|
+
const expiresAt = new Date(Date.now() + 3 * 60 * 1000);
|
|
116
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
117
|
+
value: session.session.token,
|
|
118
|
+
identifier: `one-time-token:${token}`,
|
|
119
|
+
expiresAt,
|
|
120
|
+
});
|
|
121
|
+
const redirectTo = ctx.context.responseHeaders?.get("location");
|
|
122
|
+
if (!redirectTo) {
|
|
123
|
+
console.error("No redirect to found");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const url = new URL(redirectTo);
|
|
127
|
+
url.searchParams.set("ott", token);
|
|
128
|
+
throw ctx.redirect(url.toString());
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
endpoints: {
|
|
134
|
+
verifyOneTimeToken: createAuthEndpoint(
|
|
135
|
+
"/cross-domain/one-time-token/verify",
|
|
136
|
+
{
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: z.object({
|
|
139
|
+
token: z.string(),
|
|
140
|
+
}),
|
|
141
|
+
},
|
|
142
|
+
async (ctx) => {
|
|
143
|
+
const response = await oneTimeToken.endpoints.verifyOneTimeToken({
|
|
144
|
+
...ctx,
|
|
145
|
+
returnHeaders: false,
|
|
146
|
+
});
|
|
147
|
+
await setSessionCookie(ctx, response);
|
|
148
|
+
return response;
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
},
|
|
152
|
+
options: {
|
|
153
|
+
trustedOrigins: [siteUrl],
|
|
154
|
+
},
|
|
155
|
+
} satisfies BetterAuthPlugin;
|
|
156
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { ConvexReactClient } from "convex/react";
|
|
2
|
+
import isNetworkError from "is-network-error";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { createAuthClient } from "better-auth/react";
|
|
12
|
+
import { convexClient, crossDomainClient } from "../client/plugins";
|
|
13
|
+
import { BetterAuthClientPlugin, ClientOptions } from "better-auth";
|
|
14
|
+
|
|
15
|
+
const ConvexAuthInternalContext = createContext<{
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
isAuthenticated: boolean;
|
|
18
|
+
fetchAccessToken: ({
|
|
19
|
+
forceRefreshToken,
|
|
20
|
+
}: {
|
|
21
|
+
forceRefreshToken: boolean;
|
|
22
|
+
}) => Promise<string | null>;
|
|
23
|
+
}>(undefined as any);
|
|
24
|
+
|
|
25
|
+
export function useAuth() {
|
|
26
|
+
return useContext(ConvexAuthInternalContext);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ConvexAuthClient = {
|
|
30
|
+
verbose: boolean | undefined;
|
|
31
|
+
logger?: ConvexReactClient["logger"];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type CrossDomainClient = ReturnType<typeof crossDomainClient>;
|
|
35
|
+
type ConvexClient = ReturnType<typeof convexClient>;
|
|
36
|
+
type PluginsWithCrossDomain = (
|
|
37
|
+
| CrossDomainClient
|
|
38
|
+
| ConvexClient
|
|
39
|
+
| BetterAuthClientPlugin
|
|
40
|
+
)[];
|
|
41
|
+
type PluginsWithoutCrossDomain = (ConvexClient | BetterAuthClientPlugin)[];
|
|
42
|
+
type AuthClientWithPlugins<
|
|
43
|
+
Plugins extends PluginsWithCrossDomain | PluginsWithoutCrossDomain,
|
|
44
|
+
> = ReturnType<
|
|
45
|
+
typeof createAuthClient<
|
|
46
|
+
ClientOptions & {
|
|
47
|
+
plugins: Plugins;
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
>;
|
|
51
|
+
export type AuthClient =
|
|
52
|
+
| AuthClientWithPlugins<PluginsWithCrossDomain>
|
|
53
|
+
| AuthClientWithPlugins<PluginsWithoutCrossDomain>;
|
|
54
|
+
|
|
55
|
+
export function AuthProvider({
|
|
56
|
+
client,
|
|
57
|
+
authClient,
|
|
58
|
+
children,
|
|
59
|
+
}: {
|
|
60
|
+
client: ConvexAuthClient;
|
|
61
|
+
authClient: AuthClient;
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
}) {
|
|
64
|
+
const { data: session, isPending: isSessionPending } =
|
|
65
|
+
authClient.useSession();
|
|
66
|
+
|
|
67
|
+
const verbose: boolean = (client as any).options?.verbose ?? false;
|
|
68
|
+
const logVerbose = useCallback(
|
|
69
|
+
(message: string) => {
|
|
70
|
+
if (verbose) {
|
|
71
|
+
console.debug(`${new Date().toISOString()} ${message}`);
|
|
72
|
+
client.logger?.logVerbose(message);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[verbose]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const fetchToken = useCallback(async () => {
|
|
79
|
+
const initialBackoff = 100;
|
|
80
|
+
const maxBackoff = 1000;
|
|
81
|
+
let retries = 0;
|
|
82
|
+
|
|
83
|
+
const nextBackoff = () => {
|
|
84
|
+
const baseBackoff = initialBackoff * Math.pow(2, retries);
|
|
85
|
+
retries += 1;
|
|
86
|
+
const actualBackoff = Math.min(baseBackoff, maxBackoff);
|
|
87
|
+
const jitter = actualBackoff * (Math.random() - 0.5);
|
|
88
|
+
return actualBackoff + jitter;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const fetchWithRetry = async () => {
|
|
92
|
+
try {
|
|
93
|
+
const { data } = await authClient.convex.token();
|
|
94
|
+
return data?.token || null;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
if (!isNetworkError(e)) {
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
if (retries > 10) {
|
|
100
|
+
logVerbose(`fetchToken failed with network error, giving up`);
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
const backoff = nextBackoff();
|
|
104
|
+
logVerbose(
|
|
105
|
+
`fetchToken failed with network error, attempting retrying in ${backoff}ms`
|
|
106
|
+
);
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, backoff));
|
|
108
|
+
return fetchWithRetry();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return fetchWithRetry();
|
|
113
|
+
}, [client]);
|
|
114
|
+
|
|
115
|
+
const fetchAccessToken = useCallback(
|
|
116
|
+
async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
|
|
117
|
+
if (forceRefreshToken) {
|
|
118
|
+
const token = await fetchToken();
|
|
119
|
+
logVerbose(`returning retrieved token`);
|
|
120
|
+
return token;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
},
|
|
124
|
+
[fetchToken]
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
useEffect(
|
|
128
|
+
() => {
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
130
|
+
(async () => {
|
|
131
|
+
// Return early if cross domain plugin is not configured.
|
|
132
|
+
// Apparently there's no sane way to do this type check. Only the in
|
|
133
|
+
// keyword narrows the type effectively but it doesn't work on functions.
|
|
134
|
+
if (!(authClient as any)["crossDomain"]) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const authClientWithCrossDomain =
|
|
138
|
+
authClient as AuthClientWithPlugins<PluginsWithCrossDomain>;
|
|
139
|
+
const url = new URL(window.location.href);
|
|
140
|
+
const token = url.searchParams.get("ott");
|
|
141
|
+
if (token) {
|
|
142
|
+
url.searchParams.delete("ott");
|
|
143
|
+
const result =
|
|
144
|
+
await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
|
|
145
|
+
token,
|
|
146
|
+
});
|
|
147
|
+
const session = result.data?.session;
|
|
148
|
+
if (session) {
|
|
149
|
+
await authClient.getSession({
|
|
150
|
+
fetchOptions: {
|
|
151
|
+
headers: {
|
|
152
|
+
Authorization: `Bearer ${session.token}`,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
authClientWithCrossDomain.updateSession();
|
|
157
|
+
}
|
|
158
|
+
window.history.replaceState({}, "", url);
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
},
|
|
162
|
+
// Explicitly chosen dependencies.
|
|
163
|
+
// This effect should mostly only run once
|
|
164
|
+
// on mount.
|
|
165
|
+
[client, authClient]
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const isAuthenticated = session !== null;
|
|
169
|
+
const isLoading = isSessionPending;
|
|
170
|
+
const authState = useMemo(
|
|
171
|
+
() => ({
|
|
172
|
+
isLoading,
|
|
173
|
+
isAuthenticated,
|
|
174
|
+
fetchAccessToken,
|
|
175
|
+
}),
|
|
176
|
+
[fetchAccessToken, isLoading, isAuthenticated]
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<ConvexAuthInternalContext.Provider value={authState}>
|
|
181
|
+
{children}
|
|
182
|
+
</ConvexAuthInternalContext.Provider>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ConvexProviderWithAuth, ConvexReactClient } from "convex/react";
|
|
4
|
+
import { PropsWithChildren, useMemo } from "react";
|
|
5
|
+
import {
|
|
6
|
+
AuthClient,
|
|
7
|
+
AuthProvider,
|
|
8
|
+
useAuth,
|
|
9
|
+
type ConvexAuthClient,
|
|
10
|
+
} from "./client";
|
|
11
|
+
|
|
12
|
+
export function ConvexBetterAuthProvider({
|
|
13
|
+
client,
|
|
14
|
+
authClient,
|
|
15
|
+
children,
|
|
16
|
+
}: PropsWithChildren<{
|
|
17
|
+
client: ConvexReactClient;
|
|
18
|
+
authClient: AuthClient;
|
|
19
|
+
}>) {
|
|
20
|
+
const convexAuthClient = useMemo(
|
|
21
|
+
() =>
|
|
22
|
+
({
|
|
23
|
+
verbose: (client as any).options?.verbose,
|
|
24
|
+
logger: client.logger,
|
|
25
|
+
}) satisfies ConvexAuthClient,
|
|
26
|
+
[client]
|
|
27
|
+
);
|
|
28
|
+
return (
|
|
29
|
+
<AuthProvider client={convexAuthClient} authClient={authClient}>
|
|
30
|
+
<ConvexProviderWithAuth client={client} useAuth={useAuth}>
|
|
31
|
+
{children}
|
|
32
|
+
</ConvexProviderWithAuth>
|
|
33
|
+
</AuthProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO: Remove, short-lived alias
|
|
38
|
+
export { ConvexBetterAuthProvider as ConvexProviderWithBetterAuth };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Auth, betterAuth } from "better-auth";
|
|
2
|
+
import { createCookieGetter } from "better-auth/cookies";
|
|
3
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
4
|
+
import { GenericActionCtx } from "convex/server";
|
|
5
|
+
|
|
6
|
+
export const getCookieName = async (
|
|
7
|
+
createAuth: (ctx: GenericActionCtx<any>) => ReturnType<typeof betterAuth>
|
|
8
|
+
) => {
|
|
9
|
+
const auth = createAuth({} as any);
|
|
10
|
+
const createCookie = createCookieGetter(auth.options);
|
|
11
|
+
const cookie = createCookie("convex_jwt");
|
|
12
|
+
return cookie.name;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const fetchSession = async <
|
|
16
|
+
T extends (ctx: GenericActionCtx<any>) => ReturnType<typeof betterAuth>,
|
|
17
|
+
>(
|
|
18
|
+
createAuth: T,
|
|
19
|
+
request?: Request
|
|
20
|
+
) => {
|
|
21
|
+
type Session = ReturnType<T>["$Infer"]["Session"];
|
|
22
|
+
|
|
23
|
+
if (!request) {
|
|
24
|
+
throw new Error("No request found");
|
|
25
|
+
}
|
|
26
|
+
const baseURL = new URL(request.url).origin;
|
|
27
|
+
const { data: session } = await betterFetch<Session>(
|
|
28
|
+
"/api/auth/get-session",
|
|
29
|
+
{
|
|
30
|
+
baseURL,
|
|
31
|
+
headers: {
|
|
32
|
+
cookie: request.headers.get("cookie") ?? "",
|
|
33
|
+
origin: baseURL,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
return {
|
|
38
|
+
session,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const reactStartHandler = (
|
|
43
|
+
request: Request,
|
|
44
|
+
opts?: { convexSiteUrl?: string }
|
|
45
|
+
) => {
|
|
46
|
+
const convexSiteUrl = opts?.convexSiteUrl ?? process.env.CONVEX_SITE_URL;
|
|
47
|
+
const requestUrl = new URL(request.url);
|
|
48
|
+
const nextUrl = `${convexSiteUrl}${requestUrl.pathname}${requestUrl.search}`;
|
|
49
|
+
request.headers.set("accept-encoding", "application/json");
|
|
50
|
+
return fetch(nextUrl, new Request(request, { redirect: "manual" }));
|
|
51
|
+
};
|