better-auth 1.6.18 → 1.6.19
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/api/routes/email-verification.mjs +19 -6
- package/dist/api/routes/session.mjs +3 -12
- package/dist/client/config.d.mts +86 -0
- package/dist/client/lynx/index.d.mts +16 -103
- package/dist/client/react/index.d.mts +16 -103
- package/dist/client/solid/index.d.mts +15 -97
- package/dist/client/svelte/index.d.mts +16 -103
- package/dist/client/types.d.mts +2 -2
- package/dist/client/vanilla.d.mts +15 -103
- package/dist/client/vue/index.d.mts +35 -133
- package/dist/cookies/index.mjs +2 -12
- package/dist/cookies/session-store.d.mts +0 -17
- package/dist/cookies/session-store.mjs +42 -51
- package/dist/package.mjs +1 -1
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +5 -3
- package/dist/plugins/last-login-method/client.d.mts +10 -0
- package/dist/plugins/last-login-method/client.mjs +4 -1
- package/dist/plugins/oauth-popup/index.mjs +3 -2
- package/dist/plugins/open-api/generator.d.mts +1 -1
- package/dist/plugins/open-api/generator.mjs +56 -31
- package/dist/plugins/organization/adapter.mjs +3 -2
- package/dist/plugins/organization/routes/crud-access-control.mjs +1 -1
- package/dist/plugins/two-factor/backup-codes/index.mjs +4 -3
- package/dist/state.mjs +2 -1
- package/dist/test-utils/test-instance.d.mts +3 -12058
- package/package.json +10 -10
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { symmetricDecodeJWT, symmetricEncodeJWT } from "../crypto/jwt.mjs";
|
|
2
2
|
import { parseCookies } from "./cookie-utils.mjs";
|
|
3
3
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
4
|
+
import { serializeCookie } from "better-call";
|
|
4
5
|
import * as z from "zod";
|
|
5
6
|
//#region src/cookies/session-store.ts
|
|
6
|
-
const ALLOWED_COOKIE_SIZE = 4096;
|
|
7
|
-
const ESTIMATED_EMPTY_COOKIE_SIZE = 200;
|
|
8
|
-
const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
|
|
9
7
|
/**
|
|
10
|
-
*
|
|
8
|
+
* Per-cookie byte ceiling.
|
|
9
|
+
* Safari's ~4093 floor is the lowest among browsers.
|
|
10
|
+
* Kept a little under it for attributes added after sizing.
|
|
11
|
+
*
|
|
12
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6265#section-6.1
|
|
13
|
+
* @see https://github.com/dotnet/aspnetcore/blob/aa5493528640932601bb82ef3295e4d8ca7e11c5/src/Shared/ChunkingCookieManager/ChunkingCookieManager.cs#L40
|
|
14
|
+
*/
|
|
15
|
+
const MAX_COOKIE_SIZE = 4050;
|
|
16
|
+
/**
|
|
17
|
+
* Max chunks per cookie.
|
|
18
|
+
* A larger value does not belong in a cookie.
|
|
11
19
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const MAX_COOKIE_CHUNKS = 100;
|
|
21
|
+
/**
|
|
22
|
+
* Largest value that keeps the serialized cookie within {@link MAX_COOKIE_SIZE},
|
|
23
|
+
* measured with the real `serializeCookie` writer so it stays in sync with the
|
|
24
|
+
* wire. Non-positive when the name and attributes alone overflow.
|
|
25
|
+
*/
|
|
26
|
+
function getMaxCookieValueSize(name, options) {
|
|
27
|
+
return MAX_COOKIE_SIZE - serializeCookie(name, "", { ...options }).length;
|
|
17
28
|
}
|
|
18
29
|
/**
|
|
19
30
|
* Read all existing chunks from cookies
|
|
@@ -25,27 +36,24 @@ function readExistingChunks(cookieName, ctx) {
|
|
|
25
36
|
return chunks;
|
|
26
37
|
}
|
|
27
38
|
/**
|
|
28
|
-
* Get the full session data by joining all chunks
|
|
29
|
-
*/
|
|
30
|
-
function joinChunks(chunks) {
|
|
31
|
-
return Object.keys(chunks).sort((a, b) => {
|
|
32
|
-
return getChunkIndex(a) - getChunkIndex(b);
|
|
33
|
-
}).map((key) => chunks[key]).join("");
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
39
|
* Split a cookie value into chunks if needed
|
|
37
40
|
*/
|
|
38
41
|
function chunkCookie(storeName, cookie, chunks, logger) {
|
|
39
|
-
const
|
|
40
|
-
|
|
42
|
+
const chunkSize = getMaxCookieValueSize(`${cookie.name}.${MAX_COOKIE_CHUNKS - 1}`, cookie.attributes);
|
|
43
|
+
const chunkCount = chunkSize > 0 ? Math.ceil(cookie.value.length / chunkSize) : Infinity;
|
|
44
|
+
if (chunkCount <= 1) {
|
|
41
45
|
chunks[cookie.name] = cookie.value;
|
|
42
46
|
return [cookie];
|
|
43
47
|
}
|
|
48
|
+
if (chunkCount > MAX_COOKIE_CHUNKS) {
|
|
49
|
+
logger.warn(`${storeName} cookie is too large to store even after chunking, so the cache was skipped. Reduce the cached data or use a database session.`);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
44
52
|
const cookies = [];
|
|
45
53
|
for (let i = 0; i < chunkCount; i++) {
|
|
46
54
|
const name = `${cookie.name}.${i}`;
|
|
47
|
-
const start = i *
|
|
48
|
-
const value = cookie.value.substring(start, start +
|
|
55
|
+
const start = i * chunkSize;
|
|
56
|
+
const value = cookie.value.substring(start, start + chunkSize);
|
|
49
57
|
cookies.push({
|
|
50
58
|
...cookie,
|
|
51
59
|
name,
|
|
@@ -54,11 +62,10 @@ function chunkCookie(storeName, cookie, chunks, logger) {
|
|
|
54
62
|
chunks[name] = value;
|
|
55
63
|
}
|
|
56
64
|
logger.debug(`CHUNKING_${storeName.toUpperCase()}_COOKIE`, {
|
|
57
|
-
message: `${storeName} cookie exceeds
|
|
58
|
-
emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE,
|
|
65
|
+
message: `${storeName} cookie exceeds the ${MAX_COOKIE_SIZE} byte limit and was split into ${chunkCount} chunks.`,
|
|
59
66
|
valueSize: cookie.value.length,
|
|
60
67
|
chunkCount,
|
|
61
|
-
|
|
68
|
+
chunkSizes: cookies.map((c) => c.value.length)
|
|
62
69
|
});
|
|
63
70
|
return cookies;
|
|
64
71
|
}
|
|
@@ -78,26 +85,22 @@ function getCleanCookies(chunks, cookieOptions) {
|
|
|
78
85
|
return cleanedChunks;
|
|
79
86
|
}
|
|
80
87
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
88
|
+
* Store that splits a cookie into numbered chunks when its serialized form
|
|
89
|
+
* would exceed the per-cookie byte limit, expiring stale chunks as needed.
|
|
83
90
|
*
|
|
84
|
-
* Based on next-auth's SessionStore implementation.
|
|
85
91
|
* @see https://github.com/nextauthjs/next-auth/blob/27b2519b84b8eb9cf053775dea29d577d2aa0098/packages/next-auth/src/core/lib/cookie.ts
|
|
86
92
|
*/
|
|
87
93
|
const storeFactory = (storeName) => (cookieName, cookieOptions, ctx) => {
|
|
88
94
|
const chunks = readExistingChunks(cookieName, ctx);
|
|
89
95
|
const logger = ctx.context.logger;
|
|
96
|
+
const expireExistingChunks = () => {
|
|
97
|
+
const expired = getCleanCookies(chunks, cookieOptions);
|
|
98
|
+
for (const name in chunks) delete chunks[name];
|
|
99
|
+
return expired;
|
|
100
|
+
};
|
|
90
101
|
return {
|
|
91
|
-
getValue() {
|
|
92
|
-
return joinChunks(chunks);
|
|
93
|
-
},
|
|
94
|
-
hasChunks() {
|
|
95
|
-
return Object.keys(chunks).length > 0;
|
|
96
|
-
},
|
|
97
102
|
chunk(value, options) {
|
|
98
|
-
const
|
|
99
|
-
for (const name in chunks) delete chunks[name];
|
|
100
|
-
const cookies = cleanedChunks;
|
|
103
|
+
const cookies = expireExistingChunks();
|
|
101
104
|
const chunked = chunkCookie(storeName, {
|
|
102
105
|
name: cookieName,
|
|
103
106
|
value,
|
|
@@ -110,9 +113,7 @@ const storeFactory = (storeName) => (cookieName, cookieOptions, ctx) => {
|
|
|
110
113
|
return Object.values(cookies);
|
|
111
114
|
},
|
|
112
115
|
clean() {
|
|
113
|
-
|
|
114
|
-
for (const name in chunks) delete chunks[name];
|
|
115
|
-
return Object.values(cleanedChunks);
|
|
116
|
+
return Object.values(expireExistingChunks());
|
|
116
117
|
},
|
|
117
118
|
setCookies(cookies) {
|
|
118
119
|
for (const cookie of cookies) ctx.setCookie(cookie.name, cookie.value, cookie.attributes);
|
|
@@ -148,18 +149,8 @@ async function setAccountCookie(c, accountData) {
|
|
|
148
149
|
...accountDataCookie.attributes
|
|
149
150
|
};
|
|
150
151
|
const data = await symmetricEncodeJWT(accountData, c.context.secretConfig, "better-auth-account", options.maxAge);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const cookies = accountStore.chunk(data, options);
|
|
154
|
-
accountStore.setCookies(cookies);
|
|
155
|
-
} else {
|
|
156
|
-
const accountStore = createAccountStore(accountDataCookie.name, options, c);
|
|
157
|
-
if (accountStore.hasChunks()) {
|
|
158
|
-
const cleanCookies = accountStore.clean();
|
|
159
|
-
accountStore.setCookies(cleanCookies);
|
|
160
|
-
}
|
|
161
|
-
c.setCookie(accountDataCookie.name, data, options);
|
|
162
|
-
}
|
|
152
|
+
const accountStore = createAccountStore(accountDataCookie.name, options, c);
|
|
153
|
+
accountStore.setCookies(accountStore.chunk(data, options));
|
|
163
154
|
}
|
|
164
155
|
async function getAccountCookie(c) {
|
|
165
156
|
const accountCookie = getChunkedCookie(c, c.context.authCookies.accountData.name);
|
package/dist/package.mjs
CHANGED
|
@@ -104,6 +104,7 @@ declare const deviceAuthorization: (options?: Partial<DeviceAuthorizationOptions
|
|
|
104
104
|
method: "POST";
|
|
105
105
|
body: z.ZodObject<{
|
|
106
106
|
client_id: z.ZodString;
|
|
107
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
107
108
|
scope: z.ZodOptional<z.ZodString>;
|
|
108
109
|
}, z.core.$strip>;
|
|
109
110
|
error: z.ZodObject<{
|
|
@@ -9,6 +9,7 @@ import * as z from "zod";
|
|
|
9
9
|
const defaultCharset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
10
10
|
const deviceCodeBodySchema = z.object({
|
|
11
11
|
client_id: z.string().meta({ description: "The client ID of the application" }),
|
|
12
|
+
user_id: z.string().meta({ description: "The user ID to which the device code should be pre-bound." }).optional(),
|
|
12
13
|
scope: z.string().meta({ description: "Space-separated list of scopes" }).optional()
|
|
13
14
|
});
|
|
14
15
|
const deviceCodeErrorSchema = z.object({
|
|
@@ -99,7 +100,7 @@ Follow [rfc8628#section-3.2](https://datatracker.ietf.org/doc/html/rfc8628#secti
|
|
|
99
100
|
data: {
|
|
100
101
|
deviceCode,
|
|
101
102
|
userCode,
|
|
102
|
-
userId: null,
|
|
103
|
+
userId: ctx.body.user_id || null,
|
|
103
104
|
expiresAt,
|
|
104
105
|
status: "pending",
|
|
105
106
|
pollingInterval: ms(opts.interval),
|
|
@@ -341,7 +342,7 @@ const deviceVerify = createAuthEndpoint("/device", {
|
|
|
341
342
|
});
|
|
342
343
|
const session = await getSessionFromCtx(ctx);
|
|
343
344
|
if (session?.user?.id && !deviceCodeRecord.userId && deviceCodeRecord.status === "pending") {
|
|
344
|
-
if (await ctx.context.adapter.
|
|
345
|
+
if (await ctx.context.adapter.incrementOne({
|
|
345
346
|
model: "deviceCode",
|
|
346
347
|
where: [
|
|
347
348
|
{
|
|
@@ -358,7 +359,8 @@ const deviceVerify = createAuthEndpoint("/device", {
|
|
|
358
359
|
value: null
|
|
359
360
|
}
|
|
360
361
|
],
|
|
361
|
-
|
|
362
|
+
increment: {},
|
|
363
|
+
set: { userId: session.user.id }
|
|
362
364
|
})) deviceCodeRecord.userId = session.user.id;
|
|
363
365
|
}
|
|
364
366
|
return ctx.json({
|
|
@@ -8,6 +8,16 @@ interface LastLoginMethodClientConfig {
|
|
|
8
8
|
* @default "better-auth.last_used_login_method"
|
|
9
9
|
*/
|
|
10
10
|
cookieName?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Domain for the cookie. Required when using cross-subdomain cookies
|
|
13
|
+
* so the client can properly clear the cookie set by the server.
|
|
14
|
+
*
|
|
15
|
+
* Should match the `domain` value in your server's
|
|
16
|
+
* `crossSubDomainCookies` configuration.
|
|
17
|
+
*
|
|
18
|
+
* @example ".example.com"
|
|
19
|
+
*/
|
|
20
|
+
domain?: string | undefined;
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
23
|
* Client-side plugin to retrieve the last used login method
|
|
@@ -19,7 +19,10 @@ const lastLoginMethodClient = (config = {}) => {
|
|
|
19
19
|
return getCookieValue(cookieName);
|
|
20
20
|
},
|
|
21
21
|
clearLastUsedLoginMethod: () => {
|
|
22
|
-
if (typeof document !== "undefined")
|
|
22
|
+
if (typeof document !== "undefined") {
|
|
23
|
+
const domainPart = config.domain ? ` domain=${config.domain};` : "";
|
|
24
|
+
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;${domainPart}`;
|
|
25
|
+
}
|
|
23
26
|
},
|
|
24
27
|
isLastUsedLoginMethod: (method) => {
|
|
25
28
|
return getCookieValue(cookieName) === method;
|
|
@@ -3,7 +3,7 @@ import { generateRandomString } from "../../crypto/random.mjs";
|
|
|
3
3
|
import { expireCookie } from "../../cookies/index.mjs";
|
|
4
4
|
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
5
5
|
import { setOAuthState } from "../../api/state/oauth.mjs";
|
|
6
|
-
import { generateGenericState } from "../../state.mjs";
|
|
6
|
+
import { INTERNAL_STATE_KEYS, generateGenericState } from "../../state.mjs";
|
|
7
7
|
import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
|
|
8
8
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
9
9
|
import { OAUTH_POPUP_DATA_ELEMENT_ID, OAUTH_POPUP_MESSAGE_TYPE, POPUP_MARKER_COOKIE } from "./constants.mjs";
|
|
@@ -132,8 +132,9 @@ const oauthPopupStart = createAuthEndpoint("/oauth-popup/start", {
|
|
|
132
132
|
let url;
|
|
133
133
|
try {
|
|
134
134
|
const codeVerifier = generateRandomString(128);
|
|
135
|
+
const parsedAdditionalData = c.query.additionalData ? safeJSONParse(c.query.additionalData) ?? {} : {};
|
|
135
136
|
const stateData = {
|
|
136
|
-
...
|
|
137
|
+
...Object.fromEntries(Object.entries(parsedAdditionalData).filter(([key]) => !INTERNAL_STATE_KEYS.has(key))),
|
|
137
138
|
callbackURL,
|
|
138
139
|
codeVerifier,
|
|
139
140
|
errorURL: c.query.errorCallbackURL,
|
|
@@ -72,7 +72,7 @@ type OpenAPIOperation = {
|
|
|
72
72
|
};
|
|
73
73
|
type FieldSchema = {
|
|
74
74
|
type: DBFieldType;
|
|
75
|
-
default?:
|
|
75
|
+
default?: DBFieldAttributeConfig["defaultValue"] | undefined;
|
|
76
76
|
readOnly?: boolean | undefined;
|
|
77
77
|
format?: string;
|
|
78
78
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { db_exports } from "../../db/index.mjs";
|
|
2
2
|
import { getEndpoints } from "../../api/index.mjs";
|
|
3
3
|
import * as z from "zod";
|
|
4
|
-
import { toPascalCase } from "@better-auth/core/utils/string";
|
|
5
4
|
//#region src/plugins/open-api/generator.ts
|
|
6
5
|
const OPEN_API_SCHEMA_TYPES = new Set([
|
|
7
6
|
"string",
|
|
@@ -20,7 +19,9 @@ function getFieldSchema(field) {
|
|
|
20
19
|
type: field.type === "date" ? "string" : field.type,
|
|
21
20
|
...field.type === "date" && { format: "date-time" }
|
|
22
21
|
};
|
|
23
|
-
if (field.defaultValue !== void 0)
|
|
22
|
+
if (field.defaultValue !== void 0) {
|
|
23
|
+
if (typeof field.defaultValue !== "function") schema.default = field.defaultValue;
|
|
24
|
+
}
|
|
24
25
|
if (field.input === false) schema.readOnly = true;
|
|
25
26
|
return schema;
|
|
26
27
|
}
|
|
@@ -68,11 +69,8 @@ function getZodPipeSchema(zodType) {
|
|
|
68
69
|
}
|
|
69
70
|
function getParameters(options) {
|
|
70
71
|
const parameters = [];
|
|
71
|
-
if (options.metadata?.openapi?.parameters)
|
|
72
|
-
|
|
73
|
-
return parameters;
|
|
74
|
-
}
|
|
75
|
-
if (options.query instanceof z.ZodObject) Object.entries(options.query.shape).forEach(([key, value]) => {
|
|
72
|
+
if (options.metadata?.openapi?.parameters) parameters.push(...options.metadata.openapi.parameters);
|
|
73
|
+
if (!options.metadata?.openapi?.parameters && options.query instanceof z.ZodObject) Object.entries(options.query.shape).forEach(([key, value]) => {
|
|
76
74
|
if (value instanceof z.ZodType) {
|
|
77
75
|
const parameterSchema = toOpenApiSchema(value);
|
|
78
76
|
parameters.push({
|
|
@@ -84,6 +82,15 @@ function getParameters(options) {
|
|
|
84
82
|
});
|
|
85
83
|
return parameters;
|
|
86
84
|
}
|
|
85
|
+
function getPathParameters(path, parameters) {
|
|
86
|
+
const existingParameters = new Set(parameters.map((parameter) => `${parameter.in}:${parameter.name}`));
|
|
87
|
+
return path.split("/").filter((part) => part.startsWith(":")).map((part) => part.slice(1)).filter((name) => !existingParameters.has(`path:${name}`)).map((name) => ({
|
|
88
|
+
name,
|
|
89
|
+
in: "path",
|
|
90
|
+
required: true,
|
|
91
|
+
schema: { type: "string" }
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
87
94
|
function getRequestBodySchemaInfo(zodType) {
|
|
88
95
|
return {
|
|
89
96
|
required: !schemaAcceptsUndefined(zodType),
|
|
@@ -278,6 +285,29 @@ function getResponse(responses) {
|
|
|
278
285
|
function toOpenApiPath(path) {
|
|
279
286
|
return path.split("/").map((part) => part.startsWith(":") ? `{${part.slice(1)}}` : part).join("/");
|
|
280
287
|
}
|
|
288
|
+
function getOperationId(operationId, method, usedOperationIds) {
|
|
289
|
+
if (!operationId) return;
|
|
290
|
+
if (!usedOperationIds.has(operationId)) {
|
|
291
|
+
usedOperationIds.add(operationId);
|
|
292
|
+
return operationId;
|
|
293
|
+
}
|
|
294
|
+
const normalizedMethod = method.toUpperCase();
|
|
295
|
+
const methodSuffix = normalizedMethod.charAt(0) + normalizedMethod.slice(1).toLowerCase();
|
|
296
|
+
let candidate = `${operationId}${methodSuffix}`;
|
|
297
|
+
let duplicateIndex = 2;
|
|
298
|
+
while (usedOperationIds.has(candidate)) {
|
|
299
|
+
candidate = `${operationId}${methodSuffix}${duplicateIndex}`;
|
|
300
|
+
duplicateIndex += 1;
|
|
301
|
+
}
|
|
302
|
+
usedOperationIds.add(candidate);
|
|
303
|
+
return candidate;
|
|
304
|
+
}
|
|
305
|
+
function cloneOpenAPIValue(value) {
|
|
306
|
+
if (Array.isArray(value)) return value.map((item) => cloneOpenAPIValue(item));
|
|
307
|
+
if (value instanceof Date) return new Date(value);
|
|
308
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneOpenAPIValue(entry)]));
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
281
311
|
async function generator(ctx, options) {
|
|
282
312
|
const baseEndpoints = getEndpoints(ctx, {
|
|
283
313
|
...options,
|
|
@@ -315,31 +345,24 @@ async function generator(ctx, options) {
|
|
|
315
345
|
return acc;
|
|
316
346
|
}, {}) } };
|
|
317
347
|
const paths = {};
|
|
318
|
-
const
|
|
319
|
-
const uniqueOperationId = (operationId, method) => {
|
|
320
|
-
if (!operationId) return void 0;
|
|
321
|
-
const base = seenOperationIds.has(operationId) ? `${operationId}${toPascalCase(method)}` : operationId;
|
|
322
|
-
let result = base;
|
|
323
|
-
let n = 2;
|
|
324
|
-
while (seenOperationIds.has(result)) result = `${base}${n++}`;
|
|
325
|
-
seenOperationIds.add(result);
|
|
326
|
-
return result;
|
|
327
|
-
};
|
|
348
|
+
const usedOperationIds = /* @__PURE__ */ new Set();
|
|
328
349
|
Object.entries(baseEndpoints.api).forEach(([_, value]) => {
|
|
329
350
|
if (!value.path || ctx.options.disabledPaths?.includes(value.path)) return;
|
|
330
351
|
const options = value.options;
|
|
331
352
|
if (options.metadata?.SERVER_ONLY) return;
|
|
332
353
|
const path = toOpenApiPath(value.path);
|
|
354
|
+
const operationParameters = getParameters(options);
|
|
355
|
+
const parameters = [...operationParameters, ...getPathParameters(value.path, operationParameters)];
|
|
333
356
|
const methods = Array.isArray(options.method) ? options.method : [options.method];
|
|
334
357
|
for (const method of methods.filter((m) => m === "GET" || m === "DELETE")) paths[path] = {
|
|
335
358
|
...paths[path],
|
|
336
359
|
[method.toLowerCase()]: {
|
|
337
360
|
tags: ["Default", ...options.metadata?.openapi?.tags || []],
|
|
338
361
|
description: options.metadata?.openapi?.description,
|
|
339
|
-
operationId:
|
|
362
|
+
operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
|
|
340
363
|
security: [{ bearerAuth: [] }],
|
|
341
|
-
parameters:
|
|
342
|
-
responses: getResponse(options.metadata?.openapi?.responses)
|
|
364
|
+
parameters: cloneOpenAPIValue(parameters),
|
|
365
|
+
responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
|
|
343
366
|
}
|
|
344
367
|
};
|
|
345
368
|
for (const method of methods.filter((m) => m === "POST" || m === "PATCH" || m === "PUT")) {
|
|
@@ -349,14 +372,14 @@ async function generator(ctx, options) {
|
|
|
349
372
|
[method.toLowerCase()]: {
|
|
350
373
|
tags: ["Default", ...options.metadata?.openapi?.tags || []],
|
|
351
374
|
description: options.metadata?.openapi?.description,
|
|
352
|
-
operationId:
|
|
375
|
+
operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
|
|
353
376
|
security: [{ bearerAuth: [] }],
|
|
354
|
-
parameters:
|
|
355
|
-
...body ? { requestBody: body } : { requestBody: { content: { "application/json": { schema: {
|
|
377
|
+
parameters: cloneOpenAPIValue(parameters),
|
|
378
|
+
...body ? { requestBody: cloneOpenAPIValue(body) } : { requestBody: { content: { "application/json": { schema: {
|
|
356
379
|
type: "object",
|
|
357
380
|
properties: {}
|
|
358
381
|
} } } } },
|
|
359
|
-
responses: getResponse(options.metadata?.openapi?.responses)
|
|
382
|
+
responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
|
|
360
383
|
}
|
|
361
384
|
};
|
|
362
385
|
}
|
|
@@ -376,16 +399,18 @@ async function generator(ctx, options) {
|
|
|
376
399
|
const options = value.options;
|
|
377
400
|
if (options.metadata?.SERVER_ONLY) return;
|
|
378
401
|
const path = toOpenApiPath(value.path);
|
|
402
|
+
const operationParameters = getParameters(options);
|
|
403
|
+
const parameters = [...operationParameters, ...getPathParameters(value.path, operationParameters)];
|
|
379
404
|
const methods = Array.isArray(options.method) ? options.method : [options.method];
|
|
380
405
|
for (const method of methods.filter((m) => m === "GET" || m === "DELETE")) paths[path] = {
|
|
381
406
|
...paths[path],
|
|
382
407
|
[method.toLowerCase()]: {
|
|
383
408
|
tags: options.metadata?.openapi?.tags || [plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)],
|
|
384
409
|
description: options.metadata?.openapi?.description,
|
|
385
|
-
operationId:
|
|
410
|
+
operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
|
|
386
411
|
security: [{ bearerAuth: [] }],
|
|
387
|
-
parameters:
|
|
388
|
-
responses: getResponse(options.metadata?.openapi?.responses)
|
|
412
|
+
parameters: cloneOpenAPIValue(parameters),
|
|
413
|
+
responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
|
|
389
414
|
}
|
|
390
415
|
};
|
|
391
416
|
for (const method of methods.filter((m) => m === "POST" || m === "PATCH" || m === "PUT")) paths[path] = {
|
|
@@ -393,11 +418,11 @@ async function generator(ctx, options) {
|
|
|
393
418
|
[method.toLowerCase()]: {
|
|
394
419
|
tags: options.metadata?.openapi?.tags || [plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)],
|
|
395
420
|
description: options.metadata?.openapi?.description,
|
|
396
|
-
operationId:
|
|
421
|
+
operationId: getOperationId(options.metadata?.openapi?.operationId, method, usedOperationIds),
|
|
397
422
|
security: [{ bearerAuth: [] }],
|
|
398
|
-
parameters:
|
|
399
|
-
requestBody: getRequestBody(options),
|
|
400
|
-
responses: getResponse(options.metadata?.openapi?.responses)
|
|
423
|
+
parameters: cloneOpenAPIValue(parameters),
|
|
424
|
+
requestBody: cloneOpenAPIValue(getRequestBody(options)),
|
|
425
|
+
responses: cloneOpenAPIValue(getResponse(options.metadata?.openapi?.responses))
|
|
401
426
|
}
|
|
402
427
|
};
|
|
403
428
|
});
|
|
@@ -678,10 +678,11 @@ const getOrgAdapter = (context, options) => {
|
|
|
678
678
|
field: "status",
|
|
679
679
|
value: data.fromStatus
|
|
680
680
|
});
|
|
681
|
-
return await adapter.
|
|
681
|
+
return await adapter.incrementOne({
|
|
682
682
|
model: "invitation",
|
|
683
683
|
where,
|
|
684
|
-
|
|
684
|
+
increment: {},
|
|
685
|
+
set: { status: data.status }
|
|
685
686
|
});
|
|
686
687
|
}
|
|
687
688
|
};
|
|
@@ -564,7 +564,7 @@ const updateOrgRole = (options) => {
|
|
|
564
564
|
...updateData,
|
|
565
565
|
...updateData.permission ? { permission: JSON.stringify(updateData.permission) } : {}
|
|
566
566
|
};
|
|
567
|
-
await ctx.context.adapter.
|
|
567
|
+
await ctx.context.adapter.updateMany({
|
|
568
568
|
model: "organizationRole",
|
|
569
569
|
where: [{
|
|
570
570
|
field: "organizationId",
|
|
@@ -177,16 +177,17 @@ const backupCode2fa = (opts) => {
|
|
|
177
177
|
}, ctx.context.secretConfig, opts);
|
|
178
178
|
if (!validate.status || !validate.updated) throw APIError.from("UNAUTHORIZED", TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE);
|
|
179
179
|
const updatedBackupCodes = await encodeBackupCodes(validate.updated, ctx.context.secretConfig, opts);
|
|
180
|
-
if (!await ctx.context.adapter.
|
|
180
|
+
if (!await ctx.context.adapter.incrementOne({
|
|
181
181
|
model: twoFactorTable,
|
|
182
|
-
update: { backupCodes: updatedBackupCodes },
|
|
183
182
|
where: [{
|
|
184
183
|
field: "id",
|
|
185
184
|
value: twoFactor.id
|
|
186
185
|
}, {
|
|
187
186
|
field: "backupCodes",
|
|
188
187
|
value: twoFactor.backupCodes
|
|
189
|
-
}]
|
|
188
|
+
}],
|
|
189
|
+
increment: {},
|
|
190
|
+
set: { backupCodes: updatedBackupCodes }
|
|
190
191
|
})) throw APIError.fromStatus("CONFLICT", { message: "Failed to verify backup code. Please try again." });
|
|
191
192
|
if (!ctx.body.disableSession) return valid(ctx);
|
|
192
193
|
return ctx.json({
|
package/dist/state.mjs
CHANGED
|
@@ -17,6 +17,7 @@ const stateDataSchema = z.looseObject({
|
|
|
17
17
|
}).optional(),
|
|
18
18
|
requestSignUp: z.boolean().optional()
|
|
19
19
|
});
|
|
20
|
+
const INTERNAL_STATE_KEYS = new Set(Object.keys(stateDataSchema.shape));
|
|
20
21
|
var StateError = class extends BetterAuthError {
|
|
21
22
|
code;
|
|
22
23
|
details;
|
|
@@ -130,4 +131,4 @@ async function parseGenericState(c, state, settings) {
|
|
|
130
131
|
return parsedData;
|
|
131
132
|
}
|
|
132
133
|
//#endregion
|
|
133
|
-
export { StateError, generateGenericState, parseGenericState };
|
|
134
|
+
export { INTERNAL_STATE_KEYS, StateError, generateGenericState, parseGenericState };
|