@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
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
import { ConvexAuthConfig } from "../../types.js";
|
|
15
15
|
import { LOG_LEVELS, logWithLevel, sha256 } from "../utils.js";
|
|
16
16
|
import { upsertUserAndAccount } from "../users.js";
|
|
17
|
-
import {
|
|
17
|
+
import { authDb } from "../db.js";
|
|
18
|
+
import { AUTH_STORE_REF } from "./storeRef.js";
|
|
18
19
|
|
|
19
20
|
export const verifyCodeAndSignInArgs = v.object({
|
|
20
21
|
params: v.any(),
|
|
@@ -87,7 +88,7 @@ export const callVerifyCodeAndSignIn = async (
|
|
|
87
88
|
ctx: ActionCtx,
|
|
88
89
|
args: Infer<typeof verifyCodeAndSignInArgs>,
|
|
89
90
|
): Promise<ReturnType> => {
|
|
90
|
-
return ctx.runMutation(
|
|
91
|
+
return ctx.runMutation(AUTH_STORE_REF, {
|
|
91
92
|
args: {
|
|
92
93
|
type: "verifyCodeAndSignIn",
|
|
93
94
|
...args,
|
|
@@ -116,26 +117,15 @@ async function verifyCodeOnly(
|
|
|
116
117
|
config: ConvexAuthConfig,
|
|
117
118
|
sessionId: GenericId<"session"> | null,
|
|
118
119
|
) {
|
|
119
|
-
const authDb
|
|
120
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
120
|
+
const db = authDb(ctx, config);
|
|
121
121
|
const { params, verifier } = args;
|
|
122
122
|
const codeHash = await sha256(params.code);
|
|
123
|
-
const verificationCode =
|
|
124
|
-
authDb !== null
|
|
125
|
-
? await authDb.verificationCodes.getByCode(codeHash)
|
|
126
|
-
: await ctx.db
|
|
127
|
-
.query("verification")
|
|
128
|
-
.withIndex("code", (q) => q.eq("code", codeHash))
|
|
129
|
-
.unique();
|
|
123
|
+
const verificationCode = await db.verificationCodes.getByCode(codeHash);
|
|
130
124
|
if (verificationCode === null) {
|
|
131
125
|
logWithLevel(LOG_LEVELS.ERROR, "Invalid verification code");
|
|
132
126
|
return null;
|
|
133
127
|
}
|
|
134
|
-
|
|
135
|
-
await authDb.verificationCodes.delete(verificationCode._id);
|
|
136
|
-
} else {
|
|
137
|
-
await ctx.db.delete(verificationCode._id);
|
|
138
|
-
}
|
|
128
|
+
await db.verificationCodes.delete(verificationCode._id);
|
|
139
129
|
if (verificationCode.verifier !== verifier) {
|
|
140
130
|
logWithLevel(LOG_LEVELS.ERROR, "Invalid verifier");
|
|
141
131
|
return null;
|
|
@@ -145,8 +135,7 @@ async function verifyCodeOnly(
|
|
|
145
135
|
return null;
|
|
146
136
|
}
|
|
147
137
|
const { accountId, emailVerified, phoneVerified } = verificationCode;
|
|
148
|
-
const account =
|
|
149
|
-
authDb !== null ? await authDb.accounts.getById(accountId) : await ctx.db.get(accountId);
|
|
138
|
+
const account = await db.accounts.getById(accountId);
|
|
150
139
|
if (account === null) {
|
|
151
140
|
logWithLevel(
|
|
152
141
|
LOG_LEVELS.ERROR,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthProviderMaterializedConfig } from "../types.js";
|
|
2
|
+
import { ConvexAuthMaterializedConfig } from "../types.js";
|
|
2
3
|
|
|
3
4
|
export async function hash(provider: any, secret: string) {
|
|
4
5
|
if (provider.type !== "credentials") {
|
|
@@ -35,4 +36,4 @@ export type GetProviderOrThrowFunc = (
|
|
|
35
36
|
allowExtraProviders?: boolean,
|
|
36
37
|
) => AuthProviderMaterializedConfig;
|
|
37
38
|
|
|
38
|
-
export type Config =
|
|
39
|
+
export type Config = ConvexAuthMaterializedConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConvexAuthConfig } from "../types.js";
|
|
2
2
|
import { Doc, MutationCtx } from "./types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { authDb } from "./db.js";
|
|
4
4
|
|
|
5
5
|
const DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR = 10;
|
|
6
6
|
|
|
@@ -21,34 +21,20 @@ export async function recordFailedSignIn(
|
|
|
21
21
|
identifier: string,
|
|
22
22
|
config: ConvexAuthConfig,
|
|
23
23
|
) {
|
|
24
|
+
const db = authDb(ctx, config);
|
|
24
25
|
const state = await getRateLimitState(ctx, identifier, config);
|
|
25
26
|
if (state !== null) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
await ctx.db.patch(state.limit._id, {
|
|
33
|
-
attemptsLeft: state.attempsLeft - 1,
|
|
34
|
-
lastAttemptTime: Date.now(),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
27
|
+
await db.rateLimits.patch(state.limit._id, {
|
|
28
|
+
attemptsLeft: state.attempsLeft - 1,
|
|
29
|
+
lastAttemptTime: Date.now(),
|
|
30
|
+
});
|
|
37
31
|
} else {
|
|
38
32
|
const maxAttempsPerHour = configuredMaxAttempsPerHour(config);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
await ctx.db.insert("limit", {
|
|
47
|
-
identifier,
|
|
48
|
-
attemptsLeft: maxAttempsPerHour - 1,
|
|
49
|
-
lastAttemptTime: Date.now(),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
33
|
+
await db.rateLimits.create({
|
|
34
|
+
identifier,
|
|
35
|
+
attemptsLeft: maxAttempsPerHour - 1,
|
|
36
|
+
lastAttemptTime: Date.now(),
|
|
37
|
+
});
|
|
52
38
|
}
|
|
53
39
|
}
|
|
54
40
|
|
|
@@ -59,13 +45,7 @@ export async function resetSignInRateLimit(
|
|
|
59
45
|
) {
|
|
60
46
|
const existingState = await getRateLimitState(ctx, identifier, config);
|
|
61
47
|
if (existingState !== null) {
|
|
62
|
-
|
|
63
|
-
await createAuthDb(ctx, config.component).rateLimits.delete(
|
|
64
|
-
existingState.limit._id,
|
|
65
|
-
);
|
|
66
|
-
} else {
|
|
67
|
-
await ctx.db.delete(existingState.limit._id);
|
|
68
|
-
}
|
|
48
|
+
await authDb(ctx, config).rateLimits.delete(existingState.limit._id);
|
|
69
49
|
}
|
|
70
50
|
}
|
|
71
51
|
|
|
@@ -76,15 +56,9 @@ async function getRateLimitState(
|
|
|
76
56
|
) {
|
|
77
57
|
const now = Date.now();
|
|
78
58
|
const maxAttempsPerHour = configuredMaxAttempsPerHour(config);
|
|
79
|
-
const limit =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
| Doc<"limit">
|
|
83
|
-
| null)
|
|
84
|
-
: await ctx.db
|
|
85
|
-
.query("limit")
|
|
86
|
-
.withIndex("identifier", (q) => q.eq("identifier", identifier))
|
|
87
|
-
.unique();
|
|
59
|
+
const limit = (await authDb(ctx, config).rateLimits.get(identifier)) as
|
|
60
|
+
| Doc<"limit">
|
|
61
|
+
| null;
|
|
88
62
|
if (limit === null) {
|
|
89
63
|
return null;
|
|
90
64
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
maybeRedact,
|
|
9
9
|
stringToNumber,
|
|
10
10
|
} from "./utils.js";
|
|
11
|
-
import {
|
|
11
|
+
import { authDb } from "./db.js";
|
|
12
12
|
|
|
13
13
|
const DEFAULT_SESSION_INACTIVE_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
|
|
14
14
|
export const REFRESH_TOKEN_REUSE_WINDOW_MS = 10 * 1000; // 10 seconds
|
|
@@ -17,24 +17,18 @@ export async function createRefreshToken(
|
|
|
17
17
|
config: ConvexAuthConfig,
|
|
18
18
|
sessionId: GenericId<"session">,
|
|
19
19
|
parentRefreshTokenId: GenericId<"token"> | null,
|
|
20
|
-
) {
|
|
20
|
+
): Promise<GenericId<"token">> {
|
|
21
|
+
const db = authDb(ctx, config);
|
|
21
22
|
const expirationTime =
|
|
22
23
|
Date.now() +
|
|
23
24
|
(config.session?.inactiveDurationMs ??
|
|
24
25
|
stringToNumber(process.env.AUTH_SESSION_INACTIVE_DURATION_MS) ??
|
|
25
26
|
DEFAULT_SESSION_INACTIVE_DURATION_MS);
|
|
26
|
-
|
|
27
|
-
return (await createAuthDb(ctx, config.component).refreshTokens.create({
|
|
28
|
-
sessionId,
|
|
29
|
-
expirationTime,
|
|
30
|
-
parentRefreshTokenId: parentRefreshTokenId ?? undefined,
|
|
31
|
-
})) as GenericId<"token">;
|
|
32
|
-
}
|
|
33
|
-
const newRefreshTokenId = await ctx.db.insert("token", {
|
|
27
|
+
const newRefreshTokenId = (await db.refreshTokens.create({
|
|
34
28
|
sessionId,
|
|
35
29
|
expirationTime,
|
|
36
30
|
parentRefreshTokenId: parentRefreshTokenId ?? undefined,
|
|
37
|
-
})
|
|
31
|
+
})) as GenericId<"token">;
|
|
38
32
|
return newRefreshTokenId;
|
|
39
33
|
}
|
|
40
34
|
|
|
@@ -74,27 +68,16 @@ export async function invalidateRefreshTokensInSubtree(
|
|
|
74
68
|
refreshToken: Doc<"token">,
|
|
75
69
|
config: ConvexAuthConfig,
|
|
76
70
|
) {
|
|
77
|
-
const authDb
|
|
78
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
71
|
+
const db = authDb(ctx, config);
|
|
79
72
|
const tokensToInvalidate = [refreshToken];
|
|
80
|
-
let frontier = [refreshToken._id];
|
|
73
|
+
let frontier: GenericId<"token">[] = [refreshToken._id];
|
|
81
74
|
while (frontier.length > 0) {
|
|
82
|
-
const nextFrontier = [];
|
|
75
|
+
const nextFrontier: GenericId<"token">[] = [];
|
|
83
76
|
for (const currentTokenId of frontier) {
|
|
84
|
-
const children =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
currentTokenId,
|
|
89
|
-
)) as Doc<"token">[])
|
|
90
|
-
: await ctx.db
|
|
91
|
-
.query("token")
|
|
92
|
-
.withIndex("sessionIdAndParentRefreshTokenId", (q) =>
|
|
93
|
-
q
|
|
94
|
-
.eq("sessionId", refreshToken.sessionId)
|
|
95
|
-
.eq("parentRefreshTokenId", currentTokenId),
|
|
96
|
-
)
|
|
97
|
-
.collect();
|
|
77
|
+
const children = (await db.refreshTokens.getChildren(
|
|
78
|
+
refreshToken.sessionId,
|
|
79
|
+
currentTokenId,
|
|
80
|
+
)) as Doc<"token">[];
|
|
98
81
|
tokensToInvalidate.push(...children);
|
|
99
82
|
nextFrontier.push(...children.map((child) => child._id));
|
|
100
83
|
}
|
|
@@ -106,15 +89,9 @@ export async function invalidateRefreshTokensInSubtree(
|
|
|
106
89
|
token.firstUsedTime === undefined ||
|
|
107
90
|
token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS
|
|
108
91
|
) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
await ctx.db.patch(token._id, {
|
|
115
|
-
firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
92
|
+
await db.refreshTokens.patch(token._id, {
|
|
93
|
+
firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,
|
|
94
|
+
});
|
|
118
95
|
}
|
|
119
96
|
}
|
|
120
97
|
return tokensToInvalidate;
|
|
@@ -125,19 +102,7 @@ export async function deleteAllRefreshTokens(
|
|
|
125
102
|
sessionId: GenericId<"session">,
|
|
126
103
|
config: ConvexAuthConfig,
|
|
127
104
|
) {
|
|
128
|
-
|
|
129
|
-
await createAuthDb(ctx, config.component).refreshTokens.deleteAll(sessionId);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const existingRefreshTokens = await ctx.db
|
|
133
|
-
.query("token")
|
|
134
|
-
.withIndex("sessionIdAndParentRefreshTokenId", (q) =>
|
|
135
|
-
q.eq("sessionId", sessionId),
|
|
136
|
-
)
|
|
137
|
-
.collect();
|
|
138
|
-
for (const refreshTokenDoc of existingRefreshTokens) {
|
|
139
|
-
await ctx.db.delete(refreshTokenDoc._id);
|
|
140
|
-
}
|
|
105
|
+
await authDb(ctx, config).refreshTokens.deleteAll(sessionId);
|
|
141
106
|
}
|
|
142
107
|
|
|
143
108
|
export async function refreshTokenIfValid(
|
|
@@ -146,16 +111,12 @@ export async function refreshTokenIfValid(
|
|
|
146
111
|
tokenSessionId: string,
|
|
147
112
|
config: ConvexAuthConfig,
|
|
148
113
|
) {
|
|
149
|
-
const authDb
|
|
150
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
114
|
+
const db = authDb(ctx, config);
|
|
151
115
|
let refreshTokenDoc: Doc<"token"> | null;
|
|
152
116
|
try {
|
|
153
|
-
refreshTokenDoc =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
refreshTokenId as GenericId<"token">,
|
|
157
|
-
)) as Doc<"token"> | null)
|
|
158
|
-
: await ctx.db.get(refreshTokenId as GenericId<"token">);
|
|
117
|
+
refreshTokenDoc = (await db.refreshTokens.getById(
|
|
118
|
+
refreshTokenId as GenericId<"token">,
|
|
119
|
+
)) as Doc<"token"> | null;
|
|
159
120
|
} catch {
|
|
160
121
|
logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token format");
|
|
161
122
|
return null;
|
|
@@ -175,12 +136,9 @@ export async function refreshTokenIfValid(
|
|
|
175
136
|
}
|
|
176
137
|
let session: Doc<"session"> | null;
|
|
177
138
|
try {
|
|
178
|
-
session =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
| Doc<"session">
|
|
182
|
-
| null)
|
|
183
|
-
: await ctx.db.get(refreshTokenDoc.sessionId);
|
|
139
|
+
session = (await db.sessions.getById(refreshTokenDoc.sessionId)) as
|
|
140
|
+
| Doc<"session">
|
|
141
|
+
| null;
|
|
184
142
|
} catch {
|
|
185
143
|
logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token session format");
|
|
186
144
|
return null;
|
|
@@ -207,15 +165,7 @@ export async function loadActiveRefreshToken(
|
|
|
207
165
|
sessionId: GenericId<"session">,
|
|
208
166
|
config: ConvexAuthConfig,
|
|
209
167
|
) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
)) as Doc<"token"> | null;
|
|
214
|
-
}
|
|
215
|
-
return ctx.db
|
|
216
|
-
.query("token")
|
|
217
|
-
.withIndex("sessionId", (q) => q.eq("sessionId", sessionId))
|
|
218
|
-
.filter((q) => q.eq(q.field("firstUsedTime"), undefined))
|
|
219
|
-
.order("desc")
|
|
220
|
-
.first();
|
|
168
|
+
return (await authDb(ctx, config).refreshTokens.getActive(sessionId)) as
|
|
169
|
+
| Doc<"token">
|
|
170
|
+
| null;
|
|
221
171
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
formatRefreshToken,
|
|
16
16
|
deleteAllRefreshTokens,
|
|
17
17
|
} from "./refreshTokens.js";
|
|
18
|
-
import {
|
|
18
|
+
import { authDb } from "./db.js";
|
|
19
19
|
|
|
20
20
|
const DEFAULT_SESSION_TOTAL_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
|
|
21
21
|
|
|
@@ -45,14 +45,10 @@ export async function createNewAndDeleteExistingSession(
|
|
|
45
45
|
config: ConvexAuthConfig,
|
|
46
46
|
userId: GenericId<"user">,
|
|
47
47
|
) {
|
|
48
|
-
const authDb
|
|
49
|
-
config.component !== undefined ? createAuthDb(ctx, config.component) : null;
|
|
48
|
+
const db = authDb(ctx, config);
|
|
50
49
|
const existingSessionId = await getAuthSessionId(ctx);
|
|
51
50
|
if (existingSessionId !== null) {
|
|
52
|
-
const existingSession =
|
|
53
|
-
authDb !== null
|
|
54
|
-
? await authDb.sessions.getById(existingSessionId)
|
|
55
|
-
: await ctx.db.get(existingSessionId);
|
|
51
|
+
const existingSession = await db.sessions.getById(existingSessionId);
|
|
56
52
|
if (existingSession !== null) {
|
|
57
53
|
await deleteSession(ctx, existingSession, config);
|
|
58
54
|
}
|
|
@@ -95,18 +91,13 @@ async function createSession(
|
|
|
95
91
|
userId: GenericId<"user">,
|
|
96
92
|
config: ConvexAuthConfig,
|
|
97
93
|
) {
|
|
94
|
+
const db = authDb(ctx, config);
|
|
98
95
|
const expirationTime =
|
|
99
96
|
Date.now() +
|
|
100
97
|
(config.session?.totalDurationMs ??
|
|
101
98
|
stringToNumber(process.env.AUTH_SESSION_TOTAL_DURATION_MS) ??
|
|
102
99
|
DEFAULT_SESSION_TOTAL_DURATION_MS);
|
|
103
|
-
|
|
104
|
-
return (await createAuthDb(ctx, config.component).sessions.create(
|
|
105
|
-
userId,
|
|
106
|
-
expirationTime,
|
|
107
|
-
)) as GenericId<"session">;
|
|
108
|
-
}
|
|
109
|
-
return await ctx.db.insert("session", { expirationTime, userId });
|
|
100
|
+
return (await db.sessions.create(userId, expirationTime)) as GenericId<"session">;
|
|
110
101
|
}
|
|
111
102
|
|
|
112
103
|
export async function deleteSession(
|
|
@@ -114,36 +105,14 @@ export async function deleteSession(
|
|
|
114
105
|
session: Doc<"session">,
|
|
115
106
|
config: ConvexAuthConfig,
|
|
116
107
|
) {
|
|
117
|
-
|
|
118
|
-
await createAuthDb(ctx, config.component).sessions.delete(session._id);
|
|
119
|
-
} else {
|
|
120
|
-
await ctx.db.delete(session._id);
|
|
121
|
-
}
|
|
108
|
+
await authDb(ctx, config).sessions.delete(session._id);
|
|
122
109
|
await deleteAllRefreshTokens(ctx, session._id, config);
|
|
123
110
|
}
|
|
124
111
|
|
|
125
112
|
/**
|
|
126
|
-
* Return the current session ID.
|
|
127
|
-
*
|
|
128
|
-
* ```ts filename="convex/myFunctions.tsx"
|
|
129
|
-
* import { mutation } from "./_generated/server";
|
|
130
|
-
* import { getAuthSessionId } from "@robelest/convex-auth/component";
|
|
131
|
-
*
|
|
132
|
-
* export const doSomething = mutation({
|
|
133
|
-
* args: {/* ... *\/},
|
|
134
|
-
* handler: async (ctx, args) => {
|
|
135
|
-
* const sessionId = await getAuthSessionId(ctx);
|
|
136
|
-
* if (sessionId === null) {
|
|
137
|
-
* throw new Error("Client is not authenticated!")
|
|
138
|
-
* }
|
|
139
|
-
* const session = await ctx.db.get(sessionId);
|
|
140
|
-
* // ...
|
|
141
|
-
* },
|
|
142
|
-
* });
|
|
143
|
-
* ```
|
|
113
|
+
* Return the current session ID from the auth identity subject.
|
|
144
114
|
*
|
|
145
|
-
*
|
|
146
|
-
* @returns the session ID or `null` if the client isn't authenticated
|
|
115
|
+
* Internal helper used by auth runtime internals and `auth.session.current`.
|
|
147
116
|
*/
|
|
148
117
|
export async function getAuthSessionId(ctx: { auth: Auth }) {
|
|
149
118
|
const identity = await ctx.auth.getUserIdentity();
|
|
@@ -4,215 +4,40 @@ import {
|
|
|
4
4
|
GenericMutationCtx,
|
|
5
5
|
GenericQueryCtx,
|
|
6
6
|
TableNamesInDataModel,
|
|
7
|
-
defineSchema,
|
|
8
|
-
defineTable,
|
|
9
7
|
} from "convex/server";
|
|
10
|
-
import { GenericId
|
|
8
|
+
import { GenericId } from "convex/values";
|
|
11
9
|
import { GenericDoc } from "../convex_types.js";
|
|
10
|
+
import schema from "../../component/schema.js";
|
|
12
11
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* Your schema must include these so that the indexes
|
|
17
|
-
* are set up:
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* ```ts filename="convex/schema.ts"
|
|
21
|
-
* import { defineSchema } from "convex/server";
|
|
22
|
-
* import { authTables } from "@robelest/convex-auth/component";
|
|
23
|
-
*
|
|
24
|
-
* const schema = defineSchema({
|
|
25
|
-
* ...authTables,
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* export default schema;
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* You can inline the table definitions into your schema
|
|
32
|
-
* and extend them with additional optional and required
|
|
33
|
-
* fields. See https://labs.convex.dev/auth/setup/schema
|
|
34
|
-
* for more details.
|
|
35
|
-
*/
|
|
36
|
-
export const authTables = {
|
|
37
|
-
/**
|
|
38
|
-
* Users.
|
|
39
|
-
*/
|
|
40
|
-
user: defineTable({
|
|
41
|
-
name: v.optional(v.string()),
|
|
42
|
-
image: v.optional(v.string()),
|
|
43
|
-
email: v.optional(v.string()),
|
|
44
|
-
emailVerificationTime: v.optional(v.number()),
|
|
45
|
-
phone: v.optional(v.string()),
|
|
46
|
-
phoneVerificationTime: v.optional(v.number()),
|
|
47
|
-
isAnonymous: v.optional(v.boolean()),
|
|
48
|
-
})
|
|
49
|
-
.index("email", ["email"])
|
|
50
|
-
.index("phone", ["phone"]),
|
|
51
|
-
/**
|
|
52
|
-
* Sessions.
|
|
53
|
-
* A single user can have multiple active sessions.
|
|
54
|
-
* See [Session document lifecycle](https://labs.convex.dev/auth/advanced#session-document-lifecycle).
|
|
55
|
-
*/
|
|
56
|
-
session: defineTable({
|
|
57
|
-
userId: v.id("user"),
|
|
58
|
-
expirationTime: v.number(),
|
|
59
|
-
}).index("userId", ["userId"]),
|
|
60
|
-
/**
|
|
61
|
-
* Accounts. An account corresponds to
|
|
62
|
-
* a single authentication provider.
|
|
63
|
-
* A single user can have multiple accounts linked.
|
|
64
|
-
*/
|
|
65
|
-
account: defineTable({
|
|
66
|
-
userId: v.id("user"),
|
|
67
|
-
provider: v.string(),
|
|
68
|
-
providerAccountId: v.string(),
|
|
69
|
-
secret: v.optional(v.string()),
|
|
70
|
-
emailVerified: v.optional(v.string()),
|
|
71
|
-
phoneVerified: v.optional(v.string()),
|
|
72
|
-
})
|
|
73
|
-
.index("userIdAndProvider", ["userId", "provider"])
|
|
74
|
-
.index("providerAndAccountId", ["provider", "providerAccountId"]),
|
|
75
|
-
/**
|
|
76
|
-
* Refresh tokens.
|
|
77
|
-
* Refresh tokens are generally meant to be used once, to be exchanged for another
|
|
78
|
-
* refresh token and a JWT access token, but with a few exceptions:
|
|
79
|
-
* - The "active refresh token" is the most recently created refresh token that has
|
|
80
|
-
* not been used yet. The parent of the active refresh token can always be used to
|
|
81
|
-
* obtain the active refresh token.
|
|
82
|
-
* - A refresh token can be used within a 10 second window ("reuse window") to
|
|
83
|
-
* obtain a new refresh token.
|
|
84
|
-
* - On any invalid use of a refresh token, the token itself and all its descendants
|
|
85
|
-
* are invalidated.
|
|
86
|
-
*/
|
|
87
|
-
token: defineTable({
|
|
88
|
-
sessionId: v.id("session"),
|
|
89
|
-
expirationTime: v.number(),
|
|
90
|
-
firstUsedTime: v.optional(v.number()),
|
|
91
|
-
// This is the ID of the refresh token that was exchanged to create this one.
|
|
92
|
-
parentRefreshTokenId: v.optional(v.id("token")),
|
|
93
|
-
})
|
|
94
|
-
// Sort by creationTime
|
|
95
|
-
.index("sessionId", ["sessionId"])
|
|
96
|
-
.index("sessionIdAndParentRefreshTokenId", [
|
|
97
|
-
"sessionId",
|
|
98
|
-
"parentRefreshTokenId",
|
|
99
|
-
]),
|
|
100
|
-
/**
|
|
101
|
-
* Verification codes:
|
|
102
|
-
* - OTP tokens
|
|
103
|
-
* - magic link tokens
|
|
104
|
-
* - OAuth codes
|
|
105
|
-
*/
|
|
106
|
-
verification: defineTable({
|
|
107
|
-
accountId: v.id("account"),
|
|
108
|
-
provider: v.string(),
|
|
109
|
-
code: v.string(),
|
|
110
|
-
expirationTime: v.number(),
|
|
111
|
-
verifier: v.optional(v.string()),
|
|
112
|
-
emailVerified: v.optional(v.string()),
|
|
113
|
-
phoneVerified: v.optional(v.string()),
|
|
114
|
-
})
|
|
115
|
-
.index("accountId", ["accountId"])
|
|
116
|
-
.index("code", ["code"]),
|
|
117
|
-
/**
|
|
118
|
-
* PKCE verifiers for OAuth.
|
|
119
|
-
*/
|
|
120
|
-
verifier: defineTable({
|
|
121
|
-
sessionId: v.optional(v.id("session")),
|
|
122
|
-
signature: v.optional(v.string()),
|
|
123
|
-
}).index("signature", ["signature"]),
|
|
124
|
-
/**
|
|
125
|
-
* Rate limits for OTP and password sign-in.
|
|
126
|
-
*/
|
|
127
|
-
limit: defineTable({
|
|
128
|
-
identifier: v.string(),
|
|
129
|
-
lastAttemptTime: v.number(),
|
|
130
|
-
attemptsLeft: v.number(),
|
|
131
|
-
}).index("identifier", ["identifier"]),
|
|
12
|
+
/** Data model derived from the component schema. */
|
|
13
|
+
export type AuthDataModel = DataModelFromSchemaDefinition<typeof schema>;
|
|
132
14
|
|
|
133
|
-
|
|
134
|
-
name: v.string(),
|
|
135
|
-
slug: v.optional(v.string()),
|
|
136
|
-
ownerUserId: v.optional(v.id("user")),
|
|
137
|
-
parentOrganizationId: v.optional(v.id("organization")),
|
|
138
|
-
metadata: v.optional(v.any()),
|
|
139
|
-
})
|
|
140
|
-
.index("slug", ["slug"])
|
|
141
|
-
.index("ownerUserId", ["ownerUserId"])
|
|
142
|
-
.index("parentOrganizationId", ["parentOrganizationId"]),
|
|
143
|
-
team: defineTable({
|
|
144
|
-
organizationId: v.id("organization"),
|
|
145
|
-
name: v.string(),
|
|
146
|
-
slug: v.optional(v.string()),
|
|
147
|
-
parentTeamId: v.optional(v.id("team")),
|
|
148
|
-
metadata: v.optional(v.any()),
|
|
149
|
-
})
|
|
150
|
-
.index("organizationId", ["organizationId"])
|
|
151
|
-
.index("organizationIdAndSlug", ["organizationId", "slug"])
|
|
152
|
-
.index("parentTeamId", ["parentTeamId"]),
|
|
153
|
-
teamRelation: defineTable({
|
|
154
|
-
organizationId: v.id("organization"),
|
|
155
|
-
parentTeamId: v.id("team"),
|
|
156
|
-
childTeamId: v.id("team"),
|
|
157
|
-
relation: v.optional(v.string()),
|
|
158
|
-
})
|
|
159
|
-
.index("organizationId", ["organizationId"])
|
|
160
|
-
.index("organizationIdAndParentTeamId", ["organizationId", "parentTeamId"])
|
|
161
|
-
.index("organizationIdAndChildTeamId", ["organizationId", "childTeamId"]),
|
|
162
|
-
member: defineTable({
|
|
163
|
-
organizationId: v.id("organization"),
|
|
164
|
-
userId: v.id("user"),
|
|
165
|
-
teamId: v.optional(v.id("team")),
|
|
166
|
-
role: v.optional(v.string()),
|
|
167
|
-
status: v.optional(v.string()),
|
|
168
|
-
metadata: v.optional(v.any()),
|
|
169
|
-
})
|
|
170
|
-
.index("organizationId", ["organizationId"])
|
|
171
|
-
.index("organizationIdAndUserId", ["organizationId", "userId"])
|
|
172
|
-
.index("teamId", ["teamId"])
|
|
173
|
-
.index("userId", ["userId"]),
|
|
174
|
-
invite: defineTable({
|
|
175
|
-
organizationId: v.optional(v.id("organization")),
|
|
176
|
-
teamId: v.optional(v.id("team")),
|
|
177
|
-
invitedByUserId: v.id("user"),
|
|
178
|
-
email: v.string(),
|
|
179
|
-
tokenHash: v.string(),
|
|
180
|
-
role: v.optional(v.string()),
|
|
181
|
-
status: v.union(
|
|
182
|
-
v.literal("pending"),
|
|
183
|
-
v.literal("accepted"),
|
|
184
|
-
v.literal("revoked"),
|
|
185
|
-
v.literal("expired"),
|
|
186
|
-
),
|
|
187
|
-
expiresTime: v.number(),
|
|
188
|
-
acceptedByUserId: v.optional(v.id("user")),
|
|
189
|
-
acceptedTime: v.optional(v.number()),
|
|
190
|
-
metadata: v.optional(v.any()),
|
|
191
|
-
})
|
|
192
|
-
.index("tokenHash", ["tokenHash"])
|
|
193
|
-
.index("emailAndStatus", ["email", "status"])
|
|
194
|
-
.index("invitedByUserIdAndStatus", ["invitedByUserId", "status"])
|
|
195
|
-
.index("organizationId", ["organizationId"])
|
|
196
|
-
.index("organizationIdAndStatus", ["organizationId", "status"]),
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
type DefaultSchema = ReturnType<typeof defineSchema<typeof authTables>>;
|
|
200
|
-
|
|
201
|
-
export type AuthDataModel = DataModelFromSchemaDefinition<DefaultSchema>;
|
|
15
|
+
/** Action context typed to the auth component's data model. */
|
|
202
16
|
export type ActionCtx = GenericActionCtx<AuthDataModel>;
|
|
17
|
+
|
|
18
|
+
/** Mutation context typed to the auth component's data model. */
|
|
203
19
|
export type MutationCtx = GenericMutationCtx<AuthDataModel>;
|
|
20
|
+
|
|
21
|
+
/** Query context typed to the auth component's data model. */
|
|
204
22
|
export type QueryCtx = GenericQueryCtx<AuthDataModel>;
|
|
23
|
+
|
|
24
|
+
/** A document from any table in the auth component schema. */
|
|
205
25
|
export type Doc<T extends TableNamesInDataModel<AuthDataModel>> = GenericDoc<
|
|
206
26
|
AuthDataModel,
|
|
207
27
|
T
|
|
208
28
|
>;
|
|
209
29
|
|
|
30
|
+
/** A pair of JWT access token and refresh token. */
|
|
210
31
|
export type Tokens = { token: string; refreshToken: string };
|
|
32
|
+
|
|
33
|
+
/** Session information returned after authentication. */
|
|
211
34
|
export type SessionInfo = {
|
|
212
35
|
userId: GenericId<"user">;
|
|
213
36
|
sessionId: GenericId<"session">;
|
|
214
37
|
tokens: Tokens | null;
|
|
215
38
|
};
|
|
39
|
+
|
|
40
|
+
/** Session information with guaranteed non-null tokens. */
|
|
216
41
|
export type SessionInfoWithTokens = {
|
|
217
42
|
userId: GenericId<"user">;
|
|
218
43
|
sessionId: GenericId<"session">;
|