better-auth 1.5.4 → 1.5.6
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/adapters/index.d.mts +25 -1
- package/dist/adapters/index.mjs +9 -1
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/api/index.d.mts +36 -10
- package/dist/api/index.mjs +19 -4
- package/dist/api/index.mjs.map +1 -1
- package/dist/api/middlewares/origin-check.mjs +17 -8
- package/dist/api/middlewares/origin-check.mjs.map +1 -1
- package/dist/api/routes/account.d.mts +1 -1
- package/dist/api/routes/email-verification.d.mts +0 -1
- package/dist/api/routes/password.d.mts +1 -0
- package/dist/api/routes/password.mjs +2 -1
- package/dist/api/routes/password.mjs.map +1 -1
- package/dist/api/routes/session.d.mts +0 -1
- package/dist/api/routes/sign-in.d.mts +16 -2
- package/dist/api/routes/sign-in.mjs +10 -2
- package/dist/api/routes/sign-in.mjs.map +1 -1
- package/dist/api/routes/sign-up.d.mts +0 -1
- package/dist/api/routes/sign-up.mjs +3 -2
- package/dist/api/routes/sign-up.mjs.map +1 -1
- package/dist/api/routes/update-session.d.mts +0 -1
- package/dist/api/routes/update-user.d.mts +0 -1
- package/dist/api/to-auth-endpoints.mjs +49 -12
- package/dist/api/to-auth-endpoints.mjs.map +1 -1
- package/dist/auth/full.d.mts +0 -1
- package/dist/auth/minimal.d.mts +0 -1
- package/dist/client/index.d.mts +3 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/path-to-object.d.mts +9 -2
- package/dist/client/query.mjs +3 -2
- package/dist/client/query.mjs.map +1 -1
- package/dist/client/session-refresh.d.mts +11 -3
- package/dist/client/session-refresh.mjs +13 -8
- package/dist/client/session-refresh.mjs.map +1 -1
- package/dist/client/types.d.mts +0 -1
- package/dist/context/create-context.mjs +4 -1
- package/dist/context/create-context.mjs.map +1 -1
- package/dist/context/helpers.mjs +10 -4
- package/dist/context/helpers.mjs.map +1 -1
- package/dist/cookies/index.d.mts +0 -1
- package/dist/cookies/session-store.d.mts +0 -2
- package/dist/db/get-migration.mjs +3 -2
- package/dist/db/get-migration.mjs.map +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/internal-adapter.d.mts +2 -1
- package/dist/db/internal-adapter.mjs +1 -1
- package/dist/db/internal-adapter.mjs.map +1 -1
- package/dist/db/schema.d.mts +0 -1
- package/dist/db/with-hooks.d.mts +6 -2
- package/dist/db/with-hooks.mjs +72 -31
- package/dist/db/with-hooks.mjs.map +1 -1
- package/dist/index.d.mts +0 -2
- package/dist/integrations/node.d.mts +0 -1
- package/dist/oauth2/link-account.d.mts +0 -1
- package/dist/plugins/admin/access/statement.d.mts +0 -2
- package/dist/plugins/admin/admin.d.mts +0 -1
- package/dist/plugins/admin/client.d.mts +0 -2
- package/dist/plugins/admin/types.d.mts +0 -2
- package/dist/plugins/anonymous/types.d.mts +0 -1
- package/dist/plugins/email-otp/index.mjs +2 -1
- package/dist/plugins/email-otp/index.mjs.map +1 -1
- package/dist/plugins/email-otp/otp-token.mjs +31 -2
- package/dist/plugins/email-otp/otp-token.mjs.map +1 -1
- package/dist/plugins/email-otp/routes.mjs +60 -59
- package/dist/plugins/email-otp/routes.mjs.map +1 -1
- package/dist/plugins/email-otp/types.d.mts +12 -0
- package/dist/plugins/email-otp/utils.mjs +4 -1
- package/dist/plugins/email-otp/utils.mjs.map +1 -1
- package/dist/plugins/generic-oauth/client.d.mts +0 -1
- package/dist/plugins/generic-oauth/index.d.mts +0 -1
- package/dist/plugins/index.d.mts +0 -3
- package/dist/plugins/jwt/types.d.mts +0 -1
- package/dist/plugins/magic-link/index.d.mts +2 -0
- package/dist/plugins/magic-link/index.mjs +5 -3
- package/dist/plugins/magic-link/index.mjs.map +1 -1
- package/dist/plugins/mcp/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/authorize.mjs +13 -4
- package/dist/plugins/oidc-provider/authorize.mjs.map +1 -1
- package/dist/plugins/oidc-provider/error.mjs +12 -2
- package/dist/plugins/oidc-provider/error.mjs.map +1 -1
- package/dist/plugins/oidc-provider/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/types.d.mts +0 -1
- package/dist/plugins/one-time-token/index.d.mts +0 -1
- package/dist/plugins/organization/access/statement.d.mts +0 -2
- package/dist/plugins/organization/adapter.d.mts +0 -2
- package/dist/plugins/organization/adapter.mjs +2 -2
- package/dist/plugins/organization/adapter.mjs.map +1 -1
- package/dist/plugins/organization/client.d.mts +0 -5
- package/dist/plugins/organization/organization.d.mts +0 -2
- package/dist/plugins/organization/permission.d.mts +0 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +0 -2
- package/dist/plugins/organization/routes/crud-invites.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
- package/dist/plugins/organization/routes/crud-invites.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-members.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-members.mjs +1 -1
- package/dist/plugins/organization/routes/crud-members.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-org.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-team.d.mts +2 -3
- package/dist/plugins/organization/routes/crud-team.mjs +18 -14
- package/dist/plugins/organization/routes/crud-team.mjs.map +1 -1
- package/dist/plugins/organization/schema.d.mts +0 -1
- package/dist/plugins/organization/types.d.mts +0 -2
- package/dist/plugins/phone-number/types.d.mts +0 -1
- package/dist/plugins/siwe/index.d.mts +0 -1
- package/dist/plugins/test-utils/types.d.mts +0 -2
- package/dist/plugins/two-factor/client.d.mts +7 -0
- package/dist/plugins/two-factor/client.mjs +5 -1
- package/dist/plugins/two-factor/client.mjs.map +1 -1
- package/dist/plugins/two-factor/index.mjs +7 -1
- package/dist/plugins/two-factor/index.mjs.map +1 -1
- package/dist/plugins/two-factor/otp/index.d.mts +2 -2
- package/dist/plugins/two-factor/otp/index.mjs.map +1 -1
- package/dist/plugins/two-factor/types.d.mts +7 -1
- package/dist/test-utils/test-instance.d.mts +108 -21
- package/dist/types/index.d.mts +0 -1
- package/package.json +13 -10
package/dist/db/with-hooks.mjs
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { getCurrentAdapter, getCurrentAuthContext, queueAfterTransactionHook } from "@better-auth/core/context";
|
|
2
|
+
import { ATTR_CONTEXT, ATTR_DB_COLLECTION_NAME, ATTR_HOOK_TYPE, withSpan } from "@better-auth/core/instrumentation";
|
|
2
3
|
|
|
3
4
|
//#region src/db/with-hooks.ts
|
|
4
5
|
function getWithHooks(adapter, ctx) {
|
|
5
|
-
const
|
|
6
|
+
const hooksEntries = ctx.hooks;
|
|
6
7
|
async function createWithHooks(data, model, customCreateFn) {
|
|
7
8
|
const context = await getCurrentAuthContext().catch(() => null);
|
|
8
9
|
let actualData = data;
|
|
9
|
-
for (const
|
|
10
|
-
const toRun =
|
|
10
|
+
for (const { source, hooks } of hooksEntries) {
|
|
11
|
+
const toRun = hooks[model]?.create?.before;
|
|
11
12
|
if (toRun) {
|
|
12
|
-
const result = await
|
|
13
|
+
const result = await withSpan(`db create.before ${model}`, {
|
|
14
|
+
[ATTR_HOOK_TYPE]: "create.before",
|
|
15
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
16
|
+
[ATTR_CONTEXT]: source
|
|
17
|
+
}, () => toRun(actualData, context));
|
|
13
18
|
if (result === false) return null;
|
|
14
19
|
if (typeof result === "object" && "data" in result) actualData = {
|
|
15
20
|
...actualData,
|
|
@@ -24,10 +29,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
24
29
|
forceAllowId: true
|
|
25
30
|
});
|
|
26
31
|
if (customCreateFn?.fn) created = await customCreateFn.fn(created ?? actualData);
|
|
27
|
-
for (const
|
|
28
|
-
const toRun =
|
|
32
|
+
for (const { source, hooks } of hooksEntries) {
|
|
33
|
+
const toRun = hooks[model]?.create?.after;
|
|
29
34
|
if (toRun) await queueAfterTransactionHook(async () => {
|
|
30
|
-
await
|
|
35
|
+
await withSpan(`db create.after ${model}`, {
|
|
36
|
+
[ATTR_HOOK_TYPE]: "create.after",
|
|
37
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
38
|
+
[ATTR_CONTEXT]: source
|
|
39
|
+
}, () => toRun(created, context));
|
|
31
40
|
});
|
|
32
41
|
}
|
|
33
42
|
return created;
|
|
@@ -35,10 +44,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
35
44
|
async function updateWithHooks(data, where, model, customUpdateFn) {
|
|
36
45
|
const context = await getCurrentAuthContext().catch(() => null);
|
|
37
46
|
let actualData = data;
|
|
38
|
-
for (const
|
|
39
|
-
const toRun =
|
|
47
|
+
for (const { source, hooks } of hooksEntries) {
|
|
48
|
+
const toRun = hooks[model]?.update?.before;
|
|
40
49
|
if (toRun) {
|
|
41
|
-
const result = await
|
|
50
|
+
const result = await withSpan(`db update.before ${model}`, {
|
|
51
|
+
[ATTR_HOOK_TYPE]: "update.before",
|
|
52
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
53
|
+
[ATTR_CONTEXT]: source
|
|
54
|
+
}, () => toRun(data, context));
|
|
42
55
|
if (result === false) return null;
|
|
43
56
|
if (typeof result === "object" && "data" in result) actualData = {
|
|
44
57
|
...actualData,
|
|
@@ -52,10 +65,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
52
65
|
update: actualData,
|
|
53
66
|
where
|
|
54
67
|
}) : customUpdated;
|
|
55
|
-
for (const
|
|
56
|
-
const toRun =
|
|
68
|
+
for (const { source, hooks } of hooksEntries) {
|
|
69
|
+
const toRun = hooks[model]?.update?.after;
|
|
57
70
|
if (toRun) await queueAfterTransactionHook(async () => {
|
|
58
|
-
await
|
|
71
|
+
await withSpan(`db update.after ${model}`, {
|
|
72
|
+
[ATTR_HOOK_TYPE]: "update.after",
|
|
73
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
74
|
+
[ATTR_CONTEXT]: source
|
|
75
|
+
}, () => toRun(updated, context));
|
|
59
76
|
});
|
|
60
77
|
}
|
|
61
78
|
return updated;
|
|
@@ -63,10 +80,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
63
80
|
async function updateManyWithHooks(data, where, model, customUpdateFn) {
|
|
64
81
|
const context = await getCurrentAuthContext().catch(() => null);
|
|
65
82
|
let actualData = data;
|
|
66
|
-
for (const
|
|
67
|
-
const toRun =
|
|
83
|
+
for (const { source, hooks } of hooksEntries) {
|
|
84
|
+
const toRun = hooks[model]?.update?.before;
|
|
68
85
|
if (toRun) {
|
|
69
|
-
const result = await
|
|
86
|
+
const result = await withSpan(`db updateMany.before ${model}`, {
|
|
87
|
+
[ATTR_HOOK_TYPE]: "updateMany.before",
|
|
88
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
89
|
+
[ATTR_CONTEXT]: source
|
|
90
|
+
}, () => toRun(data, context));
|
|
70
91
|
if (result === false) return null;
|
|
71
92
|
if (typeof result === "object" && "data" in result) actualData = {
|
|
72
93
|
...actualData,
|
|
@@ -80,10 +101,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
80
101
|
update: actualData,
|
|
81
102
|
where
|
|
82
103
|
}) : customUpdated;
|
|
83
|
-
for (const
|
|
84
|
-
const toRun =
|
|
104
|
+
for (const { source, hooks } of hooksEntries) {
|
|
105
|
+
const toRun = hooks[model]?.update?.after;
|
|
85
106
|
if (toRun) await queueAfterTransactionHook(async () => {
|
|
86
|
-
await
|
|
107
|
+
await withSpan(`db updateMany.after ${model}`, {
|
|
108
|
+
[ATTR_HOOK_TYPE]: "updateMany.after",
|
|
109
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
110
|
+
[ATTR_CONTEXT]: source
|
|
111
|
+
}, () => toRun(updated, context));
|
|
87
112
|
});
|
|
88
113
|
}
|
|
89
114
|
return updated;
|
|
@@ -98,10 +123,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
98
123
|
limit: 1
|
|
99
124
|
}))[0] || null;
|
|
100
125
|
} catch {}
|
|
101
|
-
if (entityToDelete) for (const
|
|
102
|
-
const toRun =
|
|
126
|
+
if (entityToDelete) for (const { source, hooks } of hooksEntries) {
|
|
127
|
+
const toRun = hooks[model]?.delete?.before;
|
|
103
128
|
if (toRun) {
|
|
104
|
-
if (await
|
|
129
|
+
if (await withSpan(`db delete.before ${model}`, {
|
|
130
|
+
[ATTR_HOOK_TYPE]: "delete.before",
|
|
131
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
132
|
+
[ATTR_CONTEXT]: source
|
|
133
|
+
}, () => toRun(entityToDelete, context)) === false) return null;
|
|
105
134
|
}
|
|
106
135
|
}
|
|
107
136
|
const customDeleted = customDeleteFn ? await customDeleteFn.fn(where) : null;
|
|
@@ -109,10 +138,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
109
138
|
model,
|
|
110
139
|
where
|
|
111
140
|
}) : customDeleted;
|
|
112
|
-
if (entityToDelete) for (const
|
|
113
|
-
const toRun =
|
|
141
|
+
if (entityToDelete) for (const { source, hooks } of hooksEntries) {
|
|
142
|
+
const toRun = hooks[model]?.delete?.after;
|
|
114
143
|
if (toRun) await queueAfterTransactionHook(async () => {
|
|
115
|
-
await
|
|
144
|
+
await withSpan(`db delete.after ${model}`, {
|
|
145
|
+
[ATTR_HOOK_TYPE]: "delete.after",
|
|
146
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
147
|
+
[ATTR_CONTEXT]: source
|
|
148
|
+
}, () => toRun(entityToDelete, context));
|
|
116
149
|
});
|
|
117
150
|
}
|
|
118
151
|
return deleted;
|
|
@@ -126,10 +159,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
126
159
|
where
|
|
127
160
|
});
|
|
128
161
|
} catch {}
|
|
129
|
-
for (const entity of entitiesToDelete) for (const
|
|
130
|
-
const toRun =
|
|
162
|
+
for (const entity of entitiesToDelete) for (const { source, hooks } of hooksEntries) {
|
|
163
|
+
const toRun = hooks[model]?.delete?.before;
|
|
131
164
|
if (toRun) {
|
|
132
|
-
if (await
|
|
165
|
+
if (await withSpan(`db delete.before ${model}`, {
|
|
166
|
+
[ATTR_HOOK_TYPE]: "delete.before",
|
|
167
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
168
|
+
[ATTR_CONTEXT]: source
|
|
169
|
+
}, () => toRun(entity, context)) === false) return null;
|
|
133
170
|
}
|
|
134
171
|
}
|
|
135
172
|
const customDeleted = customDeleteFn ? await customDeleteFn.fn(where) : null;
|
|
@@ -137,10 +174,14 @@ function getWithHooks(adapter, ctx) {
|
|
|
137
174
|
model,
|
|
138
175
|
where
|
|
139
176
|
}) : customDeleted;
|
|
140
|
-
for (const entity of entitiesToDelete) for (const
|
|
141
|
-
const toRun =
|
|
177
|
+
for (const entity of entitiesToDelete) for (const { source, hooks } of hooksEntries) {
|
|
178
|
+
const toRun = hooks[model]?.delete?.after;
|
|
142
179
|
if (toRun) await queueAfterTransactionHook(async () => {
|
|
143
|
-
await
|
|
180
|
+
await withSpan(`db delete.after ${model}`, {
|
|
181
|
+
[ATTR_HOOK_TYPE]: "delete.after",
|
|
182
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
183
|
+
[ATTR_CONTEXT]: source
|
|
184
|
+
}, () => toRun(entity, context));
|
|
144
185
|
});
|
|
145
186
|
}
|
|
146
187
|
return deleted;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-hooks.mjs","names":[],"sources":["../../src/db/with-hooks.ts"],"sourcesContent":["import type { BetterAuthOptions } from \"@better-auth/core\";\nimport {\n\tgetCurrentAdapter,\n\tgetCurrentAuthContext,\n\tqueueAfterTransactionHook,\n} from \"@better-auth/core/context\";\nimport type { BaseModelNames } from \"@better-auth/core/db\";\nimport type { DBAdapter, Where } from \"@better-auth/core/db/adapter\";\n\nexport function getWithHooks(\n\tadapter: DBAdapter<BetterAuthOptions>,\n\tctx: {\n\t\toptions: BetterAuthOptions;\n\t\thooks: Exclude<BetterAuthOptions[\"databaseHooks\"], undefined>[];\n\t},\n) {\n\tconst hooks = ctx.hooks;\n\tasync function createWithHooks<T extends Record<string, any>>(\n\t\tdata: T,\n\t\tmodel: BaseModelNames,\n\t\tcustomCreateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.create?.before;\n\t\t\tif (toRun) {\n\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\tconst result = await toRun(actualData as any, context);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet created: any = null;\n\t\tif (!customCreateFn || customCreateFn.executeMainFn) {\n\t\t\tcreated = await (await getCurrentAdapter(adapter)).create<T>({\n\t\t\t\tmodel,\n\t\t\t\tdata: actualData as any,\n\t\t\t\tforceAllowId: true,\n\t\t\t});\n\t\t}\n\t\tif (customCreateFn?.fn) {\n\t\t\tcreated = await customCreateFn.fn(created ?? actualData);\n\t\t}\n\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.create?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\tawait toRun(created as any, context);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn created;\n\t}\n\n\tasync function updateWithHooks<T extends Record<string, any>>(\n\t\tdata: any,\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomUpdateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.update?.before;\n\t\t\tif (toRun) {\n\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\tconst result = await toRun(data as any, context);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customUpdated = customUpdateFn\n\t\t\t? await customUpdateFn.fn(actualData)\n\t\t\t: null;\n\n\t\tconst updated =\n\t\t\t!customUpdateFn || customUpdateFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).update<T>({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tupdate: actualData,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customUpdated;\n\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.update?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\tawait toRun(updated as any, context);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn updated;\n\t}\n\n\tasync function updateManyWithHooks<_T extends Record<string, any>>(\n\t\tdata: any,\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomUpdateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.update?.before;\n\t\t\tif (toRun) {\n\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\tconst result = await toRun(data as any, context);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customUpdated = customUpdateFn\n\t\t\t? await customUpdateFn.fn(actualData)\n\t\t\t: null;\n\n\t\tconst updated =\n\t\t\t!customUpdateFn || customUpdateFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).updateMany({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tupdate: actualData,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customUpdated;\n\n\t\tfor (const hook of hooks || []) {\n\t\t\tconst toRun = hook[model]?.update?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\tawait toRun(updated as any, context);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\tasync function deleteWithHooks<T extends Record<string, any>>(\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomDeleteFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (where: Where[]) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet entityToDelete: T | null = null;\n\n\t\ttry {\n\t\t\tconst entities = await (await getCurrentAdapter(adapter)).findMany<T>({\n\t\t\t\tmodel,\n\t\t\t\twhere,\n\t\t\t\tlimit: 1,\n\t\t\t});\n\t\t\tentityToDelete = entities[0] || null;\n\t\t} catch {\n\t\t\t// If we can't find the entity, we'll still proceed with deletion\n\t\t}\n\n\t\tif (entityToDelete) {\n\t\t\tfor (const hook of hooks || []) {\n\t\t\t\tconst toRun = hook[model]?.delete?.before;\n\t\t\t\tif (toRun) {\n\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\tconst result = await toRun(entityToDelete as any, context);\n\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customDeleted = customDeleteFn\n\t\t\t? await customDeleteFn.fn(where)\n\t\t\t: null;\n\n\t\tconst shouldRunAdapterDelete =\n\t\t\t!customDeleteFn || customDeleteFn.executeMainFn;\n\t\tconst deleted =\n\t\t\tshouldRunAdapterDelete && entityToDelete\n\t\t\t\t? await (await getCurrentAdapter(adapter)).delete({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customDeleted;\n\n\t\tif (entityToDelete) {\n\t\t\tfor (const hook of hooks || []) {\n\t\t\t\tconst toRun = hook[model]?.delete?.after;\n\t\t\t\tif (toRun) {\n\t\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\tawait toRun(entityToDelete as any, context);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn deleted;\n\t}\n\n\tasync function deleteManyWithHooks<T extends Record<string, any>>(\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomDeleteFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (where: Where[]) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet entitiesToDelete: T[] = [];\n\n\t\ttry {\n\t\t\tentitiesToDelete = await (await getCurrentAdapter(adapter)).findMany<T>({\n\t\t\t\tmodel,\n\t\t\t\twhere,\n\t\t\t});\n\t\t} catch {\n\t\t\t// If we can't find the entities, we'll still proceed with deletion\n\t\t}\n\n\t\tfor (const entity of entitiesToDelete) {\n\t\t\tfor (const hook of hooks || []) {\n\t\t\t\tconst toRun = hook[model]?.delete?.before;\n\t\t\t\tif (toRun) {\n\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\tconst result = await toRun(entity as any, context);\n\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customDeleted = customDeleteFn\n\t\t\t? await customDeleteFn.fn(where)\n\t\t\t: null;\n\n\t\tconst deleted =\n\t\t\t!customDeleteFn || customDeleteFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).deleteMany({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customDeleted;\n\n\t\tfor (const entity of entitiesToDelete) {\n\t\t\tfor (const hook of hooks || []) {\n\t\t\t\tconst toRun = hook[model]?.delete?.after;\n\t\t\t\tif (toRun) {\n\t\t\t\t\t// Queue after hooks to run post-transaction\n\t\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\tawait toRun(entity as any, context);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn deleted;\n\t}\n\n\treturn {\n\t\tcreateWithHooks,\n\t\tupdateWithHooks,\n\t\tupdateManyWithHooks,\n\t\tdeleteWithHooks,\n\t\tdeleteManyWithHooks,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"with-hooks.mjs","names":[],"sources":["../../src/db/with-hooks.ts"],"sourcesContent":["import type { BetterAuthOptions } from \"@better-auth/core\";\nimport {\n\tgetCurrentAdapter,\n\tgetCurrentAuthContext,\n\tqueueAfterTransactionHook,\n} from \"@better-auth/core/context\";\nimport type { BaseModelNames } from \"@better-auth/core/db\";\nimport type { DBAdapter, Where } from \"@better-auth/core/db/adapter\";\nimport {\n\tATTR_CONTEXT,\n\tATTR_DB_COLLECTION_NAME,\n\tATTR_HOOK_TYPE,\n\twithSpan,\n} from \"@better-auth/core/instrumentation\";\n\nexport type DatabaseHooksEntry = {\n\tsource: string;\n\thooks: Exclude<BetterAuthOptions[\"databaseHooks\"], undefined>;\n};\n\nexport function getWithHooks(\n\tadapter: DBAdapter<BetterAuthOptions>,\n\tctx: {\n\t\toptions: BetterAuthOptions;\n\t\thooks: DatabaseHooksEntry[];\n\t},\n) {\n\tconst hooksEntries = ctx.hooks;\n\tasync function createWithHooks<T extends Record<string, any>>(\n\t\tdata: T,\n\t\tmodel: BaseModelNames,\n\t\tcustomCreateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.create?.before;\n\t\t\tif (toRun) {\n\t\t\t\tconst result = await withSpan(\n\t\t\t\t\t`db create.before ${model}`,\n\t\t\t\t\t{\n\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"create.before\",\n\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t},\n\t\t\t\t\t() =>\n\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\ttoRun(actualData as any, context),\n\t\t\t\t);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet created: any = null;\n\t\tif (!customCreateFn || customCreateFn.executeMainFn) {\n\t\t\tcreated = await (await getCurrentAdapter(adapter)).create<T>({\n\t\t\t\tmodel,\n\t\t\t\tdata: actualData as any,\n\t\t\t\tforceAllowId: true,\n\t\t\t});\n\t\t}\n\t\tif (customCreateFn?.fn) {\n\t\t\tcreated = await customCreateFn.fn(created ?? actualData);\n\t\t}\n\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.create?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\tawait withSpan(\n\t\t\t\t\t\t`db create.after ${model}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"create.after\",\n\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\ttoRun(created as any, context),\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn created;\n\t}\n\n\tasync function updateWithHooks<T extends Record<string, any>>(\n\t\tdata: any,\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomUpdateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.update?.before;\n\t\t\tif (toRun) {\n\t\t\t\tconst result = await withSpan(\n\t\t\t\t\t`db update.before ${model}`,\n\t\t\t\t\t{\n\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"update.before\",\n\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t},\n\t\t\t\t\t() =>\n\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\ttoRun(data as any, context),\n\t\t\t\t);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customUpdated = customUpdateFn\n\t\t\t? await customUpdateFn.fn(actualData)\n\t\t\t: null;\n\n\t\tconst updated =\n\t\t\t!customUpdateFn || customUpdateFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).update<T>({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tupdate: actualData,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customUpdated;\n\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.update?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\tawait withSpan(\n\t\t\t\t\t\t`db update.after ${model}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"update.after\",\n\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\ttoRun(updated as any, context),\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn updated;\n\t}\n\n\tasync function updateManyWithHooks<_T extends Record<string, any>>(\n\t\tdata: any,\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomUpdateFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (data: Record<string, any>) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet actualData = data;\n\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.update?.before;\n\t\t\tif (toRun) {\n\t\t\t\tconst result = await withSpan(\n\t\t\t\t\t`db updateMany.before ${model}`,\n\t\t\t\t\t{\n\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"updateMany.before\",\n\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t},\n\t\t\t\t\t() =>\n\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\ttoRun(data as any, context),\n\t\t\t\t);\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst isObject = typeof result === \"object\" && \"data\" in result;\n\t\t\t\tif (isObject) {\n\t\t\t\t\tactualData = {\n\t\t\t\t\t\t...actualData,\n\t\t\t\t\t\t...result.data,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customUpdated = customUpdateFn\n\t\t\t? await customUpdateFn.fn(actualData)\n\t\t\t: null;\n\n\t\tconst updated =\n\t\t\t!customUpdateFn || customUpdateFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).updateMany({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tupdate: actualData,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customUpdated;\n\n\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\tconst toRun = hooks[model]?.update?.after;\n\t\t\tif (toRun) {\n\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\tawait withSpan(\n\t\t\t\t\t\t`db updateMany.after ${model}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"updateMany.after\",\n\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\ttoRun(updated as any, context),\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\tasync function deleteWithHooks<T extends Record<string, any>>(\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomDeleteFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (where: Where[]) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet entityToDelete: T | null = null;\n\n\t\ttry {\n\t\t\tconst entities = await (await getCurrentAdapter(adapter)).findMany<T>({\n\t\t\t\tmodel,\n\t\t\t\twhere,\n\t\t\t\tlimit: 1,\n\t\t\t});\n\t\t\tentityToDelete = entities[0] || null;\n\t\t} catch {\n\t\t\t// If we can't find the entity, we'll still proceed with deletion\n\t\t}\n\n\t\tif (entityToDelete) {\n\t\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\t\tconst toRun = hooks[model]?.delete?.before;\n\t\t\t\tif (toRun) {\n\t\t\t\t\tconst result = await withSpan(\n\t\t\t\t\t\t`db delete.before ${model}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"delete.before\",\n\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\ttoRun(entityToDelete as any, context),\n\t\t\t\t\t);\n\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customDeleted = customDeleteFn\n\t\t\t? await customDeleteFn.fn(where)\n\t\t\t: null;\n\n\t\tconst shouldRunAdapterDelete =\n\t\t\t!customDeleteFn || customDeleteFn.executeMainFn;\n\t\tconst deleted =\n\t\t\tshouldRunAdapterDelete && entityToDelete\n\t\t\t\t? await (await getCurrentAdapter(adapter)).delete({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customDeleted;\n\n\t\tif (entityToDelete) {\n\t\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\t\tconst toRun = hooks[model]?.delete?.after;\n\t\t\t\tif (toRun) {\n\t\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t\tawait withSpan(\n\t\t\t\t\t\t\t`db delete.after ${model}`,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"delete.after\",\n\t\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\t\ttoRun(entityToDelete as any, context),\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn deleted;\n\t}\n\n\tasync function deleteManyWithHooks<T extends Record<string, any>>(\n\t\twhere: Where[],\n\t\tmodel: BaseModelNames,\n\t\tcustomDeleteFn?:\n\t\t\t| {\n\t\t\t\t\tfn: (where: Where[]) => void | Promise<any>;\n\t\t\t\t\texecuteMainFn?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t) {\n\t\tconst context = await getCurrentAuthContext().catch(() => null);\n\t\tlet entitiesToDelete: T[] = [];\n\n\t\ttry {\n\t\t\tentitiesToDelete = await (await getCurrentAdapter(adapter)).findMany<T>({\n\t\t\t\tmodel,\n\t\t\t\twhere,\n\t\t\t});\n\t\t} catch {\n\t\t\t// If we can't find the entities, we'll still proceed with deletion\n\t\t}\n\n\t\tfor (const entity of entitiesToDelete) {\n\t\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\t\tconst toRun = hooks[model]?.delete?.before;\n\t\t\t\tif (toRun) {\n\t\t\t\t\tconst result = await withSpan(\n\t\t\t\t\t\t`db delete.before ${model}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"delete.before\",\n\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\ttoRun(entity as any, context),\n\t\t\t\t\t);\n\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst customDeleted = customDeleteFn\n\t\t\t? await customDeleteFn.fn(where)\n\t\t\t: null;\n\n\t\tconst deleted =\n\t\t\t!customDeleteFn || customDeleteFn.executeMainFn\n\t\t\t\t? await (await getCurrentAdapter(adapter)).deleteMany({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t})\n\t\t\t\t: customDeleted;\n\n\t\tfor (const entity of entitiesToDelete) {\n\t\t\tfor (const { source, hooks } of hooksEntries) {\n\t\t\t\tconst toRun = hooks[model]?.delete?.after;\n\t\t\t\tif (toRun) {\n\t\t\t\t\t// Queue after hooks to run post-transaction\n\t\t\t\t\tawait queueAfterTransactionHook(async () => {\n\t\t\t\t\t\tawait withSpan(\n\t\t\t\t\t\t\t`db delete.after ${model}`,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t[ATTR_HOOK_TYPE]: \"delete.after\",\n\t\t\t\t\t\t\t\t[ATTR_DB_COLLECTION_NAME]: model,\n\t\t\t\t\t\t\t\t[ATTR_CONTEXT]: source,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t// @ts-expect-error context type mismatch\n\t\t\t\t\t\t\t\ttoRun(entity as any, context),\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn deleted;\n\t}\n\n\treturn {\n\t\tcreateWithHooks,\n\t\tupdateWithHooks,\n\t\tupdateManyWithHooks,\n\t\tdeleteWithHooks,\n\t\tdeleteManyWithHooks,\n\t};\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { __exportAll, __reExport } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
1
|
import { HasRequiredKeys, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "./types/helper.mjs";
|
|
3
2
|
import { BetterAuthClientOptions, BetterAuthClientPlugin, ClientAtomListener, ClientStore, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferSessionFromClient, InferUserFromClient, IsSignal, SessionQueryParams } from "./client/types.mjs";
|
|
4
3
|
import { DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, JoinConfig, JoinOption, Where } from "./types/adapter.mjs";
|
|
@@ -12,7 +11,6 @@ import { generateState, parseState } from "./oauth2/state.mjs";
|
|
|
12
11
|
import { StateData, generateGenericState, parseGenericState } from "./state.mjs";
|
|
13
12
|
import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
|
|
14
13
|
import { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
|
|
15
|
-
import "./utils/index.mjs";
|
|
16
14
|
import { APIError } from "./api/index.mjs";
|
|
17
15
|
import { StandardSchemaV1 } from "@better-auth/core";
|
|
18
16
|
import { getCurrentAdapter } from "@better-auth/core/context";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Subset } from "../../access/types.mjs";
|
|
2
2
|
import { AuthorizeResponse } from "../../access/access.mjs";
|
|
3
|
-
import "../../index.mjs";
|
|
4
|
-
|
|
5
3
|
//#region src/plugins/admin/access/statement.d.ts
|
|
6
4
|
declare const defaultStatements: {
|
|
7
5
|
readonly user: readonly ["create", "list", "set-role", "ban", "impersonate", "impersonate-admins", "delete", "set-password", "get", "update"];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AccessControl, ArrayElement, Statements } from "../access/types.mjs";
|
|
2
2
|
import { AdminOptions, InferAdminRolesFromOption, SessionWithImpersonatedBy, UserWithRole } from "./types.mjs";
|
|
3
|
-
import "../index.mjs";
|
|
4
3
|
import * as _better_auth_core0 from "@better-auth/core";
|
|
5
4
|
import * as _better_auth_core_utils_error_codes0 from "@better-auth/core/utils/error-codes";
|
|
6
5
|
import * as better_call0 from "better-call";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AccessControl, ArrayElement, Role, Statements } from "../access/types.mjs";
|
|
2
|
-
import "../access/index.mjs";
|
|
3
2
|
import { AdminOptions, InferAdminRolesFromOption, SessionWithImpersonatedBy, UserWithRole } from "./types.mjs";
|
|
4
3
|
import { admin } from "./admin.mjs";
|
|
5
|
-
import "../index.mjs";
|
|
6
4
|
import { ADMIN_ERROR_CODES } from "./error-codes.mjs";
|
|
7
5
|
import * as _better_auth_core_utils_error_codes0 from "@better-auth/core/utils/error-codes";
|
|
8
6
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Session, User } from "../../types/models.mjs";
|
|
2
2
|
import { InferOptionSchema } from "../../types/plugins.mjs";
|
|
3
|
-
import "../../types/index.mjs";
|
|
4
3
|
import { AccessControl, Role } from "../access/types.mjs";
|
|
5
|
-
import "../access/index.mjs";
|
|
6
4
|
import { AdminSchema } from "./schema.mjs";
|
|
7
5
|
|
|
8
6
|
//#region src/plugins/admin/types.d.ts
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Session, User } from "../../types/models.mjs";
|
|
2
2
|
import { InferOptionSchema } from "../../types/plugins.mjs";
|
|
3
|
-
import "../../types/index.mjs";
|
|
4
3
|
import { schema } from "./schema.mjs";
|
|
5
4
|
import { AuthContext, Awaitable, GenericEndpointContext } from "@better-auth/core";
|
|
6
5
|
import { EndpointContext } from "better-call";
|
|
@@ -3,6 +3,7 @@ import { generateRandomString } from "../../crypto/random.mjs";
|
|
|
3
3
|
import "../../crypto/index.mjs";
|
|
4
4
|
import { EMAIL_OTP_ERROR_CODES } from "./error-codes.mjs";
|
|
5
5
|
import { getEndpointResponse } from "../../utils/plugin-helper.mjs";
|
|
6
|
+
import { toOTPIdentifier } from "./utils.mjs";
|
|
6
7
|
import { storeOTP } from "./otp-token.mjs";
|
|
7
8
|
import { changeEmailEmailOTP, checkVerificationOTP, createVerificationOTP, forgetPasswordEmailOTP, getVerificationOTP, requestEmailChangeEmailOTP, requestPasswordResetEmailOTP, resetPasswordEmailOTP, sendVerificationOTP, signInEmailOTP, verifyEmailOTP } from "./routes.mjs";
|
|
8
9
|
import { createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -60,7 +61,7 @@ const emailOTP = (options) => {
|
|
|
60
61
|
const storedOTP = await storeOTP(ctx, opts, otp);
|
|
61
62
|
await ctx.context.internalAdapter.createVerificationValue({
|
|
62
63
|
value: `${storedOTP}:0`,
|
|
63
|
-
identifier:
|
|
64
|
+
identifier: toOTPIdentifier("email-verification", email),
|
|
64
65
|
expiresAt: getDate(opts.expiresIn, "sec")
|
|
65
66
|
});
|
|
66
67
|
await ctx.context.runInBackgroundOrAwait(options.sendVerificationOTP({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/email-otp/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { generateRandomString } from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport { getEndpointResponse } from \"../../utils/plugin-helper\";\nimport { EMAIL_OTP_ERROR_CODES } from \"./error-codes\";\nimport { storeOTP } from \"./otp-token\";\nimport {\n\tchangeEmailEmailOTP,\n\tcheckVerificationOTP,\n\tcreateVerificationOTP,\n\tforgetPasswordEmailOTP,\n\tgetVerificationOTP,\n\trequestEmailChangeEmailOTP,\n\trequestPasswordResetEmailOTP,\n\tresetPasswordEmailOTP,\n\tsendVerificationOTP,\n\tsignInEmailOTP,\n\tverifyEmailOTP,\n} from \"./routes\";\nimport type { EmailOTPOptions } from \"./types\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"email-otp\": {\n\t\t\tcreator: typeof emailOTP;\n\t\t};\n\t}\n}\n\nexport type { EmailOTPOptions } from \"./types\";\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\nexport const emailOTP = (options: EmailOTPOptions) => {\n\tconst opts = {\n\t\texpiresIn: 5 * 60,\n\t\tgenerateOTP: () => defaultOTPGenerator(options),\n\t\tstoreOTP: \"plain\",\n\t\t...options,\n\t} satisfies EmailOTPOptions;\n\n\tconst sendVerificationOTPAction = sendVerificationOTP(opts);\n\n\treturn {\n\t\tid: \"email-otp\",\n\t\tinit(ctx) {\n\t\t\tif (!opts.overrideDefaultEmailVerification) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\temailVerification: {\n\t\t\t\t\t\tasync sendVerificationEmail(data, request) {\n\t\t\t\t\t\t\tawait ctx.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\tsendVerificationOTPAction({\n\t\t\t\t\t\t\t\t\tcontext: ctx,\n\t\t\t\t\t\t\t\t\trequest: request,\n\t\t\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\t\t\temail: data.user.email,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t//@ts-expect-error\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tendpoints: {\n\t\t\tsendVerificationOTP: sendVerificationOTPAction,\n\t\t\tcreateVerificationOTP: createVerificationOTP(opts),\n\t\t\tgetVerificationOTP: getVerificationOTP(opts),\n\t\t\tcheckVerificationOTP: checkVerificationOTP(opts),\n\t\t\tverifyEmailOTP: verifyEmailOTP(opts),\n\t\t\tsignInEmailOTP: signInEmailOTP(opts),\n\t\t\trequestPasswordResetEmailOTP: requestPasswordResetEmailOTP(opts),\n\t\t\tforgetPasswordEmailOTP: forgetPasswordEmailOTP(opts),\n\t\t\tresetPasswordEmailOTP: resetPasswordEmailOTP(opts),\n\t\t\trequestEmailChangeEmailOTP: requestEmailChangeEmailOTP(opts),\n\t\t\tchangeEmailEmailOTP: changeEmailEmailOTP(opts),\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-up\") &&\n\t\t\t\t\t\t\topts.sendVerificationOnSignUp &&\n\t\t\t\t\t\t\t!opts.overrideDefaultEmailVerification\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst response = await getEndpointResponse<{\n\t\t\t\t\t\t\tuser: { email: string };\n\t\t\t\t\t\t}>(ctx);\n\t\t\t\t\t\tconst email = response?.user.email;\n\t\t\t\t\t\tif (email) {\n\t\t\t\t\t\t\tconst otp =\n\t\t\t\t\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\t\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\t\t\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\t\t\t\tidentifier:
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/email-otp/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthMiddleware } from \"@better-auth/core/api\";\nimport { generateRandomString } from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport { getEndpointResponse } from \"../../utils/plugin-helper\";\nimport { EMAIL_OTP_ERROR_CODES } from \"./error-codes\";\nimport { storeOTP } from \"./otp-token\";\nimport {\n\tchangeEmailEmailOTP,\n\tcheckVerificationOTP,\n\tcreateVerificationOTP,\n\tforgetPasswordEmailOTP,\n\tgetVerificationOTP,\n\trequestEmailChangeEmailOTP,\n\trequestPasswordResetEmailOTP,\n\tresetPasswordEmailOTP,\n\tsendVerificationOTP,\n\tsignInEmailOTP,\n\tverifyEmailOTP,\n} from \"./routes\";\nimport type { EmailOTPOptions } from \"./types\";\nimport { toOTPIdentifier } from \"./utils\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\t\"email-otp\": {\n\t\t\tcreator: typeof emailOTP;\n\t\t};\n\t}\n}\n\nexport type { EmailOTPOptions } from \"./types\";\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\nexport const emailOTP = (options: EmailOTPOptions) => {\n\tconst opts = {\n\t\texpiresIn: 5 * 60,\n\t\tgenerateOTP: () => defaultOTPGenerator(options),\n\t\tstoreOTP: \"plain\",\n\t\t...options,\n\t} satisfies EmailOTPOptions;\n\n\tconst sendVerificationOTPAction = sendVerificationOTP(opts);\n\n\treturn {\n\t\tid: \"email-otp\",\n\t\tinit(ctx) {\n\t\t\tif (!opts.overrideDefaultEmailVerification) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toptions: {\n\t\t\t\t\temailVerification: {\n\t\t\t\t\t\tasync sendVerificationEmail(data, request) {\n\t\t\t\t\t\t\tawait ctx.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\tsendVerificationOTPAction({\n\t\t\t\t\t\t\t\t\tcontext: ctx,\n\t\t\t\t\t\t\t\t\trequest: request,\n\t\t\t\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\t\t\temail: data.user.email,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t//@ts-expect-error\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tendpoints: {\n\t\t\tsendVerificationOTP: sendVerificationOTPAction,\n\t\t\tcreateVerificationOTP: createVerificationOTP(opts),\n\t\t\tgetVerificationOTP: getVerificationOTP(opts),\n\t\t\tcheckVerificationOTP: checkVerificationOTP(opts),\n\t\t\tverifyEmailOTP: verifyEmailOTP(opts),\n\t\t\tsignInEmailOTP: signInEmailOTP(opts),\n\t\t\trequestPasswordResetEmailOTP: requestPasswordResetEmailOTP(opts),\n\t\t\tforgetPasswordEmailOTP: forgetPasswordEmailOTP(opts),\n\t\t\tresetPasswordEmailOTP: resetPasswordEmailOTP(opts),\n\t\t\trequestEmailChangeEmailOTP: requestEmailChangeEmailOTP(opts),\n\t\t\tchangeEmailEmailOTP: changeEmailEmailOTP(opts),\n\t\t},\n\t\thooks: {\n\t\t\tafter: [\n\t\t\t\t{\n\t\t\t\t\tmatcher(context) {\n\t\t\t\t\t\treturn !!(\n\t\t\t\t\t\t\tcontext.path?.startsWith(\"/sign-up\") &&\n\t\t\t\t\t\t\topts.sendVerificationOnSignUp &&\n\t\t\t\t\t\t\t!opts.overrideDefaultEmailVerification\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\thandler: createAuthMiddleware(async (ctx) => {\n\t\t\t\t\t\tconst response = await getEndpointResponse<{\n\t\t\t\t\t\t\tuser: { email: string };\n\t\t\t\t\t\t}>(ctx);\n\t\t\t\t\t\tconst email = response?.user.email;\n\t\t\t\t\t\tif (email) {\n\t\t\t\t\t\t\tconst otp =\n\t\t\t\t\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\t\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\t\t\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\t\t\t\tidentifier: toOTPIdentifier(\"email-verification\", email),\n\t\t\t\t\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\t\t\t\t\toptions.sendVerificationOTP(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t\t\t\t\totp,\n\t\t\t\t\t\t\t\t\t\ttype: \"email-verification\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\n\t\trateLimit: [\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/send-verification-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/check-verification-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/verify-email\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/sign-in/email-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/request-password-reset\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/reset-password\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/forget-password/email-otp\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/request-email-change\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpathMatcher(path) {\n\t\t\t\t\treturn path === \"/email-otp/change-email\";\n\t\t\t\t},\n\t\t\t\twindow: opts.rateLimit?.window || 60,\n\t\t\t\tmax: opts.rateLimit?.max || 3,\n\t\t\t},\n\t\t],\n\t\toptions,\n\t\t$ERROR_CODES: EMAIL_OTP_ERROR_CODES,\n\t} satisfies BetterAuthPlugin;\n};\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { getDate } from "../../utils/date.mjs";
|
|
1
2
|
import { constantTimeEqual } from "../../crypto/buffer.mjs";
|
|
2
3
|
import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
|
|
3
|
-
import { defaultKeyHasher } from "./utils.mjs";
|
|
4
|
+
import { defaultKeyHasher, splitAtLastColon } from "./utils.mjs";
|
|
4
5
|
|
|
5
6
|
//#region src/plugins/email-otp/otp-token.ts
|
|
6
7
|
async function storeOTP(ctx, opts, otp) {
|
|
@@ -23,7 +24,35 @@ async function verifyStoredOTP(ctx, opts, storedOtp, otp) {
|
|
|
23
24
|
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) return constantTimeEqual(await opts.storeOTP.decrypt(storedOtp), otp);
|
|
24
25
|
return constantTimeEqual(otp, storedOtp);
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Retrieves the plain-text OTP from a stored value.
|
|
29
|
+
* Returns `null` if the OTP is hashed and cannot be recovered.
|
|
30
|
+
*/
|
|
31
|
+
async function retrieveOTP(ctx, opts, storedOtp) {
|
|
32
|
+
if (opts.storeOTP === "plain" || opts.storeOTP === void 0) return storedOtp;
|
|
33
|
+
if (opts.storeOTP === "encrypted") return await symmetricDecrypt({
|
|
34
|
+
key: ctx.context.secretConfig,
|
|
35
|
+
data: storedOtp
|
|
36
|
+
});
|
|
37
|
+
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) return await opts.storeOTP.decrypt(storedOtp);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tries to reuse an existing unexpired OTP.
|
|
42
|
+
* Returns the plain-text OTP if reusable, `null` otherwise.
|
|
43
|
+
*/
|
|
44
|
+
async function tryReuseOTP(ctx, opts, identifier) {
|
|
45
|
+
const existing = await ctx.context.internalAdapter.findVerificationValue(identifier);
|
|
46
|
+
if (!existing || existing.expiresAt < /* @__PURE__ */ new Date()) return null;
|
|
47
|
+
const [storedOtpValue, attempts] = splitAtLastColon(existing.value);
|
|
48
|
+
const allowedAttempts = opts.allowedAttempts || 3;
|
|
49
|
+
if (attempts && parseInt(attempts) >= allowedAttempts) return null;
|
|
50
|
+
const plainOtp = await retrieveOTP(ctx, opts, storedOtpValue);
|
|
51
|
+
if (!plainOtp) return null;
|
|
52
|
+
await ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, { expiresAt: getDate(opts.expiresIn, "sec") });
|
|
53
|
+
return plainOtp;
|
|
54
|
+
}
|
|
26
55
|
|
|
27
56
|
//#endregion
|
|
28
|
-
export { storeOTP, verifyStoredOTP };
|
|
57
|
+
export { storeOTP, tryReuseOTP, verifyStoredOTP };
|
|
29
58
|
//# sourceMappingURL=otp-token.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-token.mjs","names":[],"sources":["../../../src/plugins/email-otp/otp-token.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../../crypto\";\nimport type { EmailOTPOptions } from \"./types\";\nimport { defaultKeyHasher } from \"./utils\";\n\nexport async function storeOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\totp: string,\n) {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: otp,\n\t\t});\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\treturn await defaultKeyHasher(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.hash(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.encrypt(otp);\n\t}\n\n\treturn otp;\n}\n\nexport async function verifyStoredOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n\totp: string,\n): Promise<boolean> {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\tconst decryptedOtp = await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\tconst hashedOtp = await defaultKeyHasher(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\tconst hashedOtp = await opts.storeOTP.hash(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\tconst decryptedOtp = await opts.storeOTP.decrypt(storedOtp);\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\n\treturn constantTimeEqual(otp, storedOtp);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"otp-token.mjs","names":[],"sources":["../../../src/plugins/email-otp/otp-token.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"../../crypto\";\nimport { getDate } from \"../../utils/date\";\nimport type { EmailOTPOptions, RequiredEmailOTPOptions } from \"./types\";\nimport { defaultKeyHasher, splitAtLastColon } from \"./utils\";\n\nexport async function storeOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\totp: string,\n) {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: otp,\n\t\t});\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\treturn await defaultKeyHasher(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.hash(otp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.encrypt(otp);\n\t}\n\n\treturn otp;\n}\n\nexport async function verifyStoredOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n\totp: string,\n): Promise<boolean> {\n\tif (opts.storeOTP === \"encrypted\") {\n\t\tconst decryptedOtp = await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\tif (opts.storeOTP === \"hashed\") {\n\t\tconst hashedOtp = await defaultKeyHasher(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n\t\tconst hashedOtp = await opts.storeOTP.hash(otp);\n\t\treturn constantTimeEqual(hashedOtp, storedOtp);\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\tconst decryptedOtp = await opts.storeOTP.decrypt(storedOtp);\n\t\treturn constantTimeEqual(decryptedOtp, otp);\n\t}\n\n\treturn constantTimeEqual(otp, storedOtp);\n}\n\n/**\n * Retrieves the plain-text OTP from a stored value.\n * Returns `null` if the OTP is hashed and cannot be recovered.\n */\nasync function retrieveOTP(\n\tctx: GenericEndpointContext,\n\topts: EmailOTPOptions,\n\tstoredOtp: string,\n): Promise<string | null> {\n\tif (opts.storeOTP === \"plain\" || opts.storeOTP === undefined) {\n\t\treturn storedOtp;\n\t}\n\tif (opts.storeOTP === \"encrypted\") {\n\t\treturn await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedOtp,\n\t\t});\n\t}\n\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\treturn await opts.storeOTP.decrypt(storedOtp);\n\t}\n\t// hashed or custom hash -> cannot recover\n\treturn null;\n}\n\n/**\n * Tries to reuse an existing unexpired OTP.\n * Returns the plain-text OTP if reusable, `null` otherwise.\n */\nexport async function tryReuseOTP(\n\tctx: GenericEndpointContext,\n\topts: RequiredEmailOTPOptions,\n\tidentifier: string,\n): Promise<string | null> {\n\tconst existing =\n\t\tawait ctx.context.internalAdapter.findVerificationValue(identifier);\n\tif (!existing || existing.expiresAt < new Date()) return null;\n\n\tconst [storedOtpValue, attempts] = splitAtLastColon(existing.value);\n\tconst allowedAttempts = opts.allowedAttempts || 3;\n\tif (attempts && parseInt(attempts) >= allowedAttempts) return null;\n\n\tconst plainOtp = await retrieveOTP(ctx, opts, storedOtpValue);\n\tif (!plainOtp) return null;\n\n\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, {\n\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t});\n\n\treturn plainOtp;\n}\n"],"mappings":";;;;;;AAUA,eAAsB,SACrB,KACA,MACA,KACC;AACD,KAAI,KAAK,aAAa,YACrB,QAAO,MAAM,iBAAiB;EAC7B,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC;AAEH,KAAI,KAAK,aAAa,SACrB,QAAO,MAAM,iBAAiB,IAAI;AAEnC,KAAI,OAAO,KAAK,aAAa,YAAY,UAAU,KAAK,SACvD,QAAO,MAAM,KAAK,SAAS,KAAK,IAAI;AAErC,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAC1D,QAAO,MAAM,KAAK,SAAS,QAAQ,IAAI;AAGxC,QAAO;;AAGR,eAAsB,gBACrB,KACA,MACA,WACA,KACmB;AACnB,KAAI,KAAK,aAAa,YAKrB,QAAO,kBAJc,MAAM,iBAAiB;EAC3C,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC,EACqC,IAAI;AAE5C,KAAI,KAAK,aAAa,SAErB,QAAO,kBADW,MAAM,iBAAiB,IAAI,EACT,UAAU;AAE/C,KAAI,OAAO,KAAK,aAAa,YAAY,UAAU,KAAK,SAEvD,QAAO,kBADW,MAAM,KAAK,SAAS,KAAK,IAAI,EACX,UAAU;AAE/C,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAE1D,QAAO,kBADc,MAAM,KAAK,SAAS,QAAQ,UAAU,EACpB,IAAI;AAG5C,QAAO,kBAAkB,KAAK,UAAU;;;;;;AAOzC,eAAe,YACd,KACA,MACA,WACyB;AACzB,KAAI,KAAK,aAAa,WAAW,KAAK,aAAa,OAClD,QAAO;AAER,KAAI,KAAK,aAAa,YACrB,QAAO,MAAM,iBAAiB;EAC7B,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC;AAEH,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAC1D,QAAO,MAAM,KAAK,SAAS,QAAQ,UAAU;AAG9C,QAAO;;;;;;AAOR,eAAsB,YACrB,KACA,MACA,YACyB;CACzB,MAAM,WACL,MAAM,IAAI,QAAQ,gBAAgB,sBAAsB,WAAW;AACpE,KAAI,CAAC,YAAY,SAAS,4BAAY,IAAI,MAAM,CAAE,QAAO;CAEzD,MAAM,CAAC,gBAAgB,YAAY,iBAAiB,SAAS,MAAM;CACnE,MAAM,kBAAkB,KAAK,mBAAmB;AAChD,KAAI,YAAY,SAAS,SAAS,IAAI,gBAAiB,QAAO;CAE9D,MAAM,WAAW,MAAM,YAAY,KAAK,MAAM,eAAe;AAC7D,KAAI,CAAC,SAAU,QAAO;AAEtB,OAAM,IAAI,QAAQ,gBAAgB,+BAA+B,YAAY,EAC5E,WAAW,QAAQ,KAAK,WAAW,MAAM,EACzC,CAAC;AAEF,QAAO"}
|