@robelest/convex-auth 0.0.4-preview.27 → 0.0.4-preview.28
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 +3 -5
- package/dist/bin.js +6488 -1571
- package/dist/browser/index.js +10 -7
- package/dist/browser/locks.js +3 -5
- package/dist/browser/navigation.js +7 -10
- package/dist/browser/runtime.js +35 -33
- package/dist/client/core/types.js +17 -0
- package/dist/client/factors/device.js +26 -19
- package/dist/client/index.js +151 -163
- package/dist/client/runtime/proxy.js +6 -6
- package/dist/client/services/adapters.js +3 -7
- package/dist/client/services/http.js +2 -5
- package/dist/client/services/resolve.js +5 -11
- package/dist/client/services/runtime.js +2 -5
- package/dist/component/_generated/component.d.ts +46 -0
- package/dist/component/index.d.ts +3 -3
- package/dist/component/model.d.ts +25 -25
- package/dist/component/public/identity/sessions.js +38 -1
- package/dist/component/public/identity/tokens.js +81 -3
- package/dist/component/public/identity/verifiers.js +9 -3
- package/dist/component/public.js +3 -3
- package/dist/component/schema.d.ts +320 -320
- package/dist/core/index.d.ts +380 -0
- package/dist/core/index.js +83 -0
- package/dist/otel.d.ts +13 -17
- package/dist/otel.js +39 -49
- package/dist/providers/email.d.ts +2 -2
- package/dist/providers/password.js +8 -16
- package/dist/providers/phone.js +2 -9
- package/dist/server/auth-context.d.ts +204 -0
- package/dist/server/auth-context.js +76 -0
- package/dist/server/auth.d.ts +25 -187
- package/dist/server/auth.js +5 -96
- package/dist/server/componentContext.d.ts +12 -0
- package/dist/server/componentContext.js +1 -0
- package/dist/server/config.js +1 -12
- package/dist/server/constants.js +6 -0
- package/dist/server/contract.d.ts +1 -1
- package/dist/server/core.js +5 -14
- package/dist/server/crypto.js +26 -18
- package/dist/server/db.js +6 -1
- package/dist/server/device.js +88 -78
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.js +74 -86
- package/dist/server/index.d.ts +2 -1
- package/dist/server/limits.js +22 -15
- package/dist/server/mounts.d.ts +103 -103
- package/dist/server/mutations/account.js +6 -4
- package/dist/server/mutations/invalidate.js +3 -6
- package/dist/server/mutations/oauth.js +86 -88
- package/dist/server/mutations/refresh.js +45 -87
- package/dist/server/mutations/register.js +19 -19
- package/dist/server/mutations/retrieve.js +17 -15
- package/dist/server/mutations/signature.js +9 -13
- package/dist/server/mutations/signin.js +7 -3
- package/dist/server/mutations/signout.js +10 -15
- package/dist/server/mutations/store.js +22 -12
- package/dist/server/mutations/verifier.js +11 -6
- package/dist/server/mutations/verify.js +55 -46
- package/dist/server/oauth/runtime.js +27 -25
- package/dist/server/passkey.js +299 -250
- package/dist/server/prefetch.js +283 -281
- package/dist/server/refresh.js +7 -60
- package/dist/server/runtime.d.ts +82 -206
- package/dist/server/runtime.js +63 -56
- package/dist/server/services/config.js +5 -3
- package/dist/server/services/logger.js +2 -4
- package/dist/server/services/providers.js +2 -4
- package/dist/server/services/refresh.js +2 -4
- package/dist/server/services/resolve.js +15 -14
- package/dist/server/services/signin.js +2 -4
- package/dist/server/sessions.js +32 -33
- package/dist/server/signin.js +177 -142
- package/dist/server/sso/domain.d.ts +20 -68
- package/dist/server/sso/domain.js +444 -413
- package/dist/server/sso/http.js +53 -59
- package/dist/server/sso/oidc.js +94 -80
- package/dist/server/tokens.js +13 -3
- package/dist/server/totp.js +153 -116
- package/dist/server/types.d.ts +2 -2
- package/dist/server/users.js +18 -23
- package/dist/server/utils/cache.js +51 -0
- package/dist/server/utils/dispatch.js +36 -0
- package/dist/server/utils/retry.js +24 -0
- package/dist/server/utils/span.js +32 -0
- package/dist/shared/errors.js +9 -3
- package/dist/shared/log.js +20 -22
- package/package.json +41 -33
package/dist/server/http.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createUnauthenticatedAuthContext, getAuthContextForUser, getSessionUserId } from "./context.js";
|
|
2
2
|
import { logError } from "./log.js";
|
|
3
3
|
import { ConvexError } from "convex/values";
|
|
4
|
-
import { Cause, Effect, Exit } from "effect";
|
|
5
4
|
import { httpActionGeneric } from "convex/server";
|
|
6
5
|
import { parse } from "cookie";
|
|
7
6
|
|
|
@@ -26,12 +25,6 @@ function buildCorsHeaders(request, corsConfig, defaultOrigins) {
|
|
|
26
25
|
"Access-Control-Allow-Headers": corsConfig?.headers ?? "Content-Type,Authorization"
|
|
27
26
|
};
|
|
28
27
|
}
|
|
29
|
-
function runBoundary(effect) {
|
|
30
|
-
return Effect.runPromiseExit(effect).then(Exit.match({
|
|
31
|
-
onSuccess: (value) => value,
|
|
32
|
-
onFailure: (cause) => Promise.reject(Cause.squash(cause))
|
|
33
|
-
}));
|
|
34
|
-
}
|
|
35
28
|
async function getHttpKeyContext(auth, ctx, request) {
|
|
36
29
|
const authHeader = request.headers.get("Authorization");
|
|
37
30
|
if (!authHeader?.startsWith("Bearer sk_")) return null;
|
|
@@ -87,82 +80,78 @@ function createHttpAction(auth, defaultOrigins) {
|
|
|
87
80
|
return (handler, options) => {
|
|
88
81
|
return httpActionGeneric(async (genericCtx, request) => {
|
|
89
82
|
const corsHeaders = buildCorsHeaders(request, options?.cors, defaultOrigins);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"Content-Type": "application/json"
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
const rawKey = authHeader.slice(7);
|
|
104
|
-
const keyResult = await Effect.runPromise(Effect.tryPromise({
|
|
105
|
-
try: () => auth.key.verify(genericCtx, rawKey),
|
|
106
|
-
catch: (error) => error
|
|
107
|
-
}).pipe(Effect.match({
|
|
108
|
-
onFailure: (error) => ({
|
|
109
|
-
ok: false,
|
|
110
|
-
error
|
|
111
|
-
}),
|
|
112
|
-
onSuccess: (value) => ({
|
|
113
|
-
ok: true,
|
|
114
|
-
value
|
|
115
|
-
})
|
|
116
|
-
})));
|
|
117
|
-
if (!keyResult.ok) {
|
|
118
|
-
if (keyResult.error instanceof ConvexError && typeof keyResult.error.data === "object" && keyResult.error.data !== null && "code" in keyResult.error.data && "message" in keyResult.error.data) {
|
|
119
|
-
const { code, message } = keyResult.error.data;
|
|
120
|
-
return new Response(JSON.stringify({
|
|
121
|
-
error: message,
|
|
122
|
-
code
|
|
123
|
-
}), {
|
|
124
|
-
status: 403,
|
|
125
|
-
headers: {
|
|
126
|
-
...corsHeaders,
|
|
127
|
-
"Content-Type": "application/json"
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
throw keyResult.error;
|
|
83
|
+
try {
|
|
84
|
+
const authHeader = request.headers.get("Authorization");
|
|
85
|
+
if (!authHeader?.startsWith("Bearer ")) return new Response(JSON.stringify({
|
|
86
|
+
error: "Missing or malformed Authorization: Bearer header.",
|
|
87
|
+
code: "MISSING_BEARER_TOKEN"
|
|
88
|
+
}), {
|
|
89
|
+
status: 401,
|
|
90
|
+
headers: {
|
|
91
|
+
...corsHeaders,
|
|
92
|
+
"Content-Type": "application/json"
|
|
132
93
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
94
|
+
});
|
|
95
|
+
const rawKey = authHeader.slice(7);
|
|
96
|
+
let keyResult;
|
|
97
|
+
try {
|
|
98
|
+
keyResult = {
|
|
99
|
+
ok: true,
|
|
100
|
+
value: await auth.key.verify(genericCtx, rawKey)
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
keyResult = {
|
|
104
|
+
ok: false,
|
|
105
|
+
error
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!keyResult.ok) {
|
|
109
|
+
if (keyResult.error instanceof ConvexError && typeof keyResult.error.data === "object" && keyResult.error.data !== null && "code" in keyResult.error.data && "message" in keyResult.error.data) {
|
|
110
|
+
const { code, message } = keyResult.error.data;
|
|
111
|
+
return new Response(JSON.stringify({
|
|
112
|
+
error: message,
|
|
113
|
+
code
|
|
114
|
+
}), {
|
|
115
|
+
status: 403,
|
|
116
|
+
headers: {
|
|
117
|
+
...corsHeaders,
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
}
|
|
155
120
|
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
121
|
+
}
|
|
122
|
+
throw keyResult.error;
|
|
123
|
+
}
|
|
124
|
+
if (options?.scope && !keyResult.value.scopes.can(options.scope.resource, options.scope.action)) return new Response(JSON.stringify({
|
|
125
|
+
error: "This API key does not have the required permissions.",
|
|
126
|
+
code: "SCOPE_CHECK_FAILED"
|
|
127
|
+
}), {
|
|
128
|
+
status: 403,
|
|
129
|
+
headers: {
|
|
130
|
+
...corsHeaders,
|
|
131
|
+
"Content-Type": "application/json"
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
const result = await handler(Object.assign(genericCtx, { key: {
|
|
135
|
+
userId: keyResult.value.userId,
|
|
136
|
+
keyId: keyResult.value.keyId,
|
|
137
|
+
scopes: keyResult.value.scopes
|
|
138
|
+
} }), request);
|
|
139
|
+
return result instanceof Response ? (() => {
|
|
140
|
+
const headers = new Headers(result.headers);
|
|
141
|
+
for (const [k, val] of Object.entries(corsHeaders)) if (!headers.has(k)) headers.set(k, val);
|
|
142
|
+
return new Response(result.body, {
|
|
143
|
+
status: result.status,
|
|
144
|
+
statusText: result.statusText,
|
|
145
|
+
headers
|
|
162
146
|
});
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
|
|
147
|
+
})() : new Response(JSON.stringify(result), {
|
|
148
|
+
status: 200,
|
|
149
|
+
headers: {
|
|
150
|
+
...corsHeaders,
|
|
151
|
+
"Content-Type": "application/json"
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
166
155
|
logError(error);
|
|
167
156
|
return new Response(JSON.stringify({
|
|
168
157
|
error: "An unexpected error occurred.",
|
|
@@ -174,7 +163,7 @@ function createHttpAction(auth, defaultOrigins) {
|
|
|
174
163
|
"Content-Type": "application/json"
|
|
175
164
|
}
|
|
176
165
|
});
|
|
177
|
-
}
|
|
166
|
+
}
|
|
178
167
|
});
|
|
179
168
|
};
|
|
180
169
|
}
|
|
@@ -203,10 +192,9 @@ function createHttpRoute(wrapAction, defaultOrigins) {
|
|
|
203
192
|
}
|
|
204
193
|
function convertErrorsToResponse(errorStatusCode, action) {
|
|
205
194
|
return async (ctx, request) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}).pipe(Effect.catch((error) => Effect.sync(() => {
|
|
195
|
+
try {
|
|
196
|
+
return await action(ctx, request);
|
|
197
|
+
} catch (error) {
|
|
210
198
|
if (error instanceof ConvexError && typeof error.data === "object" && error.data !== null && "code" in error.data && "message" in error.data) return new Response(JSON.stringify({
|
|
211
199
|
code: error.data.code,
|
|
212
200
|
message: error.data.message
|
|
@@ -223,7 +211,7 @@ function convertErrorsToResponse(errorStatusCode, action) {
|
|
|
223
211
|
status: 500,
|
|
224
212
|
statusText: "Internal Server Error"
|
|
225
213
|
});
|
|
226
|
-
}
|
|
214
|
+
}
|
|
227
215
|
};
|
|
228
216
|
}
|
|
229
217
|
function getCookies(request) {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { AuthConfig, AuthContext, AuthContextConfig, InferAuth, OptionalAuthContext, UserDoc } from "./auth-context.js";
|
|
1
2
|
import { HttpAuthContext, HttpAuthContextConfig, OptionalHttpAuthContext } from "./http.js";
|
|
2
|
-
import { AuthApi, AuthApiBase,
|
|
3
|
+
import { AuthApi, AuthApiBase, ConvexAuthResult, InferClientApi, createAuth } from "./auth.js";
|
|
3
4
|
import { CreateAuthGroupSsoOptions, GroupSsoAccessHandler, GroupSsoAccessInput, GroupSsoAccessPermissions, GroupSsoPermission, GroupSsoResolvedAccessHandler, createAuthGroupSso, scim, sso } from "./mounts.js";
|
|
4
5
|
import { AuthCookie, AuthCookieConfig, AuthCookies, RefreshResult, ServerOptions, authCookieNames, parseAuthCookies, serializeAuthCookies, server, shouldProxyAuthAction, structuredAuthCookies } from "./prefetch.js";
|
|
5
6
|
export { type AuthApi, type AuthApiBase, type AuthConfig, type AuthContext, type AuthContextConfig, type AuthCookie, type AuthCookieConfig, type AuthCookies, type ConvexAuthResult, type CreateAuthGroupSsoOptions, type GroupSsoAccessHandler, type GroupSsoAccessInput, type GroupSsoAccessPermissions, type GroupSsoPermission, type GroupSsoResolvedAccessHandler, type HttpAuthContext, type HttpAuthContextConfig, type InferAuth, type InferClientApi, type OptionalAuthContext, type OptionalHttpAuthContext, type RefreshResult, type ServerOptions, type UserDoc, authCookieNames, createAuth, createAuthGroupSso, parseAuthCookies, scim, serializeAuthCookies, server, shouldProxyAuthAction, sso, structuredAuthCookies };
|
package/dist/server/limits.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { authDb } from "./db.js";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
2
|
|
|
4
3
|
//#region src/server/limits.ts
|
|
5
4
|
const DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR = 10;
|
|
@@ -7,28 +6,36 @@ const DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR = 10;
|
|
|
7
6
|
* Check whether the given identifier is currently rate-limited.
|
|
8
7
|
* @internal
|
|
9
8
|
*/
|
|
10
|
-
|
|
9
|
+
async function isSignInRateLimited(ctx, identifier, config) {
|
|
10
|
+
const state = await getRateLimitState(ctx, identifier, config);
|
|
11
11
|
return state !== null && state.attemptsLeft < 1;
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
13
|
/**
|
|
14
14
|
* Record a failed sign-in attempt for the given identifier.
|
|
15
15
|
* @internal
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
async function recordFailedSignIn(ctx, identifier, config) {
|
|
18
|
+
const state = await getRateLimitState(ctx, identifier, config);
|
|
19
|
+
if (state !== null) await authDb(ctx, config).rateLimits.patch(state.limit._id, {
|
|
20
|
+
attemptsLeft: state.attemptsLeft - 1,
|
|
21
|
+
lastAttemptTime: Date.now()
|
|
22
|
+
});
|
|
23
|
+
else await authDb(ctx, config).rateLimits.create({
|
|
24
|
+
identifier,
|
|
25
|
+
attemptsLeft: (config.signIn?.maxFailedAttemptsPerHour ?? DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR) - 1,
|
|
26
|
+
lastAttemptTime: Date.now()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
25
29
|
/**
|
|
26
30
|
* Reset the rate limit for the given identifier.
|
|
27
31
|
* @internal
|
|
28
32
|
*/
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
33
|
+
async function resetSignInRateLimit(ctx, identifier, config) {
|
|
34
|
+
const state = await getRateLimitState(ctx, identifier, config);
|
|
35
|
+
if (state !== null) await authDb(ctx, config).rateLimits.delete(state.limit._id);
|
|
36
|
+
}
|
|
37
|
+
async function getRateLimitState(ctx, identifier, config) {
|
|
38
|
+
const typedLimit = await authDb(ctx, config).rateLimits.get(identifier);
|
|
32
39
|
if (typedLimit === null) return null;
|
|
33
40
|
const now = Date.now();
|
|
34
41
|
const maxAttemptsPerHour = config.signIn?.maxFailedAttemptsPerHour ?? DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR;
|
|
@@ -38,7 +45,7 @@ const getRateLimitState = (ctx, identifier, config) => Effect.map(Effect.promise
|
|
|
38
45
|
limit: typedLimit,
|
|
39
46
|
attemptsLeft: Math.min(maxAttemptsPerHour, typedLimit.attemptsLeft + elapsed * maxAttemptsPerMs)
|
|
40
47
|
};
|
|
41
|
-
}
|
|
48
|
+
}
|
|
42
49
|
|
|
43
50
|
//#endregion
|
|
44
51
|
export { isSignInRateLimited, recordFailedSignIn, resetSignInRateLimit };
|