@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.
Files changed (88) hide show
  1. package/README.md +3 -5
  2. package/dist/bin.js +6488 -1571
  3. package/dist/browser/index.js +10 -7
  4. package/dist/browser/locks.js +3 -5
  5. package/dist/browser/navigation.js +7 -10
  6. package/dist/browser/runtime.js +35 -33
  7. package/dist/client/core/types.js +17 -0
  8. package/dist/client/factors/device.js +26 -19
  9. package/dist/client/index.js +151 -163
  10. package/dist/client/runtime/proxy.js +6 -6
  11. package/dist/client/services/adapters.js +3 -7
  12. package/dist/client/services/http.js +2 -5
  13. package/dist/client/services/resolve.js +5 -11
  14. package/dist/client/services/runtime.js +2 -5
  15. package/dist/component/_generated/component.d.ts +46 -0
  16. package/dist/component/index.d.ts +3 -3
  17. package/dist/component/model.d.ts +25 -25
  18. package/dist/component/public/identity/sessions.js +38 -1
  19. package/dist/component/public/identity/tokens.js +81 -3
  20. package/dist/component/public/identity/verifiers.js +9 -3
  21. package/dist/component/public.js +3 -3
  22. package/dist/component/schema.d.ts +320 -320
  23. package/dist/core/index.d.ts +380 -0
  24. package/dist/core/index.js +83 -0
  25. package/dist/otel.d.ts +13 -17
  26. package/dist/otel.js +39 -49
  27. package/dist/providers/email.d.ts +2 -2
  28. package/dist/providers/password.js +8 -16
  29. package/dist/providers/phone.js +2 -9
  30. package/dist/server/auth-context.d.ts +204 -0
  31. package/dist/server/auth-context.js +76 -0
  32. package/dist/server/auth.d.ts +25 -187
  33. package/dist/server/auth.js +5 -96
  34. package/dist/server/componentContext.d.ts +12 -0
  35. package/dist/server/componentContext.js +1 -0
  36. package/dist/server/config.js +1 -12
  37. package/dist/server/constants.js +6 -0
  38. package/dist/server/contract.d.ts +1 -1
  39. package/dist/server/core.js +5 -14
  40. package/dist/server/crypto.js +26 -18
  41. package/dist/server/db.js +6 -1
  42. package/dist/server/device.js +88 -78
  43. package/dist/server/http.d.ts +4 -3
  44. package/dist/server/http.js +74 -86
  45. package/dist/server/index.d.ts +2 -1
  46. package/dist/server/limits.js +22 -15
  47. package/dist/server/mounts.d.ts +103 -103
  48. package/dist/server/mutations/account.js +6 -4
  49. package/dist/server/mutations/invalidate.js +3 -6
  50. package/dist/server/mutations/oauth.js +86 -88
  51. package/dist/server/mutations/refresh.js +45 -87
  52. package/dist/server/mutations/register.js +19 -19
  53. package/dist/server/mutations/retrieve.js +17 -15
  54. package/dist/server/mutations/signature.js +9 -13
  55. package/dist/server/mutations/signin.js +7 -3
  56. package/dist/server/mutations/signout.js +10 -15
  57. package/dist/server/mutations/store.js +22 -12
  58. package/dist/server/mutations/verifier.js +11 -6
  59. package/dist/server/mutations/verify.js +55 -46
  60. package/dist/server/oauth/runtime.js +27 -25
  61. package/dist/server/passkey.js +299 -250
  62. package/dist/server/prefetch.js +283 -281
  63. package/dist/server/refresh.js +7 -60
  64. package/dist/server/runtime.d.ts +82 -206
  65. package/dist/server/runtime.js +63 -56
  66. package/dist/server/services/config.js +5 -3
  67. package/dist/server/services/logger.js +2 -4
  68. package/dist/server/services/providers.js +2 -4
  69. package/dist/server/services/refresh.js +2 -4
  70. package/dist/server/services/resolve.js +15 -14
  71. package/dist/server/services/signin.js +2 -4
  72. package/dist/server/sessions.js +32 -33
  73. package/dist/server/signin.js +177 -142
  74. package/dist/server/sso/domain.d.ts +20 -68
  75. package/dist/server/sso/domain.js +444 -413
  76. package/dist/server/sso/http.js +53 -59
  77. package/dist/server/sso/oidc.js +94 -80
  78. package/dist/server/tokens.js +13 -3
  79. package/dist/server/totp.js +153 -116
  80. package/dist/server/types.d.ts +2 -2
  81. package/dist/server/users.js +18 -23
  82. package/dist/server/utils/cache.js +51 -0
  83. package/dist/server/utils/dispatch.js +36 -0
  84. package/dist/server/utils/retry.js +24 -0
  85. package/dist/server/utils/span.js +32 -0
  86. package/dist/shared/errors.js +9 -3
  87. package/dist/shared/log.js +20 -22
  88. package/package.json +41 -33
