@robelest/convex-auth 0.0.2-preview.0 → 0.0.2-preview.2
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/dist/bin.cjs +17 -15
- package/dist/client/index.d.ts +84 -30
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +259 -59
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +46 -120
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +2 -4
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +2 -4
- package/dist/component/index.js.map +1 -1
- package/dist/component/public.d.ts +233 -167
- package/dist/component/public.d.ts.map +1 -1
- package/dist/component/public.js +328 -155
- package/dist/component/public.js.map +1 -1
- package/dist/component/schema.d.ts +127 -12
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +136 -10
- package/dist/component/schema.js.map +1 -1
- package/dist/providers/{Anonymous.d.ts → anonymous.d.ts} +8 -8
- package/dist/providers/{Anonymous.d.ts.map → anonymous.d.ts.map} +1 -1
- package/dist/providers/{Anonymous.js → anonymous.js} +9 -10
- package/dist/providers/anonymous.js.map +1 -0
- package/dist/providers/{ConvexCredentials.d.ts → credentials.d.ts} +11 -11
- package/dist/providers/credentials.d.ts.map +1 -0
- package/dist/providers/{ConvexCredentials.js → credentials.js} +8 -8
- package/dist/providers/credentials.js.map +1 -0
- package/dist/providers/{Email.d.ts → email.d.ts} +6 -6
- package/dist/providers/email.d.ts.map +1 -0
- package/dist/providers/{Email.js → email.js} +6 -6
- package/dist/providers/email.js.map +1 -0
- package/dist/providers/{Password.d.ts → password.d.ts} +10 -10
- package/dist/providers/{Password.d.ts.map → password.d.ts.map} +1 -1
- package/dist/providers/{Password.js → password.js} +19 -20
- package/dist/providers/password.js.map +1 -0
- package/dist/providers/{Phone.d.ts → phone.d.ts} +3 -3
- package/dist/providers/{Phone.d.ts.map → phone.d.ts.map} +1 -1
- package/dist/providers/{Phone.js → phone.js} +3 -3
- package/dist/providers/{Phone.js.map → phone.js.map} +1 -1
- package/dist/server/implementation/db.d.ts +5 -2
- package/dist/server/implementation/db.d.ts.map +1 -1
- package/dist/server/implementation/db.js +2 -1
- package/dist/server/implementation/db.js.map +1 -1
- package/dist/server/implementation/index.d.ts +285 -180
- package/dist/server/implementation/index.d.ts.map +1 -1
- package/dist/server/implementation/index.js +280 -173
- package/dist/server/implementation/index.js.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createAccountFromCredentials.js +8 -18
- package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +1 -1
- package/dist/server/implementation/mutations/createVerificationCode.js +16 -44
- package/dist/server/implementation/mutations/createVerificationCode.js.map +1 -1
- package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +1 -1
- package/dist/server/implementation/mutations/invalidateSessions.js +4 -8
- package/dist/server/implementation/mutations/invalidateSessions.js.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
- package/dist/server/implementation/mutations/modifyAccount.js +8 -19
- package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
- package/dist/server/implementation/mutations/refreshSession.d.ts.map +1 -1
- package/dist/server/implementation/mutations/refreshSession.js +9 -23
- package/dist/server/implementation/mutations/refreshSession.js.map +1 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +1 -1
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js +6 -12
- package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +1 -1
- package/dist/server/implementation/mutations/signIn.d.ts.map +1 -1
- package/dist/server/implementation/mutations/signIn.js +2 -1
- package/dist/server/implementation/mutations/signIn.js.map +1 -1
- package/dist/server/implementation/mutations/signOut.d.ts.map +1 -1
- package/dist/server/implementation/mutations/signOut.js +5 -6
- package/dist/server/implementation/mutations/signOut.js.map +1 -1
- package/dist/server/implementation/mutations/storeRef.d.ts +8 -0
- package/dist/server/implementation/mutations/storeRef.d.ts.map +1 -0
- package/dist/server/implementation/mutations/storeRef.js +8 -0
- package/dist/server/implementation/mutations/storeRef.js.map +1 -0
- package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
- package/dist/server/implementation/mutations/userOAuth.js +16 -53
- package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
- package/dist/server/implementation/mutations/verifier.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifier.js +4 -8
- package/dist/server/implementation/mutations/verifier.js.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifierSignature.js +6 -10
- package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +1 -1
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.js +7 -16
- package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +1 -1
- package/dist/server/implementation/provider.d.ts +2 -1
- package/dist/server/implementation/provider.d.ts.map +1 -1
- package/dist/server/implementation/provider.js.map +1 -1
- package/dist/server/implementation/rateLimit.d.ts.map +1 -1
- package/dist/server/implementation/rateLimit.js +13 -39
- package/dist/server/implementation/rateLimit.js.map +1 -1
- package/dist/server/implementation/refreshTokens.d.ts +1 -8
- package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
- package/dist/server/implementation/refreshTokens.js +14 -58
- package/dist/server/implementation/refreshTokens.js.map +1 -1
- package/dist/server/implementation/sessions.d.ts +2 -20
- package/dist/server/implementation/sessions.d.ts.map +1 -1
- package/dist/server/implementation/sessions.js +8 -35
- package/dist/server/implementation/sessions.js.map +1 -1
- package/dist/server/implementation/types.d.ts +11 -267
- package/dist/server/implementation/types.d.ts.map +1 -1
- package/dist/server/implementation/types.js +1 -181
- package/dist/server/implementation/types.js.map +1 -1
- package/dist/server/implementation/users.d.ts.map +1 -1
- package/dist/server/implementation/users.js +19 -67
- package/dist/server/implementation/users.js.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +255 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/provider_utils.d.ts +1 -1
- package/dist/server/provider_utils.d.ts.map +1 -1
- package/dist/server/provider_utils.js +2 -2
- package/dist/server/provider_utils.js.map +1 -1
- package/dist/server/types.d.ts +91 -52
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +3 -6
- package/src/cli/index.ts +20 -19
- package/src/client/index.ts +347 -110
- package/src/component/_generated/component.ts +55 -214
- package/src/component/index.ts +1 -11
- package/src/component/public.ts +366 -178
- package/src/component/schema.ts +150 -19
- package/src/providers/{Anonymous.ts → anonymous.ts} +10 -11
- package/src/providers/{ConvexCredentials.ts → credentials.ts} +11 -11
- package/src/providers/{Email.ts → email.ts} +5 -5
- package/src/providers/{Password.ts → password.ts} +22 -27
- package/src/providers/{Phone.ts → phone.ts} +2 -2
- package/src/server/implementation/db.ts +5 -2
- package/src/server/implementation/index.ts +368 -313
- package/src/server/implementation/mutations/createAccountFromCredentials.ts +11 -25
- package/src/server/implementation/mutations/createVerificationCode.ts +16 -47
- package/src/server/implementation/mutations/invalidateSessions.ts +4 -9
- package/src/server/implementation/mutations/modifyAccount.ts +8 -22
- package/src/server/implementation/mutations/refreshSession.ts +11 -24
- package/src/server/implementation/mutations/retrieveAccountWithCredentials.ts +9 -17
- package/src/server/implementation/mutations/signIn.ts +2 -1
- package/src/server/implementation/mutations/signOut.ts +5 -8
- package/src/server/implementation/mutations/storeRef.ts +7 -0
- package/src/server/implementation/mutations/userOAuth.ts +10 -50
- package/src/server/implementation/mutations/verifier.ts +4 -9
- package/src/server/implementation/mutations/verifierSignature.ts +6 -12
- package/src/server/implementation/mutations/verifyCodeAndSignIn.ts +7 -18
- package/src/server/implementation/provider.ts +2 -1
- package/src/server/implementation/rateLimit.ts +15 -41
- package/src/server/implementation/refreshTokens.ts +26 -76
- package/src/server/implementation/sessions.ts +8 -39
- package/src/server/implementation/types.ts +16 -191
- package/src/server/implementation/users.ts +19 -66
- package/src/server/index.ts +373 -0
- package/src/server/provider_utils.ts +2 -2
- package/src/server/types.ts +116 -51
- package/dist/providers/Anonymous.js.map +0 -1
- package/dist/providers/ConvexCredentials.d.ts.map +0 -1
- package/dist/providers/ConvexCredentials.js.map +0 -1
- package/dist/providers/Email.d.ts.map +0 -1
- package/dist/providers/Email.js.map +0 -1
- package/dist/providers/Password.js.map +0 -1
- package/providers/Anonymous/package.json +0 -6
- package/providers/ConvexCredentials/package.json +0 -6
- package/providers/Email/package.json +0 -6
- package/providers/Password/package.json +0 -6
- package/providers/Phone/package.json +0 -6
- package/server/package.json +0 -6
|
@@ -2,7 +2,7 @@ import { GenericId } from "convex/values";
|
|
|
2
2
|
import { Doc, MutationCtx } from "./types.js";
|
|
3
3
|
import { AuthProviderMaterializedConfig, ConvexAuthConfig } from "../types.js";
|
|
4
4
|
import { LOG_LEVELS, logWithLevel } from "./utils.js";
|
|
5
|
-
import {
|
|
5
|
+
import { authDb } from "./db.js";
|
|
6
6
|
|
|
7
7
|
type CreateOrUpdateUserArgs = {
|
|
8
8
|
type: "oauth" | "credentials" | "email" | "phone" | "verification";
|
|
@@ -56,8 +56,7 @@ async function defaultCreateOrUpdateUser(
|
|
|
56
56
|
args,
|
|
57
57
|
});
|
|
58
58
|
const existingUserId = existingAccount?.userId ?? null;
|
|
59
|
-
const authDb
|
|
60
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
59
|
+
const db = authDb(ctx, config);
|
|
61
60
|
if (config.callbacks?.createOrUpdateUser !== undefined) {
|
|
62
61
|
logWithLevel(LOG_LEVELS.DEBUG, "Using custom createOrUpdateUser callback");
|
|
63
62
|
return await config.callbacks.createOrUpdateUser(ctx, {
|
|
@@ -136,11 +135,7 @@ async function defaultCreateOrUpdateUser(
|
|
|
136
135
|
const existingOrLinkedUserId = userId;
|
|
137
136
|
if (userId !== null) {
|
|
138
137
|
try {
|
|
139
|
-
|
|
140
|
-
await authDb.users.patch(userId, userData);
|
|
141
|
-
} else {
|
|
142
|
-
await ctx.db.patch(userId, userData);
|
|
143
|
-
}
|
|
138
|
+
await db.users.patch(userId, userData);
|
|
144
139
|
} catch (error) {
|
|
145
140
|
throw new Error(
|
|
146
141
|
`Could not update user document with ID \`${userId}\`, ` +
|
|
@@ -150,10 +145,7 @@ async function defaultCreateOrUpdateUser(
|
|
|
150
145
|
);
|
|
151
146
|
}
|
|
152
147
|
} else {
|
|
153
|
-
userId =
|
|
154
|
-
authDb !== null
|
|
155
|
-
? ((await authDb.users.insert(userData)) as GenericId<"user">)
|
|
156
|
-
: await ctx.db.insert("user", userData);
|
|
148
|
+
userId = (await db.users.insert(userData)) as GenericId<"user">;
|
|
157
149
|
}
|
|
158
150
|
const afterUserCreatedOrUpdated = config.callbacks?.afterUserCreatedOrUpdated;
|
|
159
151
|
if (afterUserCreatedOrUpdated !== undefined) {
|
|
@@ -180,16 +172,8 @@ async function uniqueUserWithVerifiedEmail(
|
|
|
180
172
|
email: string,
|
|
181
173
|
config: ConvexAuthConfig,
|
|
182
174
|
) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return (await authDb.users.findByVerifiedEmail(email)) as Doc<"user"> | null;
|
|
186
|
-
}
|
|
187
|
-
const users = await ctx.db
|
|
188
|
-
.query("user")
|
|
189
|
-
.withIndex("email", (q) => q.eq("email", email))
|
|
190
|
-
.filter((q) => q.neq(q.field("emailVerificationTime"), undefined))
|
|
191
|
-
.take(2);
|
|
192
|
-
return users.length === 1 ? users[0] : null;
|
|
175
|
+
const db = authDb(ctx, config);
|
|
176
|
+
return (await db.users.findByVerifiedEmail(email)) as Doc<"user"> | null;
|
|
193
177
|
}
|
|
194
178
|
|
|
195
179
|
async function uniqueUserWithVerifiedPhone(
|
|
@@ -197,16 +181,8 @@ async function uniqueUserWithVerifiedPhone(
|
|
|
197
181
|
phone: string,
|
|
198
182
|
config: ConvexAuthConfig,
|
|
199
183
|
) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return (await authDb.users.findByVerifiedPhone(phone)) as Doc<"user"> | null;
|
|
203
|
-
}
|
|
204
|
-
const users = await ctx.db
|
|
205
|
-
.query("user")
|
|
206
|
-
.withIndex("phone", (q) => q.eq("phone", phone))
|
|
207
|
-
.filter((q) => q.neq(q.field("phoneVerificationTime"), undefined))
|
|
208
|
-
.take(2);
|
|
209
|
-
return users.length === 1 ? users[0] : null;
|
|
184
|
+
const db = authDb(ctx, config);
|
|
185
|
+
return (await db.users.findByVerifiedPhone(phone)) as Doc<"user"> | null;
|
|
210
186
|
}
|
|
211
187
|
|
|
212
188
|
async function createOrUpdateAccount(
|
|
@@ -221,49 +197,29 @@ async function createOrUpdateAccount(
|
|
|
221
197
|
args: CreateOrUpdateUserArgs,
|
|
222
198
|
config: ConvexAuthConfig,
|
|
223
199
|
) {
|
|
224
|
-
const authDb
|
|
225
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
200
|
+
const db = authDb(ctx, config);
|
|
226
201
|
const accountId =
|
|
227
202
|
"existingAccount" in account
|
|
228
203
|
? account.existingAccount._id
|
|
229
|
-
:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
})) as GenericId<"account">)
|
|
236
|
-
: await ctx.db.insert("account", {
|
|
237
|
-
userId,
|
|
238
|
-
provider: args.provider.id,
|
|
239
|
-
providerAccountId: account.providerAccountId,
|
|
240
|
-
secret: account.secret,
|
|
241
|
-
});
|
|
204
|
+
: ((await db.accounts.create({
|
|
205
|
+
userId,
|
|
206
|
+
provider: args.provider.id,
|
|
207
|
+
providerAccountId: account.providerAccountId,
|
|
208
|
+
secret: account.secret,
|
|
209
|
+
})) as GenericId<"account">);
|
|
242
210
|
// This is never used with the default `createOrUpdateUser` implementation,
|
|
243
211
|
// but it is used for manual linking via custom `createOrUpdateUser`:
|
|
244
212
|
if (
|
|
245
213
|
"existingAccount" in account &&
|
|
246
214
|
account.existingAccount.userId !== userId
|
|
247
215
|
) {
|
|
248
|
-
|
|
249
|
-
await authDb.accounts.patch(accountId, { userId });
|
|
250
|
-
} else {
|
|
251
|
-
await ctx.db.patch(accountId, { userId });
|
|
252
|
-
}
|
|
216
|
+
await db.accounts.patch(accountId, { userId });
|
|
253
217
|
}
|
|
254
218
|
if (args.profile.emailVerified) {
|
|
255
|
-
|
|
256
|
-
await authDb.accounts.patch(accountId, { emailVerified: args.profile.email });
|
|
257
|
-
} else {
|
|
258
|
-
await ctx.db.patch(accountId, { emailVerified: args.profile.email });
|
|
259
|
-
}
|
|
219
|
+
await db.accounts.patch(accountId, { emailVerified: args.profile.email });
|
|
260
220
|
}
|
|
261
221
|
if (args.profile.phoneVerified) {
|
|
262
|
-
|
|
263
|
-
await authDb.accounts.patch(accountId, { phoneVerified: args.profile.phone });
|
|
264
|
-
} else {
|
|
265
|
-
await ctx.db.patch(accountId, { phoneVerified: args.profile.phone });
|
|
266
|
-
}
|
|
222
|
+
await db.accounts.patch(accountId, { phoneVerified: args.profile.phone });
|
|
267
223
|
}
|
|
268
224
|
return accountId;
|
|
269
225
|
}
|
|
@@ -273,10 +229,7 @@ export async function getAccountOrThrow(
|
|
|
273
229
|
existingAccountId: GenericId<"account">,
|
|
274
230
|
config: ConvexAuthConfig,
|
|
275
231
|
) {
|
|
276
|
-
const existingAccount =
|
|
277
|
-
config.component !== undefined
|
|
278
|
-
? await createAuthDb(ctx, config.component).accounts.getById(existingAccountId)
|
|
279
|
-
: await ctx.db.get(existingAccountId);
|
|
232
|
+
const existingAccount = await authDb(ctx, config).accounts.getById(existingAccountId);
|
|
280
233
|
if (existingAccount === null) {
|
|
281
234
|
throw new Error(
|
|
282
235
|
`Expected an account to exist for ID "${existingAccountId}"`,
|
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
2
|
+
import { jwtDecode } from "jwt-decode";
|
|
1
3
|
import { parse, serialize } from "cookie";
|
|
4
|
+
import type {
|
|
5
|
+
SignInAction,
|
|
6
|
+
SignOutAction,
|
|
7
|
+
} from "./implementation/index.js";
|
|
2
8
|
import { isLocalHost } from "./utils.js";
|
|
3
9
|
|
|
4
10
|
export type AuthCookieConfig = {
|
|
@@ -11,6 +17,20 @@ export type AuthCookies = {
|
|
|
11
17
|
verifier: string | null;
|
|
12
18
|
};
|
|
13
19
|
|
|
20
|
+
export type ServerOptions = {
|
|
21
|
+
/** Convex deployment URL. */
|
|
22
|
+
url: string;
|
|
23
|
+
apiRoute?: string;
|
|
24
|
+
cookieMaxAge?: number | null;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
shouldHandleCode?: ((request: Request) => boolean | Promise<boolean>) | boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type RefreshResult = {
|
|
30
|
+
response?: Response;
|
|
31
|
+
cookies?: string[];
|
|
32
|
+
};
|
|
33
|
+
|
|
14
34
|
export function authCookieNames(host?: string) {
|
|
15
35
|
const prefix = isLocalHost(host) ? "" : "__Host-";
|
|
16
36
|
return {
|
|
@@ -72,3 +92,356 @@ export function shouldProxyAuthAction(pathname: string, apiRoute: string) {
|
|
|
72
92
|
}
|
|
73
93
|
return pathname === apiRoute || pathname === `${apiRoute}/`;
|
|
74
94
|
}
|
|
95
|
+
|
|
96
|
+
const REQUIRED_TOKEN_LIFETIME_MS = 60_000;
|
|
97
|
+
const MINIMUM_REQUIRED_TOKEN_LIFETIME_MS = 10_000;
|
|
98
|
+
|
|
99
|
+
type DecodedToken = { exp?: number; iat?: number };
|
|
100
|
+
|
|
101
|
+
export function server(options: ServerOptions) {
|
|
102
|
+
const convexUrl = options.url;
|
|
103
|
+
const apiRoute = options.apiRoute ?? "/api/auth";
|
|
104
|
+
const cookieConfig = { maxAge: options.cookieMaxAge ?? null };
|
|
105
|
+
const verbose = options.verbose ?? false;
|
|
106
|
+
|
|
107
|
+
const logVerbose = (message: string) => {
|
|
108
|
+
if (!verbose) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.debug(
|
|
112
|
+
`${new Date().toISOString()} [convex-auth/server] ${message}`,
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const cookieHost = (request: Request) => {
|
|
117
|
+
return request.headers.get("host") ?? new URL(request.url).host;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const parseRequestCookies = (request: Request) => {
|
|
121
|
+
return parseAuthCookies(request.headers.get("cookie"), cookieHost(request));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const attachCookies = (response: Response, cookies: string[]) => {
|
|
125
|
+
for (const value of cookies) {
|
|
126
|
+
response.headers.append("Set-Cookie", value);
|
|
127
|
+
}
|
|
128
|
+
return response;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const jsonResponse = (body: unknown, status = 200) => {
|
|
132
|
+
return new Response(JSON.stringify(body), {
|
|
133
|
+
status,
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const isCorsRequest = (request: Request) => {
|
|
141
|
+
const originHeader = request.headers.get("origin");
|
|
142
|
+
if (originHeader === null) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const requestUrl = new URL(request.url);
|
|
146
|
+
const originUrl = new URL(originHeader);
|
|
147
|
+
return (
|
|
148
|
+
originUrl.host !== requestUrl.host ||
|
|
149
|
+
originUrl.protocol !== requestUrl.protocol
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const decodeToken = (token: string): DecodedToken | null => {
|
|
154
|
+
try {
|
|
155
|
+
return jwtDecode<DecodedToken>(token);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const convexClient = (token?: string | null) => {
|
|
162
|
+
const client = new ConvexHttpClient(convexUrl);
|
|
163
|
+
if (token !== undefined && token !== null) {
|
|
164
|
+
client.setAuth(token);
|
|
165
|
+
}
|
|
166
|
+
return client;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const refreshTokens = async (
|
|
170
|
+
request: Request,
|
|
171
|
+
): Promise<{ token: string; refreshToken: string } | null | undefined> => {
|
|
172
|
+
const cookies = parseRequestCookies(request);
|
|
173
|
+
const { token, refreshToken } = cookies;
|
|
174
|
+
if (refreshToken === null && token === null) {
|
|
175
|
+
logVerbose("No auth cookies found, skipping refresh");
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
if (refreshToken === null || token === null) {
|
|
179
|
+
logVerbose("Only one auth cookie present, clearing auth cookies");
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const decodedToken = decodeToken(token);
|
|
183
|
+
if (decodedToken?.exp === undefined || decodedToken.iat === undefined) {
|
|
184
|
+
logVerbose("Failed to decode token, clearing auth cookies");
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const totalTokenLifetimeMs = decodedToken.exp * 1000 - decodedToken.iat * 1000;
|
|
188
|
+
const minimumExpiration =
|
|
189
|
+
Date.now() +
|
|
190
|
+
Math.min(
|
|
191
|
+
REQUIRED_TOKEN_LIFETIME_MS,
|
|
192
|
+
Math.max(MINIMUM_REQUIRED_TOKEN_LIFETIME_MS, totalTokenLifetimeMs / 10),
|
|
193
|
+
);
|
|
194
|
+
if (decodedToken.exp * 1000 > minimumExpiration) {
|
|
195
|
+
logVerbose("Token valid long enough, skipping refresh");
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const result = await convexClient().action(
|
|
201
|
+
"auth:signIn" as unknown as SignInAction,
|
|
202
|
+
{
|
|
203
|
+
refreshToken,
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
if (result.tokens === undefined) {
|
|
207
|
+
throw new Error("Invalid `auth:signIn` result for token refresh");
|
|
208
|
+
}
|
|
209
|
+
logVerbose(`Refreshed tokens, null=${result.tokens === null}`);
|
|
210
|
+
return result.tokens;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(error);
|
|
213
|
+
logVerbose("Token refresh failed, clearing auth cookies");
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
token(request: Request): string | null {
|
|
220
|
+
return parseRequestCookies(request).token;
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
async verify(request: Request): Promise<boolean> {
|
|
224
|
+
const token = parseRequestCookies(request).token;
|
|
225
|
+
if (token === null) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const decodedToken = decodeToken(token);
|
|
229
|
+
if (decodedToken?.exp === undefined) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return decodedToken.exp * 1000 > Date.now();
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
async proxy(request: Request): Promise<Response> {
|
|
236
|
+
const requestUrl = new URL(request.url);
|
|
237
|
+
if (!shouldProxyAuthAction(requestUrl.pathname, apiRoute)) {
|
|
238
|
+
return new Response("Invalid route", { status: 404 });
|
|
239
|
+
}
|
|
240
|
+
if (request.method !== "POST") {
|
|
241
|
+
return new Response("Invalid method", { status: 405 });
|
|
242
|
+
}
|
|
243
|
+
if (isCorsRequest(request)) {
|
|
244
|
+
return new Response("Invalid origin", { status: 403 });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const body = await request.json();
|
|
248
|
+
const action = body.action as string;
|
|
249
|
+
const args = (body.args ?? {}) as Record<string, any>;
|
|
250
|
+
|
|
251
|
+
if (action !== "auth:signIn" && action !== "auth:signOut") {
|
|
252
|
+
return new Response("Invalid action", { status: 400 });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const currentCookies = parseRequestCookies(request);
|
|
256
|
+
const host = cookieHost(request);
|
|
257
|
+
|
|
258
|
+
if (action === "auth:signIn") {
|
|
259
|
+
if (args.refreshToken !== undefined) {
|
|
260
|
+
if (currentCookies.refreshToken === null) {
|
|
261
|
+
return jsonResponse({ tokens: null });
|
|
262
|
+
}
|
|
263
|
+
args.refreshToken = currentCookies.refreshToken;
|
|
264
|
+
}
|
|
265
|
+
const client = convexClient(
|
|
266
|
+
args.refreshToken !== undefined || args.params?.code !== undefined
|
|
267
|
+
? null
|
|
268
|
+
: currentCookies.token,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const result = await client.action(
|
|
273
|
+
"auth:signIn" as unknown as SignInAction,
|
|
274
|
+
args,
|
|
275
|
+
);
|
|
276
|
+
if (result.redirect !== undefined) {
|
|
277
|
+
const response = jsonResponse({ redirect: result.redirect });
|
|
278
|
+
return attachCookies(
|
|
279
|
+
response,
|
|
280
|
+
serializeAuthCookies(
|
|
281
|
+
{
|
|
282
|
+
...currentCookies,
|
|
283
|
+
verifier: result.verifier ?? null,
|
|
284
|
+
},
|
|
285
|
+
host,
|
|
286
|
+
cookieConfig,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (result.tokens !== undefined) {
|
|
291
|
+
const response = jsonResponse({
|
|
292
|
+
tokens:
|
|
293
|
+
result.tokens === null
|
|
294
|
+
? null
|
|
295
|
+
: { token: result.tokens.token, refreshToken: "dummy" },
|
|
296
|
+
});
|
|
297
|
+
return attachCookies(
|
|
298
|
+
response,
|
|
299
|
+
serializeAuthCookies(
|
|
300
|
+
{
|
|
301
|
+
token: result.tokens?.token ?? null,
|
|
302
|
+
refreshToken: result.tokens?.refreshToken ?? null,
|
|
303
|
+
verifier: null,
|
|
304
|
+
},
|
|
305
|
+
host,
|
|
306
|
+
cookieConfig,
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return jsonResponse(result);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const response = jsonResponse({ error: (error as Error).message }, 400);
|
|
313
|
+
return attachCookies(
|
|
314
|
+
response,
|
|
315
|
+
serializeAuthCookies(
|
|
316
|
+
{
|
|
317
|
+
token: null,
|
|
318
|
+
refreshToken: null,
|
|
319
|
+
verifier: null,
|
|
320
|
+
},
|
|
321
|
+
host,
|
|
322
|
+
cookieConfig,
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
await convexClient(currentCookies.token).action(
|
|
330
|
+
"auth:signOut" as unknown as SignOutAction,
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error(error);
|
|
334
|
+
}
|
|
335
|
+
return attachCookies(
|
|
336
|
+
jsonResponse(null),
|
|
337
|
+
serializeAuthCookies(
|
|
338
|
+
{
|
|
339
|
+
token: null,
|
|
340
|
+
refreshToken: null,
|
|
341
|
+
verifier: null,
|
|
342
|
+
},
|
|
343
|
+
host,
|
|
344
|
+
cookieConfig,
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
async refresh(request: Request): Promise<RefreshResult> {
|
|
350
|
+
const host = cookieHost(request);
|
|
351
|
+
|
|
352
|
+
if (isCorsRequest(request)) {
|
|
353
|
+
return {
|
|
354
|
+
cookies: serializeAuthCookies(
|
|
355
|
+
{
|
|
356
|
+
token: null,
|
|
357
|
+
refreshToken: null,
|
|
358
|
+
verifier: null,
|
|
359
|
+
},
|
|
360
|
+
host,
|
|
361
|
+
cookieConfig,
|
|
362
|
+
),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const requestUrl = new URL(request.url);
|
|
367
|
+
const code = requestUrl.searchParams.get("code");
|
|
368
|
+
const shouldHandleCode =
|
|
369
|
+
options.shouldHandleCode === undefined
|
|
370
|
+
? true
|
|
371
|
+
: typeof options.shouldHandleCode === "function"
|
|
372
|
+
? await options.shouldHandleCode(request)
|
|
373
|
+
: options.shouldHandleCode;
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
code !== null &&
|
|
377
|
+
request.method === "GET" &&
|
|
378
|
+
request.headers.get("accept")?.includes("text/html") &&
|
|
379
|
+
shouldHandleCode
|
|
380
|
+
) {
|
|
381
|
+
const requestCookies = parseRequestCookies(request);
|
|
382
|
+
const redirectUrl = new URL(requestUrl);
|
|
383
|
+
redirectUrl.searchParams.delete("code");
|
|
384
|
+
try {
|
|
385
|
+
const result = await convexClient().action(
|
|
386
|
+
"auth:signIn" as unknown as SignInAction,
|
|
387
|
+
{
|
|
388
|
+
params: { code },
|
|
389
|
+
verifier: requestCookies.verifier ?? undefined,
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
if (result.tokens === undefined) {
|
|
393
|
+
throw new Error("Invalid `auth:signIn` result for code exchange");
|
|
394
|
+
}
|
|
395
|
+
const response = Response.redirect(redirectUrl.toString(), 302);
|
|
396
|
+
return {
|
|
397
|
+
response: attachCookies(
|
|
398
|
+
response,
|
|
399
|
+
serializeAuthCookies(
|
|
400
|
+
{
|
|
401
|
+
token: result.tokens?.token ?? null,
|
|
402
|
+
refreshToken: result.tokens?.refreshToken ?? null,
|
|
403
|
+
verifier: null,
|
|
404
|
+
},
|
|
405
|
+
host,
|
|
406
|
+
cookieConfig,
|
|
407
|
+
),
|
|
408
|
+
),
|
|
409
|
+
};
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error(error);
|
|
412
|
+
const response = Response.redirect(redirectUrl.toString(), 302);
|
|
413
|
+
return {
|
|
414
|
+
response: attachCookies(
|
|
415
|
+
response,
|
|
416
|
+
serializeAuthCookies(
|
|
417
|
+
{
|
|
418
|
+
token: null,
|
|
419
|
+
refreshToken: null,
|
|
420
|
+
verifier: null,
|
|
421
|
+
},
|
|
422
|
+
host,
|
|
423
|
+
cookieConfig,
|
|
424
|
+
),
|
|
425
|
+
),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const tokens = await refreshTokens(request);
|
|
431
|
+
if (tokens === undefined) {
|
|
432
|
+
return {};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
cookies: serializeAuthCookies(
|
|
436
|
+
{
|
|
437
|
+
token: tokens?.token ?? null,
|
|
438
|
+
refreshToken: tokens?.refreshToken ?? null,
|
|
439
|
+
verifier: null,
|
|
440
|
+
},
|
|
441
|
+
host,
|
|
442
|
+
cookieConfig,
|
|
443
|
+
),
|
|
444
|
+
};
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
@@ -59,13 +59,13 @@ export function configDefaults(config_: ConvexAuthConfig) {
|
|
|
59
59
|
* @internal
|
|
60
60
|
*/
|
|
61
61
|
export function materializeProvider(provider: AuthProviderConfig) {
|
|
62
|
-
const config = { providers: [provider] };
|
|
62
|
+
const config = { providers: [provider], component: {} as any };
|
|
63
63
|
materializeAndDefaultProviders(config);
|
|
64
64
|
return config.providers[0] as AuthProviderMaterializedConfig;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function materializeProviders(providers: AuthProviderConfig[]) {
|
|
68
|
-
const config = { providers };
|
|
68
|
+
const config = { providers, component: {} as any };
|
|
69
69
|
materializeAndDefaultProviders(config);
|
|
70
70
|
return config.providers as AuthProviderMaterializedConfig[];
|
|
71
71
|
}
|