payload-better-auth 1.0.0

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 (48) hide show
  1. package/README.md +225 -0
  2. package/dist/better-auth/crypto-shared.d.ts +61 -0
  3. package/dist/better-auth/crypto-shared.js +90 -0
  4. package/dist/better-auth/crypto-shared.js.map +1 -0
  5. package/dist/better-auth/databaseHooks.d.ts +5 -0
  6. package/dist/better-auth/databaseHooks.js +24 -0
  7. package/dist/better-auth/databaseHooks.js.map +1 -0
  8. package/dist/better-auth/plugin.d.ts +13 -0
  9. package/dist/better-auth/plugin.js +159 -0
  10. package/dist/better-auth/plugin.js.map +1 -0
  11. package/dist/better-auth/reconcile-queue.d.ts +70 -0
  12. package/dist/better-auth/reconcile-queue.js +311 -0
  13. package/dist/better-auth/reconcile-queue.js.map +1 -0
  14. package/dist/better-auth/sources.d.ts +35 -0
  15. package/dist/better-auth/sources.js +126 -0
  16. package/dist/better-auth/sources.js.map +1 -0
  17. package/dist/collections/Users/index.d.ts +5 -0
  18. package/dist/collections/Users/index.js +167 -0
  19. package/dist/collections/Users/index.js.map +1 -0
  20. package/dist/components/BeforeDashboardClient.d.ts +2 -0
  21. package/dist/components/BeforeDashboardClient.js +36 -0
  22. package/dist/components/BeforeDashboardClient.js.map +1 -0
  23. package/dist/components/BeforeDashboardServer.d.ts +3 -0
  24. package/dist/components/BeforeDashboardServer.js +22 -0
  25. package/dist/components/BeforeDashboardServer.js.map +1 -0
  26. package/dist/components/BeforeDashboardServer.module.css +5 -0
  27. package/dist/components/BetterAuthLoginServer.d.ts +5 -0
  28. package/dist/components/BetterAuthLoginServer.js +82 -0
  29. package/dist/components/BetterAuthLoginServer.js.map +1 -0
  30. package/dist/components/EmailPasswordFormClient.d.ts +5 -0
  31. package/dist/components/EmailPasswordFormClient.js +162 -0
  32. package/dist/components/EmailPasswordFormClient.js.map +1 -0
  33. package/dist/exports/client.d.ts +1 -0
  34. package/dist/exports/client.js +3 -0
  35. package/dist/exports/client.js.map +1 -0
  36. package/dist/exports/rsc.d.ts +1 -0
  37. package/dist/exports/rsc.js +3 -0
  38. package/dist/exports/rsc.js.map +1 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +6 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/payload/plugin.d.ts +7 -0
  43. package/dist/payload/plugin.js +76 -0
  44. package/dist/payload/plugin.js.map +1 -0
  45. package/dist/utils/payload-reconcile.d.ts +6 -0
  46. package/dist/utils/payload-reconcile.js +37 -0
  47. package/dist/utils/payload-reconcile.js.map +1 -0
  48. package/package.json +96 -0
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Payload Plugin Template
2
+
3
+ A template repo to create a [Payload CMS](https://payloadcms.com) plugin.
4
+
5
+ Payload is built with a robust infrastructure intended to support Plugins with ease. This provides a simple, modular, and reusable way for developers to extend the core capabilities of Payload.
6
+
7
+ To build your own Payload plugin, all you need is:
8
+
9
+ - An understanding of the basic Payload concepts
10
+ - And some JavaScript/Typescript experience
11
+
12
+ ## Getting started
13
+
14
+ 1. Install: `pnpm i`
15
+ 2. Generate import map for payload: `cd packages/plugins/dev && pnpx payload generate:importmap`
16
+ 3. Initialize / migrate the better-auth database: `pnpx @better-auth/cli migrate --yes` (Also from `dev` directory)
17
+ 4. Run dev: `pnpm dev`
18
+
19
+ ## Background
20
+
21
+ Here is a short recap on how to integrate plugins with Payload, to learn more visit the [plugin overview page](https://payloadcms.com/docs/plugins/overview).
22
+
23
+ ### How to install a plugin
24
+
25
+ To install any plugin, simply add it to your payload.config() in the Plugin array.
26
+
27
+ ```ts
28
+ import myPlugin from 'my-plugin'
29
+
30
+ export const config = buildConfig({
31
+ plugins: [
32
+ // You can pass options to the plugin
33
+ myPlugin({
34
+ enabled: true,
35
+ }),
36
+ ],
37
+ })
38
+ ```
39
+
40
+ ### Initialization
41
+
42
+ The initialization process goes in the following order:
43
+
44
+ 1. Incoming config is validated
45
+ 2. **Plugins execute**
46
+ 3. Default options are integrated
47
+ 4. Sanitization cleans and validates data
48
+ 5. Final config gets initialized
49
+
50
+ ## Building the Plugin
51
+
52
+ When you build a plugin, you are purely building a feature for your project and then abstracting it outside of the project.
53
+
54
+ ### Template Files
55
+
56
+ In the Payload [plugin template](https://github.com/payloadcms/payload/tree/main/templates/plugin), you will see a common file structure that is used across all plugins:
57
+
58
+ 1. root folder
59
+ 2. /src folder
60
+ 3. /dev folder
61
+
62
+ #### Root
63
+
64
+ In the root folder, you will see various files that relate to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects, so hopefully these will look familiar:
65
+
66
+ - **README**.md\* - This contains instructions on how to use the template. When you are ready, update this to contain instructions on how to use your Plugin.
67
+ - **package**.json\* - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
68
+ - .**eslint**.config.js - Eslint configuration for reporting on problematic patterns.
69
+ - .**gitignore** - List specific untracked files to omit from Git.
70
+ - .**prettierrc**.json - Configuration for Prettier code formatting.
71
+ - **tsconfig**.json - Configures the compiler options for TypeScript
72
+ - .**swcrc** - Configuration for SWC, a fast compiler that transpiles and bundles TypeScript.
73
+ - **vitest**.config.js - Config file for Vitest, defining how tests are run and how modules are resolved
74
+
75
+ **IMPORTANT\***: You will need to modify these files.
76
+
77
+ #### Dev
78
+
79
+ In the dev folder, you’ll find a basic payload project, created with `npx create-payload-app` and the blank template.
80
+
81
+ **IMPORTANT**: Make a copy of the `.env.example` file and rename it to `.env`. Update the `DATABASE_URI` to match the database you are using and your plugin name. Update `PAYLOAD_SECRET` to a unique string.
82
+ **You will not be able to run `pnpm/yarn dev` until you have created this `.env` file.**
83
+
84
+ `myPlugin` has already been added to the `payload.config()` file in this project.
85
+
86
+ ```ts
87
+ plugins: [
88
+ myPlugin({
89
+ collections: {
90
+ posts: true,
91
+ },
92
+ }),
93
+ ]
94
+ ```
95
+
96
+ Later when you rename the plugin or add additional options, **make sure to update it here**.
97
+
98
+ You may wish to add collections or expand the test project depending on the purpose of your plugin. Just make sure to keep this dev environment as simplified as possible - users should be able to install your plugin without additional configuration required.
99
+
100
+ When you’re ready to start development, initiate the project with `pnpm/npm/yarn dev` and pull up [http://localhost:3000](http://localhost:3000) in your browser.
101
+
102
+ #### Src
103
+
104
+ Now that we have our environment setup and we have a dev project ready to - it’s time to build the plugin!
105
+
106
+ **index.ts**
107
+
108
+ The essence of a Payload plugin is simply to extend the payload config - and that is exactly what we are doing in this file.
109
+
110
+ ```ts
111
+ export const myPlugin =
112
+ (pluginOptions: MyPluginConfig) =>
113
+ (config: Config): Config => {
114
+ // do cool stuff with the config here
115
+
116
+ return config
117
+ }
118
+ ```
119
+
120
+ First, we receive the existing payload config along with any plugin options.
121
+
122
+ From here, you can extend the config as you wish.
123
+
124
+ Finally, you return the config and that is it!
125
+
126
+ ##### Spread Syntax
127
+
128
+ Spread syntax (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
129
+
130
+ We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly – else this can cause adverse behavior and conflicts with Payload config and other plugins.
131
+
132
+ Let’s say you want to build a plugin that adds a new collection:
133
+
134
+ ```ts
135
+ config.collections = [
136
+ ...(config.collections || []),
137
+ // Add additional collections here
138
+ ]
139
+ ```
140
+
141
+ First we spread the `config.collections` to ensure that we don’t lose the existing collections, then you can add any additional collections just as you would in a regular payload config.
142
+
143
+ This same logic is applied to other properties like admin, hooks, globals:
144
+
145
+ ```ts
146
+ config.globals = [
147
+ ...(config.globals || []),
148
+ // Add additional globals here
149
+ ]
150
+
151
+ config.hooks = {
152
+ ...(incomingConfig.hooks || {}),
153
+ // Add additional hooks here
154
+ }
155
+ ```
156
+
157
+ Some properties will be slightly different to extend, for instance the onInit property:
158
+
159
+ ```ts
160
+ import { onInitExtension } from './onInitExtension' // example file
161
+
162
+ config.onInit = async (payload) => {
163
+ if (incomingConfig.onInit) await incomingConfig.onInit(payload)
164
+ // Add additional onInit code by defining an onInitExtension function
165
+ onInitExtension(pluginOptions, payload)
166
+ }
167
+ ```
168
+
169
+ If you wish to add to the onInit, you must include the **async/await**. We don’t use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
170
+
171
+ In the template, we have stubbed out some addition `onInit` actions that seeds in a document to the `plugin-collection`, you can use this as a base point to add more actions - and if not needed, feel free to delete it.
172
+
173
+ ##### Types.ts
174
+
175
+ If your plugin has options, you should define and provide types for these options.
176
+
177
+ ```ts
178
+ export type MyPluginConfig = {
179
+ /**
180
+ * List of collections to add a custom field
181
+ */
182
+ collections?: Partial<Record<CollectionSlug, true>>
183
+ /**
184
+ * Disable the plugin
185
+ */
186
+ disabled?: boolean
187
+ }
188
+ ```
189
+
190
+ If possible, include JSDoc comments to describe the options and their types. This allows a developer to see details about the options in their editor.
191
+
192
+ ##### Testing
193
+
194
+ Having a test suite for your plugin is essential to ensure quality and stability. **Vitest** is a fast, modern testing framework that works seamlessly with Vite and supports TypeScript out of the box.
195
+
196
+ Vitest organizes tests into test suites and cases, similar to other testing frameworks. We recommend creating individual tests based on the expected behavior of your plugin from start to finish.
197
+
198
+ Writing tests with Vitest is very straightforward, and you can learn more about how it works in the [Vitest documentation.](https://vitest.dev/)
199
+
200
+ For this template, we stubbed out `int.spec.ts` in the `dev` folder where you can write your tests.
201
+
202
+ ```ts
203
+ describe('Plugin tests', () => {
204
+ // Create tests to ensure expected behavior from the plugin
205
+ it('some condition that must be met', () => {
206
+ // Write your test logic here
207
+ expect(...)
208
+ })
209
+ })
210
+ ```
211
+
212
+ ## Best practices
213
+
214
+ With this tutorial and the plugin template, you should have everything you need to start building your own plugin.
215
+ In addition to the setup, here are other best practices aim we follow:
216
+
217
+ - **Providing an enable / disable option:** For a better user experience, provide a way to disable the plugin without uninstalling it. This is especially important if your plugin adds additional webpack aliases, this will allow you to still let the webpack run to prevent errors.
218
+ - **Include tests in your GitHub CI workflow**: If you’ve configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs)
219
+ - **Publish your finished plugin to NPM**: The best way to share and allow others to use your plugin once it is complete is to publish an NPM package. This process is straightforward and well documented, find out more [creating and publishing a NPM package here.](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/).
220
+ - **Add payload-plugin topic tag**: Apply the tag **payload-plugin **to your GitHub repository. This will boost the visibility of your plugin and ensure it gets listed with [existing payload plugins](https://github.com/topics/payload-plugin).
221
+ - **Use [Semantic Versioning](https://semver.org/) (SemVar)** - With the SemVar system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.
222
+
223
+ # Questions
224
+
225
+ Please contact [Payload](mailto:dev@payloadcms.com) with any questions about using this plugin template.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Signature object containing timestamp, nonce, and MAC
3
+ */
4
+ export interface CryptoSignature {
5
+ /** HMAC-SHA256 signature */
6
+ mac: string;
7
+ /** Unique nonce for this signature */
8
+ nonce: string;
9
+ /** Unix timestamp as string */
10
+ ts: string;
11
+ }
12
+ /**
13
+ * Input parameters for signature verification
14
+ */
15
+ export interface VerifySignatureInput {
16
+ /** The data that was signed */
17
+ body: unknown;
18
+ /** Maximum allowed time skew in seconds (default: 300) */
19
+ maxSkewSec?: number;
20
+ /** Secret key for verification */
21
+ secret: string;
22
+ /** The signature to verify */
23
+ signature: CryptoSignature;
24
+ }
25
+ /**
26
+ * Input parameters for signature creation
27
+ */
28
+ export interface SignCanonicalInput {
29
+ /** The data to sign */
30
+ body: unknown;
31
+ /** Secret key for signing */
32
+ secret: string;
33
+ }
34
+ /**
35
+ * Creates a cryptographic signature for the given data
36
+ * @param body - The data to sign
37
+ * @param secret - Secret key for signing
38
+ * @returns Signature object with timestamp, nonce, and MAC
39
+ */
40
+ export declare function signCanonical(body: unknown, secret: string): CryptoSignature;
41
+ /**
42
+ * Verifies a cryptographic signature
43
+ * @param body - The original data that was signed
44
+ * @param sig - The signature to verify
45
+ * @param secret - Secret key for verification
46
+ * @param maxSkewSec - Maximum allowed time skew in seconds (default: 300)
47
+ * @returns true if signature is valid, false otherwise
48
+ */
49
+ export declare function verifyCanonical(body: unknown, sig: CryptoSignature, secret: string, maxSkewSec?: number): boolean;
50
+ /**
51
+ * Convenience function for verifying signatures with input object
52
+ * @param input - Verification parameters
53
+ * @returns true if signature is valid, false otherwise
54
+ */
55
+ export declare function verifySignature(input: VerifySignatureInput): boolean;
56
+ /**
57
+ * Convenience function for creating signatures with input object
58
+ * @param input - Signing parameters
59
+ * @returns Signature object
60
+ */
61
+ export declare function createSignature(input: SignCanonicalInput): CryptoSignature;
@@ -0,0 +1,90 @@
1
+ // crypto-shared.ts
2
+ import crypto from 'crypto';
3
+ /**
4
+ * Converts an object to a canonical string representation
5
+ * Handles circular references and ensures consistent ordering
6
+ */ function canonicalStringify(obj) {
7
+ const seen = new WeakSet();
8
+ const walk = (v)=>{
9
+ if (v && typeof v === 'object') {
10
+ if (seen.has(v)) {
11
+ throw new Error('Circular reference detected in object');
12
+ }
13
+ seen.add(v);
14
+ if (Array.isArray(v)) {
15
+ const result = `[${v.map(walk).join(',')}]`;
16
+ seen.delete(v);
17
+ return result;
18
+ }
19
+ const keys = Object.keys(v).sort();
20
+ const result = `{${keys.map((k)=>`"${k}":${walk(v[k])}`).join(',')}}`;
21
+ seen.delete(v);
22
+ return result;
23
+ }
24
+ return JSON.stringify(v);
25
+ };
26
+ return walk(obj);
27
+ }
28
+ /**
29
+ * Creates a cryptographic signature for the given data
30
+ * @param body - The data to sign
31
+ * @param secret - Secret key for signing
32
+ * @returns Signature object with timestamp, nonce, and MAC
33
+ */ export function signCanonical(body, secret) {
34
+ if (!secret || typeof secret !== 'string') {
35
+ throw new Error('Secret must be a non-empty string');
36
+ }
37
+ const ts = Math.floor(Date.now() / 1000).toString();
38
+ const nonce = crypto.randomUUID();
39
+ const payload = canonicalStringify(body);
40
+ const mac = crypto.createHmac('sha256', secret).update(`${ts}.${nonce}.${payload}`).digest('hex');
41
+ return {
42
+ mac,
43
+ nonce,
44
+ ts
45
+ };
46
+ }
47
+ /**
48
+ * Verifies a cryptographic signature
49
+ * @param body - The original data that was signed
50
+ * @param sig - The signature to verify
51
+ * @param secret - Secret key for verification
52
+ * @param maxSkewSec - Maximum allowed time skew in seconds (default: 300)
53
+ * @returns true if signature is valid, false otherwise
54
+ */ export function verifyCanonical(body, sig, secret, maxSkewSec = 300) {
55
+ if (!secret || typeof secret !== 'string') {
56
+ return false;
57
+ }
58
+ if (!sig || typeof sig !== 'object' || !sig.ts || !sig.nonce || !sig.mac) {
59
+ return false;
60
+ }
61
+ // Validate timestamp
62
+ const now = Math.floor(Date.now() / 1000);
63
+ const t = Number(sig.ts);
64
+ if (!Number.isFinite(t) || Math.abs(now - t) > maxSkewSec) {
65
+ return false;
66
+ }
67
+ try {
68
+ const payload = canonicalStringify(body);
69
+ const expected = crypto.createHmac('sha256', secret).update(`${sig.ts}.${sig.nonce}.${payload}`).digest('hex');
70
+ return crypto.timingSafeEqual(new Uint8Array(Buffer.from(sig.mac, 'hex')), new Uint8Array(Buffer.from(expected, 'hex')));
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * Convenience function for verifying signatures with input object
77
+ * @param input - Verification parameters
78
+ * @returns true if signature is valid, false otherwise
79
+ */ export function verifySignature(input) {
80
+ return verifyCanonical(input.body, input.signature, input.secret, input.maxSkewSec);
81
+ }
82
+ /**
83
+ * Convenience function for creating signatures with input object
84
+ * @param input - Signing parameters
85
+ * @returns Signature object
86
+ */ export function createSignature(input) {
87
+ return signCanonical(input.body, input.secret);
88
+ }
89
+
90
+ //# sourceMappingURL=crypto-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/better-auth/crypto-shared.ts"],"sourcesContent":["// crypto-shared.ts\nimport crypto from 'crypto'\n\n/**\n * Type for serializable values that can be canonically stringified\n */\ntype SerializableValue =\n | boolean\n | null\n | number\n | SerializableArray\n | SerializableObject\n | string\n | undefined\n\ninterface SerializableObject {\n [key: string]: SerializableValue\n}\n\ninterface SerializableArray extends Array<SerializableValue> {}\n\n/**\n * Signature object containing timestamp, nonce, and MAC\n */\nexport interface CryptoSignature {\n /** HMAC-SHA256 signature */\n mac: string\n /** Unique nonce for this signature */\n nonce: string\n /** Unix timestamp as string */\n ts: string\n}\n\n/**\n * Input parameters for signature verification\n */\nexport interface VerifySignatureInput {\n /** The data that was signed */\n body: unknown\n /** Maximum allowed time skew in seconds (default: 300) */\n maxSkewSec?: number\n /** Secret key for verification */\n secret: string\n /** The signature to verify */\n signature: CryptoSignature\n}\n\n/**\n * Input parameters for signature creation\n */\nexport interface SignCanonicalInput {\n /** The data to sign */\n body: unknown\n /** Secret key for signing */\n secret: string\n}\n\n/**\n * Converts an object to a canonical string representation\n * Handles circular references and ensures consistent ordering\n */\nfunction canonicalStringify(obj: unknown): string {\n const seen = new WeakSet<object>()\n\n const walk = (v: unknown): string => {\n if (v && typeof v === 'object') {\n if (seen.has(v)) {\n throw new Error('Circular reference detected in object')\n }\n seen.add(v)\n\n if (Array.isArray(v)) {\n const result = `[${v.map(walk).join(',')}]`\n seen.delete(v)\n return result\n }\n\n const keys = Object.keys(v).sort()\n const result = `{${keys.map((k) => `\"${k}\":${walk((v as Record<string, unknown>)[k])}`).join(',')}}`\n seen.delete(v)\n return result\n }\n return JSON.stringify(v)\n }\n\n return walk(obj)\n}\n\n/**\n * Creates a cryptographic signature for the given data\n * @param body - The data to sign\n * @param secret - Secret key for signing\n * @returns Signature object with timestamp, nonce, and MAC\n */\nexport function signCanonical(body: unknown, secret: string): CryptoSignature {\n if (!secret || typeof secret !== 'string') {\n throw new Error('Secret must be a non-empty string')\n }\n\n const ts = Math.floor(Date.now() / 1000).toString()\n const nonce = crypto.randomUUID()\n const payload = canonicalStringify(body)\n const mac = crypto.createHmac('sha256', secret).update(`${ts}.${nonce}.${payload}`).digest('hex')\n\n return { mac, nonce, ts }\n}\n\n/**\n * Verifies a cryptographic signature\n * @param body - The original data that was signed\n * @param sig - The signature to verify\n * @param secret - Secret key for verification\n * @param maxSkewSec - Maximum allowed time skew in seconds (default: 300)\n * @returns true if signature is valid, false otherwise\n */\nexport function verifyCanonical(\n body: unknown,\n sig: CryptoSignature,\n secret: string,\n maxSkewSec: number = 300,\n) {\n if (!secret || typeof secret !== 'string') {\n return false\n }\n\n if (!sig || typeof sig !== 'object' || !sig.ts || !sig.nonce || !sig.mac) {\n return false\n }\n\n // Validate timestamp\n const now = Math.floor(Date.now() / 1000)\n const t = Number(sig.ts)\n if (!Number.isFinite(t) || Math.abs(now - t) > maxSkewSec) {\n return false\n }\n\n try {\n const payload = canonicalStringify(body)\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${sig.ts}.${sig.nonce}.${payload}`)\n .digest('hex')\n\n return crypto.timingSafeEqual(\n new Uint8Array(Buffer.from(sig.mac, 'hex')),\n new Uint8Array(Buffer.from(expected, 'hex')),\n )\n } catch {\n return false\n }\n}\n\n/**\n * Convenience function for verifying signatures with input object\n * @param input - Verification parameters\n * @returns true if signature is valid, false otherwise\n */\nexport function verifySignature(input: VerifySignatureInput) {\n return verifyCanonical(input.body, input.signature, input.secret, input.maxSkewSec)\n}\n\n/**\n * Convenience function for creating signatures with input object\n * @param input - Signing parameters\n * @returns Signature object\n */\nexport function createSignature(input: SignCanonicalInput) {\n return signCanonical(input.body, input.secret)\n}\n"],"names":["crypto","canonicalStringify","obj","seen","WeakSet","walk","v","has","Error","add","Array","isArray","result","map","join","delete","keys","Object","sort","k","JSON","stringify","signCanonical","body","secret","ts","Math","floor","Date","now","toString","nonce","randomUUID","payload","mac","createHmac","update","digest","verifyCanonical","sig","maxSkewSec","t","Number","isFinite","abs","expected","timingSafeEqual","Uint8Array","Buffer","from","verifySignature","input","signature","createSignature"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { betterAuth } from 'better-auth';
2
+ import type { SanitizedConfig } from 'payload';
3
+ export declare function createDatabaseHooks({ config, }: {
4
+ config: Promise<SanitizedConfig>;
5
+ }): Parameters<typeof betterAuth>['0']['databaseHooks'];
@@ -0,0 +1,24 @@
1
+ import { createDeleteUserFromPayload, createSyncUserToPayload } from './sources.js';
2
+ export function createDatabaseHooks({ config }) {
3
+ const syncUserToPayload = createSyncUserToPayload(config);
4
+ const deleteUserFromPayload = createDeleteUserFromPayload(config);
5
+ return {
6
+ user: {
7
+ create: {
8
+ // After the BA user exists, sync to Payload. On failure, enqueue in memory.
9
+ after: async (user)=>{
10
+ // push BA-induced ensure to the **front** of the queue
11
+ await syncUserToPayload(user);
12
+ }
13
+ },
14
+ // TODO: possibly offer "update"
15
+ delete: {
16
+ after: async (user)=>{
17
+ await deleteUserFromPayload(user.id);
18
+ }
19
+ }
20
+ }
21
+ };
22
+ }
23
+
24
+ //# sourceMappingURL=databaseHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/better-auth/databaseHooks.ts"],"sourcesContent":["import type { betterAuth } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { createDeleteUserFromPayload, createSyncUserToPayload } from './sources.js'\n\nexport function createDatabaseHooks({\n config,\n}: {\n config: Promise<SanitizedConfig>\n}): Parameters<typeof betterAuth>['0']['databaseHooks'] {\n const syncUserToPayload = createSyncUserToPayload(config)\n const deleteUserFromPayload = createDeleteUserFromPayload(config)\n return {\n user: {\n create: {\n // After the BA user exists, sync to Payload. On failure, enqueue in memory.\n after: async (user) => {\n // push BA-induced ensure to the **front** of the queue\n await syncUserToPayload(user)\n },\n },\n // TODO: possibly offer \"update\"\n delete: {\n after: async (user) => {\n await deleteUserFromPayload(user.id)\n },\n },\n },\n }\n}\n"],"names":["createDeleteUserFromPayload","createSyncUserToPayload","createDatabaseHooks","config","syncUserToPayload","deleteUserFromPayload","user","create","after","delete","id"],"mappings":"AAGA,SAASA,2BAA2B,EAAEC,uBAAuB,QAAQ,eAAc;AAEnF,OAAO,SAASC,oBAAoB,EAClCC,MAAM,EAGP;IACC,MAAMC,oBAAoBH,wBAAwBE;IAClD,MAAME,wBAAwBL,4BAA4BG;IAC1D,OAAO;QACLG,MAAM;YACJC,QAAQ;gBACN,4EAA4E;gBAC5EC,OAAO,OAAOF;oBACZ,uDAAuD;oBACvD,MAAMF,kBAAkBE;gBAC1B;YACF;YACA,gCAAgC;YAChCG,QAAQ;gBACND,OAAO,OAAOF;oBACZ,MAAMD,sBAAsBC,KAAKI,EAAE;gBACrC;YACF;QACF;IACF;AACF"}
@@ -0,0 +1,13 @@
1
+ import type { AuthContext, BetterAuthPlugin } from 'better-auth';
2
+ import type { SanitizedConfig } from 'payload';
3
+ import { type InitOptions } from './reconcile-queue.js';
4
+ type CreateAdminsUser = Parameters<AuthContext['internalAdapter']['createUser']>['0'];
5
+ export declare const payloadBetterAuthPlugin: (opts: {
6
+ createAdmins?: {
7
+ overwrite?: boolean;
8
+ user: CreateAdminsUser;
9
+ }[];
10
+ payloadConfig: Promise<SanitizedConfig>;
11
+ token: string;
12
+ } & InitOptions) => BetterAuthPlugin;
13
+ export {};
@@ -0,0 +1,159 @@
1
+ // src/plugins/reconcile-queue-plugin.ts
2
+ import { APIError, createAuthEndpoint } from 'better-auth/api';
3
+ import { createDatabaseHooks } from './databaseHooks.js';
4
+ import { Queue } from './reconcile-queue.js';
5
+ import { createDeleteUserFromPayload, createListPayloadUsersPage, createSyncUserToPayload } from './sources.js';
6
+ const defaultLog = (msg, extra)=>{
7
+ console.log(`[reconcile] ${msg}`, extra ? JSON.stringify(extra, null, 2) : '');
8
+ };
9
+ export const payloadBetterAuthPlugin = (opts)=>{
10
+ return {
11
+ id: 'reconcile-queue-plugin',
12
+ endpoints: {
13
+ run: createAuthEndpoint('/reconcile/run', {
14
+ method: 'POST'
15
+ }, async ({ context, json, request })=>{
16
+ if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {
17
+ throw new APIError('UNAUTHORIZED', {
18
+ message: 'invalid token'
19
+ });
20
+ }
21
+ await context.payloadSyncPlugin.queue.seedFullReconcile();
22
+ return json({
23
+ ok: true
24
+ });
25
+ }),
26
+ status: createAuthEndpoint('/reconcile/status', {
27
+ method: 'GET'
28
+ }, async ({ context, json, request })=>{
29
+ if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {
30
+ return Promise.reject(new APIError('UNAUTHORIZED', {
31
+ message: 'invalid token'
32
+ }));
33
+ }
34
+ return json(context.payloadSyncPlugin.queue.status());
35
+ }),
36
+ // convenience for tests/admin tools (optional)
37
+ authMethods: createAuthEndpoint('/auth/methods', {
38
+ method: 'GET'
39
+ }, async ({ context, json })=>{
40
+ const authMethods = [];
41
+ // Check if emailAndPassword is enabled, or if present at all (not present defaults to false)
42
+ if (context.options.emailAndPassword?.enabled) {
43
+ authMethods.push('emailAndPassword');
44
+ }
45
+ return await json({
46
+ authMethods
47
+ });
48
+ }),
49
+ deleteNow: createAuthEndpoint('/reconcile/delete', {
50
+ method: 'POST'
51
+ }, async ({ context, json, request })=>{
52
+ if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {
53
+ throw new APIError('UNAUTHORIZED', {
54
+ message: 'invalid token'
55
+ });
56
+ }
57
+ const body = await request?.json().catch(()=>({}));
58
+ const baId = body?.baId;
59
+ if (!baId) {
60
+ throw new APIError('BAD_REQUEST', {
61
+ message: 'missing baId'
62
+ });
63
+ }
64
+ ;
65
+ context.payloadSyncPlugin.queue.enqueueDelete(baId, true, 'user-operation');
66
+ return json({
67
+ ok: true
68
+ });
69
+ }),
70
+ ensureNow: createAuthEndpoint('/reconcile/ensure', {
71
+ method: 'POST'
72
+ }, async ({ context, json, request })=>{
73
+ if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {
74
+ throw new APIError('UNAUTHORIZED', {
75
+ message: 'invalid token'
76
+ });
77
+ }
78
+ const body = await request?.json().catch(()=>({}));
79
+ const user = body?.user;
80
+ if (!user?.id) {
81
+ throw new APIError('BAD_REQUEST', {
82
+ message: 'missing user'
83
+ });
84
+ }
85
+ ;
86
+ context.payloadSyncPlugin.queue.enqueueEnsure(user, true, 'user-operation');
87
+ return json({
88
+ ok: true
89
+ });
90
+ })
91
+ },
92
+ // TODO: the queue must be destroyed on better auth instance destruction, as it utilizes timers.
93
+ async init ({ internalAdapter, password }) {
94
+ if (opts.createAdmins) {
95
+ try {
96
+ await Promise.all(opts.createAdmins.map(async ({ overwrite, user })=>{
97
+ const alreadyExistingUser = await internalAdapter.findUserByEmail(user.email);
98
+ if (alreadyExistingUser) {
99
+ if (overwrite) {
100
+ // clear accounts
101
+ await internalAdapter.deleteAccounts(alreadyExistingUser.user.id);
102
+ const createdUser = await internalAdapter.updateUser(alreadyExistingUser.user.id, {
103
+ ...user,
104
+ role: 'admin'
105
+ });
106
+ // assuming this creates an account?
107
+ await internalAdapter.linkAccount({
108
+ accountId: createdUser.id,
109
+ password: await password.hash(user.password),
110
+ providerId: 'credential',
111
+ userId: createdUser.id
112
+ });
113
+ }
114
+ } else {
115
+ const createdUser = await internalAdapter.createUser({
116
+ ...user,
117
+ role: 'admin'
118
+ });
119
+ await internalAdapter.linkAccount({
120
+ accountId: createdUser.id,
121
+ password: await password.hash(user.password),
122
+ providerId: 'credential',
123
+ userId: createdUser.id
124
+ });
125
+ }
126
+ }));
127
+ } catch (error) {
128
+ defaultLog('Failed to create Admin user', error);
129
+ }
130
+ }
131
+ const queue = new Queue({
132
+ deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),
133
+ internalAdapter,
134
+ listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),
135
+ log: defaultLog,
136
+ syncUserToPayload: createSyncUserToPayload(opts.payloadConfig)
137
+ }, opts);
138
+ return {
139
+ context: {
140
+ payloadSyncPlugin: {
141
+ queue
142
+ }
143
+ },
144
+ options: {
145
+ databaseHooks: createDatabaseHooks({
146
+ config: opts.payloadConfig
147
+ }),
148
+ user: {
149
+ deleteUser: {
150
+ enabled: true
151
+ }
152
+ }
153
+ }
154
+ };
155
+ }
156
+ };
157
+ };
158
+
159
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/better-auth/plugin.ts"],"sourcesContent":["// src/plugins/reconcile-queue-plugin.ts\nimport type { AuthContext, BetterAuthPlugin, DeepPartial } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { APIError, createAuthEndpoint } from 'better-auth/api'\n\nimport { createDatabaseHooks } from './databaseHooks.js'\nimport { type InitOptions, Queue } from './reconcile-queue.js'\nimport {\n type BAUser,\n createDeleteUserFromPayload,\n createListPayloadUsersPage,\n createSyncUserToPayload,\n} from './sources.js'\n\ntype PayloadSyncPluginContext = { payloadSyncPlugin: { queue: Queue } } & AuthContext\n\ntype CreateAdminsUser = Parameters<AuthContext['internalAdapter']['createUser']>['0']\n\nconst defaultLog = (msg: string, extra?: any) => {\n console.log(`[reconcile] ${msg}`, extra ? JSON.stringify(extra, null, 2) : '')\n}\n\nexport const payloadBetterAuthPlugin = (\n opts: {\n createAdmins?: { overwrite?: boolean; user: CreateAdminsUser }[]\n payloadConfig: Promise<SanitizedConfig>\n token: string // simple header token for admin endpoints\n } & InitOptions,\n): BetterAuthPlugin => {\n return {\n id: 'reconcile-queue-plugin',\n endpoints: {\n run: createAuthEndpoint(\n '/reconcile/run',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n await (context as PayloadSyncPluginContext).payloadSyncPlugin.queue.seedFullReconcile()\n return json({ ok: true })\n },\n ),\n status: createAuthEndpoint(\n '/reconcile/status',\n { method: 'GET' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n return Promise.reject(\n new APIError('UNAUTHORIZED', { message: 'invalid token' }) as Error,\n )\n }\n return json((context as PayloadSyncPluginContext).payloadSyncPlugin.queue.status())\n },\n ),\n // convenience for tests/admin tools (optional)\n authMethods: createAuthEndpoint(\n '/auth/methods',\n { method: 'GET' },\n async ({ context, json }) => {\n const authMethods: string[] = []\n\n // Check if emailAndPassword is enabled, or if present at all (not present defaults to false)\n if (context.options.emailAndPassword?.enabled) {\n authMethods.push('emailAndPassword')\n }\n\n return await json({ authMethods })\n },\n ),\n deleteNow: createAuthEndpoint(\n '/reconcile/delete',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { baId?: string } | undefined\n const baId = body?.baId\n if (!baId) {\n throw new APIError('BAD_REQUEST', { message: 'missing baId' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueDelete(\n baId,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n ensureNow: createAuthEndpoint(\n '/reconcile/ensure',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { user?: BAUser } | undefined\n const user = body?.user\n if (!user?.id) {\n throw new APIError('BAD_REQUEST', { message: 'missing user' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueEnsure(\n user,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n },\n // TODO: the queue must be destroyed on better auth instance destruction, as it utilizes timers.\n async init({ internalAdapter, password }) {\n if (opts.createAdmins) {\n try {\n await Promise.all(\n opts.createAdmins.map(async ({ overwrite, user }) => {\n const alreadyExistingUser = await internalAdapter.findUserByEmail(user.email)\n if (alreadyExistingUser) {\n if (overwrite) {\n // clear accounts\n await internalAdapter.deleteAccounts(alreadyExistingUser.user.id)\n const createdUser = await internalAdapter.updateUser(\n alreadyExistingUser.user.id,\n {\n ...user,\n role: 'admin',\n },\n )\n // assuming this creates an account?\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }\n // if the user doesnt exist there can't be an account\n else {\n const createdUser = await internalAdapter.createUser({ ...user, role: 'admin' })\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }),\n )\n } catch (error) {\n defaultLog('Failed to create Admin user', error)\n }\n }\n\n const queue = new Queue(\n {\n deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),\n internalAdapter,\n listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),\n log: defaultLog,\n syncUserToPayload: createSyncUserToPayload(opts.payloadConfig),\n },\n opts,\n )\n return {\n context: { payloadSyncPlugin: { queue } } as DeepPartial<Omit<AuthContext, 'options'>>,\n options: {\n databaseHooks: createDatabaseHooks({ config: opts.payloadConfig }),\n user: { deleteUser: { enabled: true } },\n },\n }\n },\n }\n}\n"],"names":["APIError","createAuthEndpoint","createDatabaseHooks","Queue","createDeleteUserFromPayload","createListPayloadUsersPage","createSyncUserToPayload","defaultLog","msg","extra","console","log","JSON","stringify","payloadBetterAuthPlugin","opts","id","endpoints","run","method","context","json","request","token","headers","get","message","payloadSyncPlugin","queue","seedFullReconcile","ok","status","Promise","reject","authMethods","options","emailAndPassword","enabled","push","deleteNow","body","catch","baId","enqueueDelete","ensureNow","user","enqueueEnsure","init","internalAdapter","password","createAdmins","all","map","overwrite","alreadyExistingUser","findUserByEmail","email","deleteAccounts","createdUser","updateUser","role","linkAccount","accountId","hash","providerId","userId","createUser","error","deleteUserFromPayload","payloadConfig","listPayloadUsersPage","syncUserToPayload","databaseHooks","config","deleteUser"],"mappings":"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"}