@@ -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
- return runBoundary(Effect.tryPromise({
91
- try: async () => {
92
- const authHeader = request.headers.get("Authorization");
93
- if (!authHeader?.startsWith("Bearer ")) return new Response(JSON.stringify({
94
- error: "Missing or malformed Authorization: Bearer header.",
95
- code: "MISSING_BEARER_TOKEN"
96
- }), {
97
- status: 401,
98
- headers: {
99
- ...corsHeaders,
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
- if (options?.scope && !keyResult.value.scopes.can(options.scope.resource, options.scope.action)) return new Response(JSON.stringify({
134
- error: "This API key does not have the required permissions.",
135
- code: "SCOPE_CHECK_FAILED"
136
- }), {
137
- status: 403,
138
- headers: {
139
- ...corsHeaders,
140
- "Content-Type": "application/json"
141
- }
142
- });
143
- const result = await handler(Object.assign(genericCtx, { key: {
144
- userId: keyResult.value.userId,
145
- keyId: keyResult.value.keyId,
146
- scopes: keyResult.value.scopes
147
- } }), request);
148
- return result instanceof Response ? (() => {
149
- const headers = new Headers(result.headers);
150
- for (const [k, val] of Object.entries(corsHeaders)) if (!headers.has(k)) headers.set(k, val);
151
- return new Response(result.body, {
152
- status: result.status,
153
- statusText: result.statusText,
154
- headers
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
- })() : new Response(JSON.stringify(result), {
157
- status: 200,
158
- headers: {
159
- ...corsHeaders,
160
- "Content-Type": "application/json"
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
- catch: (error) => error
165
- }).pipe(Effect.catch((error) => Effect.sync(() => {
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
- return runBoundary(Effect.tryPromise({
207
- try: () => action(ctx, request),
208
- catch: (error) => error
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) {
@@ -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, AuthConfig, AuthContext, AuthContextConfig, ConvexAuthResult, InferAuth, InferClientApi, OptionalAuthContext, UserDoc, createAuth } from "./auth.js";
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 };
@@ -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
- const isSignInRateLimited = (ctx, identifier, config) => Effect.map(getRateLimitState(ctx, identifier, config), (state) => {
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
- const recordFailedSignIn = (ctx, identifier, config) => Effect.flatMap(getRateLimitState(ctx, identifier, config), (state) => state !== null ? Effect.promise(() => authDb(ctx, config).rateLimits.patch(state.limit._id, {
18
- attemptsLeft: state.attemptsLeft - 1,
19
- lastAttemptTime: Date.now()
20
- })) : Effect.promise(() => authDb(ctx, config).rateLimits.create({
21
- identifier,
22
- attemptsLeft: (config.signIn?.maxFailedAttemptsPerHour ?? DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR) - 1,
23
- lastAttemptTime: Date.now()
24
- })).pipe(Effect.asVoid));
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
- const resetSignInRateLimit = (ctx, identifier, config) => Effect.flatMap(getRateLimitState(ctx, identifier, config), (state) => state !== null ? Effect.promise(() => authDb(ctx, config).rateLimits.delete(state.limit._id)) : Effect.void);
30
- const getRateLimitState = (ctx, identifier, config) => Effect.map(Effect.promise(() => authDb(ctx, config).rateLimits.get(identifier)), (limit) => {
31
- const typedLimit = limit;
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 };