@bivola/refresh-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.
package/README.md ADDED
@@ -0,0 +1,218 @@
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
+ ## Background
13
+
14
+ 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).
15
+
16
+ ### How to install a plugin
17
+
18
+ To install any plugin, simply add it to your payload.config() in the Plugin array.
19
+
20
+ ```ts
21
+ import myPlugin from 'my-plugin'
22
+
23
+ export const config = buildConfig({
24
+ plugins: [
25
+ // You can pass options to the plugin
26
+ myPlugin({
27
+ enabled: true,
28
+ }),
29
+ ],
30
+ })
31
+ ```
32
+
33
+ ### Initialization
34
+
35
+ The initialization process goes in the following order:
36
+
37
+ 1. Incoming config is validated
38
+ 2. **Plugins execute**
39
+ 3. Default options are integrated
40
+ 4. Sanitization cleans and validates data
41
+ 5. Final config gets initialized
42
+
43
+ ## Building the Plugin
44
+
45
+ When you build a plugin, you are purely building a feature for your project and then abstracting it outside of the project.
46
+
47
+ ### Template Files
48
+
49
+ 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:
50
+
51
+ 1. root folder
52
+ 2. /src folder
53
+ 3. /dev folder
54
+
55
+ #### Root
56
+
57
+ 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:
58
+
59
+ - **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.
60
+ - **package**.json\* - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
61
+ - .**eslint**.config.js - Eslint configuration for reporting on problematic patterns.
62
+ - .**gitignore** - List specific untracked files to omit from Git.
63
+ - .**prettierrc**.json - Configuration for Prettier code formatting.
64
+ - **tsconfig**.json - Configures the compiler options for TypeScript
65
+ - .**swcrc** - Configuration for SWC, a fast compiler that transpiles and bundles TypeScript.
66
+ - **vitest**.config.js - Config file for Vitest, defining how tests are run and how modules are resolved
67
+
68
+ **IMPORTANT\***: You will need to modify these files.
69
+
70
+ #### Dev
71
+
72
+ In the dev folder, you’ll find a basic payload project, created with `npx create-payload-app` and the blank template.
73
+
74
+ **IMPORTANT**: Make a copy of the `.env.example` file and rename it to `.env`. Update the `DATABASE_URL` to match the database you are using and your plugin name. Update `PAYLOAD_SECRET` to a unique string.
75
+ **You will not be able to run `pnpm/yarn dev` until you have created this `.env` file.**
76
+
77
+ `myPlugin` has already been added to the `payload.config()` file in this project.
78
+
79
+ ```ts
80
+ plugins: [
81
+ myPlugin({
82
+ collections: {
83
+ posts: true,
84
+ },
85
+ }),
86
+ ]
87
+ ```
88
+
89
+ Later when you rename the plugin or add additional options, **make sure to update it here**.
90
+
91
+ 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.
92
+
93
+ 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.
94
+
95
+ #### Src
96
+
97
+ Now that we have our environment setup and we have a dev project ready to - it’s time to build the plugin!
98
+
99
+ **index.ts**
100
+
101
+ The essence of a Payload plugin is simply to extend the payload config - and that is exactly what we are doing in this file.
102
+
103
+ ```ts
104
+ export const myPlugin =
105
+ (pluginOptions: MyPluginConfig) =>
106
+ (config: Config): Config => {
107
+ // do cool stuff with the config here
108
+
109
+ return config
110
+ }
111
+ ```
112
+
113
+ First, we receive the existing payload config along with any plugin options.
114
+
115
+ From here, you can extend the config as you wish.
116
+
117
+ Finally, you return the config and that is it!
118
+
119
+ ##### Spread Syntax
120
+
121
+ 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.
122
+
123
+ 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.
124
+
125
+ Let’s say you want to build a plugin that adds a new collection:
126
+
127
+ ```ts
128
+ config.collections = [
129
+ ...(config.collections || []),
130
+ // Add additional collections here
131
+ ]
132
+ ```
133
+
134
+ 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.
135
+
136
+ This same logic is applied to other properties like admin, hooks, globals:
137
+
138
+ ```ts
139
+ config.globals = [
140
+ ...(config.globals || []),
141
+ // Add additional globals here
142
+ ]
143
+
144
+ config.hooks = {
145
+ ...(incomingConfig.hooks || {}),
146
+ // Add additional hooks here
147
+ }
148
+ ```
149
+
150
+ Some properties will be slightly different to extend, for instance the onInit property:
151
+
152
+ ```ts
153
+ import { onInitExtension } from './onInitExtension' // example file
154
+
155
+ config.onInit = async (payload) => {
156
+ if (incomingConfig.onInit) await incomingConfig.onInit(payload)
157
+ // Add additional onInit code by defining an onInitExtension function
158
+ onInitExtension(pluginOptions, payload)
159
+ }
160
+ ```
161
+
162
+ 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.
163
+
164
+ 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.
165
+
166
+ ##### Types.ts
167
+
168
+ If your plugin has options, you should define and provide types for these options.
169
+
170
+ ```ts
171
+ export type MyPluginConfig = {
172
+ /**
173
+ * List of collections to add a custom field
174
+ */
175
+ collections?: Partial<Record<CollectionSlug, true>>
176
+ /**
177
+ * Disable the plugin
178
+ */
179
+ disabled?: boolean
180
+ }
181
+ ```
182
+
183
+ 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.
184
+
185
+ ##### Testing
186
+
187
+ 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.
188
+
189
+ 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.
190
+
191
+ Writing tests with Vitest is very straightforward, and you can learn more about how it works in the [Vitest documentation.](https://vitest.dev/)
192
+
193
+ For this template, we stubbed out `int.spec.ts` in the `dev` folder where you can write your tests.
194
+
195
+ ```ts
196
+ describe('Plugin tests', () => {
197
+ // Create tests to ensure expected behavior from the plugin
198
+ it('some condition that must be met', () => {
199
+ // Write your test logic here
200
+ expect(...)
201
+ })
202
+ })
203
+ ```
204
+
205
+ ## Best practices
206
+
207
+ With this tutorial and the plugin template, you should have everything you need to start building your own plugin.
208
+ In addition to the setup, here are other best practices aim we follow:
209
+
210
+ - **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.
211
+ - **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)
212
+ - **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/).
213
+ - **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).
214
+ - **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.
215
+
216
+ # Questions
217
+
218
+ Please contact [Payload](mailto:dev@payloadcms.com) with any questions about using this plugin template.
@@ -0,0 +1,10 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ import type { AuthRefreshPluginOptions } from '../index';
3
+ type RefreshTokensOptions = Pick<AuthRefreshPluginOptions, 'entity_slug'>;
4
+ export declare enum RevocationReason {
5
+ SuspiciousActivity = "suspicious_activity",
6
+ TokenRotation = "token_rotation",
7
+ UserLogout = "user_logout"
8
+ }
9
+ export declare const RefreshTokens: (options: RefreshTokensOptions) => CollectionConfig;
10
+ export {};
@@ -0,0 +1,67 @@
1
+ export var RevocationReason = /*#__PURE__*/ function(RevocationReason) {
2
+ RevocationReason["SuspiciousActivity"] = "suspicious_activity";
3
+ RevocationReason["TokenRotation"] = "token_rotation";
4
+ RevocationReason["UserLogout"] = "user_logout";
5
+ return RevocationReason;
6
+ }({});
7
+ export const RefreshTokens = (options)=>({
8
+ slug: 'refresh-tokens',
9
+ admin: {
10
+ hidden: true
11
+ },
12
+ fields: [
13
+ {
14
+ name: 'entity',
15
+ type: 'relationship',
16
+ relationTo: options.entity_slug,
17
+ required: true
18
+ },
19
+ {
20
+ name: 'tokenHash',
21
+ type: 'text',
22
+ index: true,
23
+ required: true,
24
+ unique: true
25
+ },
26
+ {
27
+ name: 'expiresAt',
28
+ type: 'date',
29
+ required: true
30
+ },
31
+ {
32
+ name: 'lastUsedAt',
33
+ type: 'date'
34
+ },
35
+ {
36
+ name: 'rotatedAt',
37
+ type: 'date'
38
+ },
39
+ {
40
+ name: 'revokedAt',
41
+ type: 'date'
42
+ },
43
+ {
44
+ name: 'deviceId',
45
+ type: 'text',
46
+ required: true
47
+ },
48
+ {
49
+ name: 'userAgent',
50
+ type: 'text'
51
+ },
52
+ {
53
+ name: 'ipAddress',
54
+ type: 'text'
55
+ },
56
+ {
57
+ name: 'revocationReason',
58
+ type: 'select',
59
+ options: Object.entries(RevocationReason).map(([key, value])=>({
60
+ label: key,
61
+ value
62
+ }))
63
+ }
64
+ ]
65
+ });
66
+
67
+ //# sourceMappingURL=refreshTokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/refreshTokens.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport type { AuthRefreshPluginOptions } from '../index'\n\ntype RefreshTokensOptions = Pick<AuthRefreshPluginOptions, 'entity_slug'>\n\nexport enum RevocationReason {\n SuspiciousActivity = 'suspicious_activity',\n TokenRotation = 'token_rotation',\n UserLogout = 'user_logout',\n}\n\nexport const RefreshTokens = (options: RefreshTokensOptions): CollectionConfig => ({\n slug: 'refresh-tokens',\n admin: {\n hidden: true,\n },\n fields: [\n {\n name: 'entity',\n type: 'relationship',\n relationTo: options.entity_slug,\n required: true,\n },\n {\n name: 'tokenHash',\n type: 'text',\n index: true,\n required: true,\n unique: true,\n },\n {\n name: 'expiresAt',\n type: 'date',\n required: true,\n },\n {\n name: 'lastUsedAt',\n type: 'date',\n },\n {\n name: 'rotatedAt',\n type: 'date',\n },\n {\n name: 'revokedAt',\n type: 'date',\n },\n {\n name: 'deviceId',\n type: 'text',\n required: true,\n },\n {\n name: 'userAgent',\n type: 'text',\n },\n {\n name: 'ipAddress',\n type: 'text',\n },\n {\n name: 'revocationReason',\n type: 'select',\n options: Object.entries(RevocationReason).map(([key, value]) => ({\n label: key,\n value,\n })),\n },\n ],\n})\n"],"names":["RevocationReason","RefreshTokens","options","slug","admin","hidden","fields","name","type","relationTo","entity_slug","required","index","unique","Object","entries","map","key","value","label"],"mappings":"AAMA,OAAO,IAAA,AAAKA,0CAAAA;;;;WAAAA;MAIX;AAED,OAAO,MAAMC,gBAAgB,CAACC,UAAqD,CAAA;QACjFC,MAAM;QACNC,OAAO;YACLC,QAAQ;QACV;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,YAAYP,QAAQQ,WAAW;gBAC/BC,UAAU;YACZ;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNI,OAAO;gBACPD,UAAU;gBACVE,QAAQ;YACV;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNG,UAAU;YACZ;YACA;gBACEJ,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNG,UAAU;YACZ;YACA;gBACEJ,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNN,SAASY,OAAOC,OAAO,CAACf,kBAAkBgB,GAAG,CAAC,CAAC,CAACC,KAAKC,MAAM,GAAM,CAAA;wBAC/DC,OAAOF;wBACPC;oBACF,CAAA;YACF;SACD;IACH,CAAA,EAAE"}
@@ -0,0 +1,5 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { AuthRefreshPluginOptions } from '../index';
3
+ type LoginEndpointOptions = Pick<AuthRefreshPluginOptions, 'entity_slug' | 'identifier_field' | 'pepper' | 'refreshTokenTTL'>;
4
+ export declare const loginEndpoint: (options: LoginEndpointOptions) => Endpoint;
5
+ export {};
@@ -0,0 +1,42 @@
1
+ import { createRefreshToken } from 'src/utils/crypto';
2
+ import { getRequestMeta } from '../utils/getRequestMeta';
3
+ export const loginEndpoint = (options)=>({
4
+ handler: async (req)=>{
5
+ const { deviceId, identifier, password } = await req.json?.() || {};
6
+ if (!identifier || !password) {
7
+ return Response.json({
8
+ message: 'Identifier and password are required.'
9
+ }, {
10
+ status: 400
11
+ });
12
+ }
13
+ const login = await req.payload.login({
14
+ collection: options.entity_slug,
15
+ data: {
16
+ [options.identifier_field]: identifier,
17
+ password
18
+ }
19
+ });
20
+ const { token, tokenHash } = createRefreshToken(options.pepper);
21
+ await req.payload.create({
22
+ collection: 'refresh-tokens',
23
+ data: {
24
+ deviceId,
25
+ entity: login.user.id,
26
+ expiresAt: new Date(Date.now() + options.refreshTokenTTL * 24 * 60 * 60 * 1000),
27
+ lastUsedAt: new Date(),
28
+ tokenHash,
29
+ ...getRequestMeta(req)
30
+ }
31
+ });
32
+ return Response.json({
33
+ access_token: login.token,
34
+ refresh_token: token,
35
+ user: login.user
36
+ });
37
+ },
38
+ method: 'post',
39
+ path: `${options.entity_slug}/auth/login`
40
+ });
41
+
42
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/login.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { createRefreshToken } from 'src/utils/crypto'\n\nimport type { AuthRefreshPluginOptions } from '../index'\n\nimport { getRequestMeta } from '../utils/getRequestMeta'\n\ntype LoginEndpointOptions = Pick<\n AuthRefreshPluginOptions,\n 'entity_slug' | 'identifier_field' | 'pepper' | 'refreshTokenTTL'\n>\n\nexport const loginEndpoint = (options: LoginEndpointOptions): Endpoint => ({\n handler: async (req) => {\n const { deviceId, identifier, password } = (await req.json?.()) || {}\n\n if (!identifier || !password) {\n return Response.json({ message: 'Identifier and password are required.' }, { status: 400 })\n }\n\n const login = await req.payload.login({\n collection: options.entity_slug,\n data: {\n [options.identifier_field]: identifier,\n password,\n } as any,\n })\n\n const { token, tokenHash } = createRefreshToken(options.pepper)\n\n await req.payload.create({\n collection: 'refresh-tokens',\n data: {\n deviceId,\n entity: login.user.id,\n expiresAt: new Date(Date.now() + options.refreshTokenTTL * 24 * 60 * 60 * 1000),\n lastUsedAt: new Date(),\n tokenHash,\n ...getRequestMeta(req),\n },\n })\n\n return Response.json({\n access_token: login.token,\n refresh_token: token,\n user: login.user,\n })\n },\n method: 'post',\n path: `${options.entity_slug}/auth/login`,\n})\n"],"names":["createRefreshToken","getRequestMeta","loginEndpoint","options","handler","req","deviceId","identifier","password","json","Response","message","status","login","payload","collection","entity_slug","data","identifier_field","token","tokenHash","pepper","create","entity","user","id","expiresAt","Date","now","refreshTokenTTL","lastUsedAt","access_token","refresh_token","method","path"],"mappings":"AAEA,SAASA,kBAAkB,QAAQ,mBAAkB;AAIrD,SAASC,cAAc,QAAQ,0BAAyB;AAOxD,OAAO,MAAMC,gBAAgB,CAACC,UAA6C,CAAA;QACzEC,SAAS,OAAOC;YACd,MAAM,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,QAAQ,EAAE,GAAG,AAAC,MAAMH,IAAII,IAAI,QAAS,CAAC;YAEpE,IAAI,CAACF,cAAc,CAACC,UAAU;gBAC5B,OAAOE,SAASD,IAAI,CAAC;oBAAEE,SAAS;gBAAwC,GAAG;oBAAEC,QAAQ;gBAAI;YAC3F;YAEA,MAAMC,QAAQ,MAAMR,IAAIS,OAAO,CAACD,KAAK,CAAC;gBACpCE,YAAYZ,QAAQa,WAAW;gBAC/BC,MAAM;oBACJ,CAACd,QAAQe,gBAAgB,CAAC,EAAEX;oBAC5BC;gBACF;YACF;YAEA,MAAM,EAAEW,KAAK,EAAEC,SAAS,EAAE,GAAGpB,mBAAmBG,QAAQkB,MAAM;YAE9D,MAAMhB,IAAIS,OAAO,CAACQ,MAAM,CAAC;gBACvBP,YAAY;gBACZE,MAAM;oBACJX;oBACAiB,QAAQV,MAAMW,IAAI,CAACC,EAAE;oBACrBC,WAAW,IAAIC,KAAKA,KAAKC,GAAG,KAAKzB,QAAQ0B,eAAe,GAAG,KAAK,KAAK,KAAK;oBAC1EC,YAAY,IAAIH;oBAChBP;oBACA,GAAGnB,eAAeI,IAAI;gBACxB;YACF;YAEA,OAAOK,SAASD,IAAI,CAAC;gBACnBsB,cAAclB,MAAMM,KAAK;gBACzBa,eAAeb;gBACfK,MAAMX,MAAMW,IAAI;YAClB;QACF;QACAS,QAAQ;QACRC,MAAM,GAAG/B,QAAQa,WAAW,CAAC,WAAW,CAAC;IAC3C,CAAA,EAAE"}
@@ -0,0 +1,5 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { AuthRefreshPluginOptions } from '../index';
3
+ type LogoutEndpointOptions = Pick<AuthRefreshPluginOptions, 'pepper'>;
4
+ export declare const logoutEndpoint: (options: LogoutEndpointOptions) => Endpoint;
5
+ export {};
@@ -0,0 +1,44 @@
1
+ import { RevocationReason } from '../collections/refreshTokens';
2
+ import { hashToken } from '../utils/crypto';
3
+ export const logoutEndpoint = (options)=>({
4
+ handler: async (req)=>{
5
+ const { refresh_token } = await req.json?.() || {};
6
+ if (!refresh_token) {
7
+ return Response.json({
8
+ message: 'refreshToken is required to logout.'
9
+ }, {
10
+ status: 400
11
+ });
12
+ }
13
+ const tokenHash = hashToken(refresh_token, options.pepper);
14
+ const result = await req.payload.find({
15
+ collection: 'refresh-tokens',
16
+ where: {
17
+ tokenHash: {
18
+ equals: tokenHash
19
+ }
20
+ }
21
+ });
22
+ if (!result?.docs?.length) {
23
+ return new Response(null, {
24
+ status: 204
25
+ });
26
+ }
27
+ const session = result.docs[0];
28
+ await req.payload.update({
29
+ id: session.id,
30
+ collection: 'refresh-tokens',
31
+ data: {
32
+ revocationReason: RevocationReason.UserLogout,
33
+ revokedAt: new Date()
34
+ }
35
+ });
36
+ return new Response(null, {
37
+ status: 204
38
+ });
39
+ },
40
+ method: 'post',
41
+ path: '/auth/logout'
42
+ });
43
+
44
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/logout.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { AuthRefreshPluginOptions } from '../index'\n\nimport { RevocationReason } from '../collections/refreshTokens'\nimport { hashToken } from '../utils/crypto'\n\ntype LogoutEndpointOptions = Pick<AuthRefreshPluginOptions, 'pepper'>\n\nexport const logoutEndpoint = (options: LogoutEndpointOptions): Endpoint => ({\n handler: async (req) => {\n const { refresh_token } = (await req.json?.()) || {}\n\n if (!refresh_token) {\n return Response.json({ message: 'refreshToken is required to logout.' }, { status: 400 })\n }\n\n const tokenHash = hashToken(refresh_token, options.pepper)\n const result = await req.payload.find({\n collection: 'refresh-tokens',\n where: { tokenHash: { equals: tokenHash } },\n })\n\n if (!result?.docs?.length) {\n return new Response(null, { status: 204 })\n }\n\n const session = result.docs[0]\n\n await req.payload.update({\n id: session.id,\n collection: 'refresh-tokens',\n data: {\n revocationReason: RevocationReason.UserLogout,\n revokedAt: new Date(),\n },\n })\n\n return new Response(null, { status: 204 })\n },\n method: 'post',\n path: '/auth/logout',\n})\n"],"names":["RevocationReason","hashToken","logoutEndpoint","options","handler","req","refresh_token","json","Response","message","status","tokenHash","pepper","result","payload","find","collection","where","equals","docs","length","session","update","id","data","revocationReason","UserLogout","revokedAt","Date","method","path"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,+BAA8B;AAC/D,SAASC,SAAS,QAAQ,kBAAiB;AAI3C,OAAO,MAAMC,iBAAiB,CAACC,UAA8C,CAAA;QAC3EC,SAAS,OAAOC;YACd,MAAM,EAAEC,aAAa,EAAE,GAAG,AAAC,MAAMD,IAAIE,IAAI,QAAS,CAAC;YAEnD,IAAI,CAACD,eAAe;gBAClB,OAAOE,SAASD,IAAI,CAAC;oBAAEE,SAAS;gBAAsC,GAAG;oBAAEC,QAAQ;gBAAI;YACzF;YAEA,MAAMC,YAAYV,UAAUK,eAAeH,QAAQS,MAAM;YACzD,MAAMC,SAAS,MAAMR,IAAIS,OAAO,CAACC,IAAI,CAAC;gBACpCC,YAAY;gBACZC,OAAO;oBAAEN,WAAW;wBAAEO,QAAQP;oBAAU;gBAAE;YAC5C;YAEA,IAAI,CAACE,QAAQM,MAAMC,QAAQ;gBACzB,OAAO,IAAIZ,SAAS,MAAM;oBAAEE,QAAQ;gBAAI;YAC1C;YAEA,MAAMW,UAAUR,OAAOM,IAAI,CAAC,EAAE;YAE9B,MAAMd,IAAIS,OAAO,CAACQ,MAAM,CAAC;gBACvBC,IAAIF,QAAQE,EAAE;gBACdP,YAAY;gBACZQ,MAAM;oBACJC,kBAAkBzB,iBAAiB0B,UAAU;oBAC7CC,WAAW,IAAIC;gBACjB;YACF;YAEA,OAAO,IAAIpB,SAAS,MAAM;gBAAEE,QAAQ;YAAI;QAC1C;QACAmB,QAAQ;QACRC,MAAM;IACR,CAAA,EAAE"}
@@ -0,0 +1,5 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { AuthRefreshPluginOptions } from '../index';
3
+ type RefreshEndpointOptions = Pick<AuthRefreshPluginOptions, 'entity_slug' | 'identifier_field' | 'pepper' | 'refreshTokenTTL'>;
4
+ export declare const refreshEndpoint: (options: RefreshEndpointOptions) => Endpoint;
5
+ export {};
@@ -0,0 +1,92 @@
1
+ import { RevocationReason } from '../collections/refreshTokens';
2
+ import { createRefreshToken, hashToken } from '../utils/crypto';
3
+ import { getRequestMeta } from '../utils/getRequestMeta';
4
+ import { signJWT } from '../utils/jwt';
5
+ export const refreshEndpoint = (options)=>({
6
+ handler: async (req)=>{
7
+ const { deviceId, refresh_token } = await req.json?.() || {};
8
+ if (!refresh_token || !deviceId) {
9
+ return Response.json({
10
+ message: 'Refresh token and device ID are required.'
11
+ }, {
12
+ status: 400
13
+ });
14
+ }
15
+ const result = await req.payload.find({
16
+ collection: 'refresh-tokens',
17
+ where: {
18
+ tokenHash: {
19
+ equals: hashToken(refresh_token, options.pepper)
20
+ }
21
+ }
22
+ });
23
+ if (!result?.docs?.length) {
24
+ return Response.json({
25
+ message: 'Unauthorized.'
26
+ }, {
27
+ status: 401
28
+ });
29
+ }
30
+ const session = result.docs[0];
31
+ if (session.deviceId !== deviceId || session.expiresAt < new Date() || session.revokedAt) {
32
+ return Response.json({
33
+ message: 'Unauthorized.'
34
+ }, {
35
+ status: 401
36
+ });
37
+ }
38
+ if (session.rotatedAt) {
39
+ await req.payload.update({
40
+ id: session.id,
41
+ collection: 'refresh-tokens',
42
+ data: {
43
+ revocationReason: RevocationReason.SuspiciousActivity,
44
+ revokedAt: new Date()
45
+ }
46
+ });
47
+ return Response.json({
48
+ message: 'Unauthorized.'
49
+ }, {
50
+ status: 401
51
+ });
52
+ }
53
+ const { token, tokenHash } = createRefreshToken(options.pepper);
54
+ const now = new Date();
55
+ const newSession = await req.payload.create({
56
+ collection: 'refresh-tokens',
57
+ data: {
58
+ deviceId,
59
+ entity: session.user,
60
+ expiresAt: new Date(now.getTime() + options.refreshTokenTTL * 24 * 60 * 60 * 1000),
61
+ lastUsedAt: now,
62
+ tokenHash,
63
+ ...getRequestMeta(req)
64
+ }
65
+ });
66
+ await req.payload.update({
67
+ id: session.id,
68
+ collection: 'refresh-tokens',
69
+ data: {
70
+ lastUsedAt: now,
71
+ replacedBy: newSession.id,
72
+ revocationReason: RevocationReason.TokenRotation,
73
+ rotatedAt: now
74
+ }
75
+ });
76
+ const accessJWT = signJWT({
77
+ collectionId: session.user,
78
+ collectionSlug: options.entity_slug,
79
+ expiresIn: '15m',
80
+ rawSecret: req.payload.secret
81
+ });
82
+ return Response.json({
83
+ access_token: accessJWT,
84
+ refresh_token: token,
85
+ user: session.user
86
+ });
87
+ },
88
+ method: 'post',
89
+ path: `${options.entity_slug}/auth/refresh`
90
+ });
91
+
92
+ //# sourceMappingURL=refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/refresh.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { AuthRefreshPluginOptions } from '../index'\n\nimport { RevocationReason } from '../collections/refreshTokens'\nimport { createRefreshToken, hashToken } from '../utils/crypto'\nimport { getRequestMeta } from '../utils/getRequestMeta'\nimport { signJWT } from '../utils/jwt'\n\ntype RefreshEndpointOptions = Pick<\n AuthRefreshPluginOptions,\n 'entity_slug' | 'identifier_field' | 'pepper' | 'refreshTokenTTL'\n>\n\nexport const refreshEndpoint = (options: RefreshEndpointOptions): Endpoint => ({\n handler: async (req) => {\n const { deviceId, refresh_token } = (await req.json?.()) || {}\n\n if (!refresh_token || !deviceId) {\n return Response.json(\n { message: 'Refresh token and device ID are required.' },\n { status: 400 },\n )\n }\n\n const result = await req.payload.find({\n collection: 'refresh-tokens',\n where: {\n tokenHash: {\n equals: hashToken(refresh_token, options.pepper),\n },\n },\n })\n\n if (!result?.docs?.length) {\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n const session = result.docs[0]\n\n if (session.deviceId !== deviceId || session.expiresAt < new Date() || session.revokedAt) {\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n if (session.rotatedAt) {\n await req.payload.update({\n id: session.id,\n collection: 'refresh-tokens',\n data: { revocationReason: RevocationReason.SuspiciousActivity, revokedAt: new Date() },\n })\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n const { token, tokenHash } = createRefreshToken(options.pepper)\n const now = new Date()\n\n const newSession = await req.payload.create({\n collection: 'refresh-tokens',\n data: {\n deviceId,\n entity: session.user,\n expiresAt: new Date(now.getTime() + options.refreshTokenTTL * 24 * 60 * 60 * 1000),\n lastUsedAt: now,\n tokenHash,\n ...getRequestMeta(req),\n },\n })\n\n await req.payload.update({\n id: session.id,\n collection: 'refresh-tokens',\n data: {\n lastUsedAt: now,\n replacedBy: newSession.id,\n revocationReason: RevocationReason.TokenRotation,\n rotatedAt: now,\n },\n })\n\n const accessJWT = signJWT({\n collectionId: session.user,\n collectionSlug: options.entity_slug,\n expiresIn: '15m',\n rawSecret: req.payload.secret,\n })\n\n return Response.json({\n access_token: accessJWT,\n refresh_token: token,\n user: session.user,\n })\n },\n method: 'post',\n path: `${options.entity_slug}/auth/refresh`,\n})\n"],"names":["RevocationReason","createRefreshToken","hashToken","getRequestMeta","signJWT","refreshEndpoint","options","handler","req","deviceId","refresh_token","json","Response","message","status","result","payload","find","collection","where","tokenHash","equals","pepper","docs","length","session","expiresAt","Date","revokedAt","rotatedAt","update","id","data","revocationReason","SuspiciousActivity","token","now","newSession","create","entity","user","getTime","refreshTokenTTL","lastUsedAt","replacedBy","TokenRotation","accessJWT","collectionId","collectionSlug","entity_slug","expiresIn","rawSecret","secret","access_token","method","path"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,+BAA8B;AAC/D,SAASC,kBAAkB,EAAEC,SAAS,QAAQ,kBAAiB;AAC/D,SAASC,cAAc,QAAQ,0BAAyB;AACxD,SAASC,OAAO,QAAQ,eAAc;AAOtC,OAAO,MAAMC,kBAAkB,CAACC,UAA+C,CAAA;QAC7EC,SAAS,OAAOC;YACd,MAAM,EAAEC,QAAQ,EAAEC,aAAa,EAAE,GAAG,AAAC,MAAMF,IAAIG,IAAI,QAAS,CAAC;YAE7D,IAAI,CAACD,iBAAiB,CAACD,UAAU;gBAC/B,OAAOG,SAASD,IAAI,CAClB;oBAAEE,SAAS;gBAA4C,GACvD;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,MAAMC,SAAS,MAAMP,IAAIQ,OAAO,CAACC,IAAI,CAAC;gBACpCC,YAAY;gBACZC,OAAO;oBACLC,WAAW;wBACTC,QAAQnB,UAAUQ,eAAeJ,QAAQgB,MAAM;oBACjD;gBACF;YACF;YAEA,IAAI,CAACP,QAAQQ,MAAMC,QAAQ;gBACzB,OAAOZ,SAASD,IAAI,CAAC;oBAAEE,SAAS;gBAAgB,GAAG;oBAAEC,QAAQ;gBAAI;YACnE;YAEA,MAAMW,UAAUV,OAAOQ,IAAI,CAAC,EAAE;YAE9B,IAAIE,QAAQhB,QAAQ,KAAKA,YAAYgB,QAAQC,SAAS,GAAG,IAAIC,UAAUF,QAAQG,SAAS,EAAE;gBACxF,OAAOhB,SAASD,IAAI,CAAC;oBAAEE,SAAS;gBAAgB,GAAG;oBAAEC,QAAQ;gBAAI;YACnE;YAEA,IAAIW,QAAQI,SAAS,EAAE;gBACrB,MAAMrB,IAAIQ,OAAO,CAACc,MAAM,CAAC;oBACvBC,IAAIN,QAAQM,EAAE;oBACdb,YAAY;oBACZc,MAAM;wBAAEC,kBAAkBjC,iBAAiBkC,kBAAkB;wBAAEN,WAAW,IAAID;oBAAO;gBACvF;gBACA,OAAOf,SAASD,IAAI,CAAC;oBAAEE,SAAS;gBAAgB,GAAG;oBAAEC,QAAQ;gBAAI;YACnE;YAEA,MAAM,EAAEqB,KAAK,EAAEf,SAAS,EAAE,GAAGnB,mBAAmBK,QAAQgB,MAAM;YAC9D,MAAMc,MAAM,IAAIT;YAEhB,MAAMU,aAAa,MAAM7B,IAAIQ,OAAO,CAACsB,MAAM,CAAC;gBAC1CpB,YAAY;gBACZc,MAAM;oBACJvB;oBACA8B,QAAQd,QAAQe,IAAI;oBACpBd,WAAW,IAAIC,KAAKS,IAAIK,OAAO,KAAKnC,QAAQoC,eAAe,GAAG,KAAK,KAAK,KAAK;oBAC7EC,YAAYP;oBACZhB;oBACA,GAAGjB,eAAeK,IAAI;gBACxB;YACF;YAEA,MAAMA,IAAIQ,OAAO,CAACc,MAAM,CAAC;gBACvBC,IAAIN,QAAQM,EAAE;gBACdb,YAAY;gBACZc,MAAM;oBACJW,YAAYP;oBACZQ,YAAYP,WAAWN,EAAE;oBACzBE,kBAAkBjC,iBAAiB6C,aAAa;oBAChDhB,WAAWO;gBACb;YACF;YAEA,MAAMU,YAAY1C,QAAQ;gBACxB2C,cAActB,QAAQe,IAAI;gBAC1BQ,gBAAgB1C,QAAQ2C,WAAW;gBACnCC,WAAW;gBACXC,WAAW3C,IAAIQ,OAAO,CAACoC,MAAM;YAC/B;YAEA,OAAOxC,SAASD,IAAI,CAAC;gBACnB0C,cAAcP;gBACdpC,eAAeyB;gBACfK,MAAMf,QAAQe,IAAI;YACpB;QACF;QACAc,QAAQ;QACRC,MAAM,GAAGjD,QAAQ2C,WAAW,CAAC,aAAa,CAAC;IAC7C,CAAA,EAAE"}
@@ -0,0 +1,8 @@
1
+ import type { Config } from 'payload';
2
+ export type AuthRefreshPluginOptions = {
3
+ entity_slug: string;
4
+ identifier_field: string;
5
+ pepper: string;
6
+ refreshTokenTTL: number;
7
+ };
8
+ export declare const authRefreshPlugin: ({ entity_slug, identifier_field, pepper, refreshTokenTTL, }: AuthRefreshPluginOptions) => ((config: Config) => Config);
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ import { RefreshTokens } from './collections/refreshTokens';
2
+ import { loginEndpoint } from './endpoints/login';
3
+ import { logoutEndpoint } from './endpoints/logout';
4
+ import { refreshEndpoint } from './endpoints/refresh';
5
+ export const authRefreshPlugin = ({ entity_slug = 'users', identifier_field = 'email', pepper = 'default-pepper', refreshTokenTTL = 30 })=>(config)=>{
6
+ return {
7
+ ...config,
8
+ collections: [
9
+ ...config.collections || [],
10
+ RefreshTokens({
11
+ entity_slug
12
+ })
13
+ ],
14
+ endpoints: [
15
+ ...config.endpoints || [],
16
+ loginEndpoint({
17
+ entity_slug,
18
+ identifier_field,
19
+ pepper,
20
+ refreshTokenTTL
21
+ }),
22
+ refreshEndpoint({
23
+ entity_slug,
24
+ identifier_field,
25
+ pepper,
26
+ refreshTokenTTL
27
+ }),
28
+ logoutEndpoint({
29
+ pepper
30
+ })
31
+ ]
32
+ };
33
+ };
34
+
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport { RefreshTokens } from './collections/refreshTokens'\nimport { loginEndpoint } from './endpoints/login'\nimport { logoutEndpoint } from './endpoints/logout'\nimport { refreshEndpoint } from './endpoints/refresh'\n\nexport type AuthRefreshPluginOptions = {\n entity_slug: string\n identifier_field: string\n pepper: string\n refreshTokenTTL: number\n}\n\nexport const authRefreshPlugin =\n ({\n entity_slug = 'users',\n identifier_field = 'email',\n pepper = 'default-pepper',\n refreshTokenTTL = 30,\n }: AuthRefreshPluginOptions): ((config: Config) => Config) =>\n (config) => {\n return {\n ...config,\n collections: [...(config.collections || []), RefreshTokens({ entity_slug })],\n endpoints: [\n ...(config.endpoints || []),\n loginEndpoint({ entity_slug, identifier_field, pepper, refreshTokenTTL }),\n refreshEndpoint({ entity_slug, identifier_field, pepper, refreshTokenTTL }),\n logoutEndpoint({ pepper }),\n ],\n }\n }\n"],"names":["RefreshTokens","loginEndpoint","logoutEndpoint","refreshEndpoint","authRefreshPlugin","entity_slug","identifier_field","pepper","refreshTokenTTL","config","collections","endpoints"],"mappings":"AAEA,SAASA,aAAa,QAAQ,8BAA6B;AAC3D,SAASC,aAAa,QAAQ,oBAAmB;AACjD,SAASC,cAAc,QAAQ,qBAAoB;AACnD,SAASC,eAAe,QAAQ,sBAAqB;AASrD,OAAO,MAAMC,oBACX,CAAC,EACCC,cAAc,OAAO,EACrBC,mBAAmB,OAAO,EAC1BC,SAAS,gBAAgB,EACzBC,kBAAkB,EAAE,EACK,GAC3B,CAACC;QACC,OAAO;YACL,GAAGA,MAAM;YACTC,aAAa;mBAAKD,OAAOC,WAAW,IAAI,EAAE;gBAAGV,cAAc;oBAAEK;gBAAY;aAAG;YAC5EM,WAAW;mBACLF,OAAOE,SAAS,IAAI,EAAE;gBAC1BV,cAAc;oBAAEI;oBAAaC;oBAAkBC;oBAAQC;gBAAgB;gBACvEL,gBAAgB;oBAAEE;oBAAaC;oBAAkBC;oBAAQC;gBAAgB;gBACzEN,eAAe;oBAAEK;gBAAO;aACzB;QACH;IACF,EAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const createRefreshToken: (pepper: string) => {
2
+ token: string;
3
+ tokenHash: string;
4
+ };
5
+ export declare const hashToken: (token: string, pepper: string) => string;
@@ -0,0 +1,14 @@
1
+ import crypto from 'crypto';
2
+ export const createRefreshToken = (pepper)=>{
3
+ const token = crypto.randomBytes(32).toString('base64url');
4
+ const tokenHash = hashToken(token, pepper);
5
+ return {
6
+ token,
7
+ tokenHash
8
+ };
9
+ };
10
+ export const hashToken = (token, pepper)=>{
11
+ return crypto.createHmac('sha256', pepper).update(token).digest('base64');
12
+ };
13
+
14
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/crypto.ts"],"sourcesContent":["import crypto from 'crypto'\n\nexport const createRefreshToken = (pepper: string) => {\n const token = crypto.randomBytes(32).toString('base64url')\n const tokenHash = hashToken(token, pepper)\n\n return { token, tokenHash }\n}\n\nexport const hashToken = (token: string, pepper: string) => {\n return crypto.createHmac('sha256', pepper).update(token).digest('base64')\n}\n"],"names":["crypto","createRefreshToken","pepper","token","randomBytes","toString","tokenHash","hashToken","createHmac","update","digest"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAE3B,OAAO,MAAMC,qBAAqB,CAACC;IACjC,MAAMC,QAAQH,OAAOI,WAAW,CAAC,IAAIC,QAAQ,CAAC;IAC9C,MAAMC,YAAYC,UAAUJ,OAAOD;IAEnC,OAAO;QAAEC;QAAOG;IAAU;AAC5B,EAAC;AAED,OAAO,MAAMC,YAAY,CAACJ,OAAeD;IACvC,OAAOF,OAAOQ,UAAU,CAAC,UAAUN,QAAQO,MAAM,CAACN,OAAOO,MAAM,CAAC;AAClE,EAAC"}
@@ -0,0 +1,4 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ export declare const getRequestMeta: (req: PayloadRequest) => {
3
+ userAgent: string | undefined;
4
+ };
@@ -0,0 +1,8 @@
1
+ export const getRequestMeta = (req)=>{
2
+ const userAgent = req.headers.get('user-agent') || undefined;
3
+ return {
4
+ userAgent
5
+ };
6
+ };
7
+
8
+ //# sourceMappingURL=getRequestMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/getRequestMeta.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nexport const getRequestMeta = (req: PayloadRequest) => {\n const userAgent = req.headers.get('user-agent') || undefined\n\n return { userAgent }\n}\n"],"names":["getRequestMeta","req","userAgent","headers","get","undefined"],"mappings":"AAEA,OAAO,MAAMA,iBAAiB,CAACC;IAC7B,MAAMC,YAAYD,IAAIE,OAAO,CAACC,GAAG,CAAC,iBAAiBC;IAEnD,OAAO;QAAEH;IAAU;AACrB,EAAC"}
@@ -0,0 +1,8 @@
1
+ type SignJWTOptions = {
2
+ collectionId: string;
3
+ collectionSlug: string;
4
+ expiresIn: number | string;
5
+ rawSecret: string;
6
+ };
7
+ export declare const signJWT: ({ collectionId, collectionSlug, expiresIn, rawSecret }: SignJWTOptions) => string;
8
+ export {};
@@ -0,0 +1,18 @@
1
+ import crypto from 'crypto';
2
+ import jwt from 'jsonwebtoken';
3
+ const derivePayloadJWTSecret = (secret)=>{
4
+ const hash = crypto.createHash('sha256').update(secret).digest('hex');
5
+ return hash.slice(0, 32);
6
+ };
7
+ export const signJWT = ({ collectionId, collectionSlug, expiresIn, rawSecret })=>{
8
+ const signingKey = derivePayloadJWTSecret(rawSecret);
9
+ const token = jwt.sign({
10
+ id: collectionId,
11
+ collection: collectionSlug
12
+ }, signingKey, {
13
+ expiresIn
14
+ });
15
+ return token;
16
+ };
17
+
18
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/jwt.ts"],"sourcesContent":["import type { Secret, SignOptions } from 'jsonwebtoken'\n\nimport crypto from 'crypto'\nimport jwt from 'jsonwebtoken'\n\nconst derivePayloadJWTSecret = (secret: string) => {\n const hash = crypto.createHash('sha256').update(secret).digest('hex')\n return hash.slice(0, 32)\n}\n\ntype SignJWTOptions = {\n collectionId: string\n collectionSlug: string\n expiresIn: number | string\n rawSecret: string\n}\n\nexport const signJWT = ({ collectionId, collectionSlug, expiresIn, rawSecret }: SignJWTOptions) => {\n const signingKey = derivePayloadJWTSecret(rawSecret)\n\n const token = jwt.sign(\n {\n id: collectionId,\n collection: collectionSlug,\n },\n signingKey as Secret,\n { expiresIn } as SignOptions,\n )\n\n return token\n}\n"],"names":["crypto","jwt","derivePayloadJWTSecret","secret","hash","createHash","update","digest","slice","signJWT","collectionId","collectionSlug","expiresIn","rawSecret","signingKey","token","sign","id","collection"],"mappings":"AAEA,OAAOA,YAAY,SAAQ;AAC3B,OAAOC,SAAS,eAAc;AAE9B,MAAMC,yBAAyB,CAACC;IAC9B,MAAMC,OAAOJ,OAAOK,UAAU,CAAC,UAAUC,MAAM,CAACH,QAAQI,MAAM,CAAC;IAC/D,OAAOH,KAAKI,KAAK,CAAC,GAAG;AACvB;AASA,OAAO,MAAMC,UAAU,CAAC,EAAEC,YAAY,EAAEC,cAAc,EAAEC,SAAS,EAAEC,SAAS,EAAkB;IAC5F,MAAMC,aAAaZ,uBAAuBW;IAE1C,MAAME,QAAQd,IAAIe,IAAI,CACpB;QACEC,IAAIP;QACJQ,YAAYP;IACd,GACAG,YACA;QAAEF;IAAU;IAGd,OAAOG;AACT,EAAC"}
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@bivola/refresh-auth",
3
+ "version": "1.0.0",
4
+ "description": "A blank template to get started with Payload 3.0",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
21
+ "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
22
+ "build:types": "tsc --outDir dist --rootDir ./src",
23
+ "clean": "rimraf {dist,*.tsbuildinfo}",
24
+ "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
25
+ "dev": "next dev dev --turbo",
26
+ "dev:generate-importmap": "pnpm dev:payload generate:importmap",
27
+ "dev:generate-types": "pnpm dev:payload generate:types",
28
+ "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
29
+ "generate:importmap": "pnpm dev:generate-importmap",
30
+ "generate:types": "pnpm dev:generate-types",
31
+ "lint": "eslint",
32
+ "lint:fix": "eslint ./src --fix",
33
+ "test": "pnpm test:int && pnpm test:e2e",
34
+ "test:e2e": "playwright test",
35
+ "test:int": "vitest"
36
+ },
37
+ "devDependencies": {
38
+ "@eslint/eslintrc": "^3.2.0",
39
+ "@payloadcms/eslint-config": "3.9.0",
40
+ "@payloadcms/next": "3.37.0",
41
+ "@payloadcms/richtext-lexical": "3.37.0",
42
+ "@payloadcms/ui": "3.37.0",
43
+ "@playwright/test": "1.58.2",
44
+ "@swc-node/register": "1.10.9",
45
+ "@swc/cli": "0.6.0",
46
+ "@types/jsonwebtoken": "^9.0.10",
47
+ "@types/node": "22.19.9",
48
+ "@types/react": "19.2.9",
49
+ "@types/react-dom": "19.2.3",
50
+ "copyfiles": "2.4.1",
51
+ "cross-env": "^7.0.3",
52
+ "eslint": "^9.23.0",
53
+ "eslint-config-next": "15.4.11",
54
+ "graphql": "^16.8.1",
55
+ "jsonwebtoken": "^9.0.3",
56
+ "next": "15.4.11",
57
+ "open": "^10.1.0",
58
+ "payload": "3.37.0",
59
+ "prettier": "^3.4.2",
60
+ "qs-esm": "7.0.2",
61
+ "react": "19.2.1",
62
+ "react-dom": "19.2.1",
63
+ "rimraf": "3.0.2",
64
+ "sharp": "0.34.2",
65
+ "sort-package-json": "^2.10.0",
66
+ "typescript": "5.7.3",
67
+ "vite-tsconfig-paths": "6.0.5",
68
+ "vitest": "4.0.18"
69
+ },
70
+ "peerDependencies": {
71
+ "payload": "^3.37.0"
72
+ },
73
+ "engines": {
74
+ "node": "^18.20.2 || >=20.9.0",
75
+ "pnpm": "^9 || ^10"
76
+ },
77
+ "publishConfig": {
78
+ "exports": {
79
+ ".": {
80
+ "import": "./dist/index.js",
81
+ "types": "./dist/index.d.ts",
82
+ "default": "./dist/index.js"
83
+ }
84
+ },
85
+ "main": "./dist/index.js",
86
+ "types": "./dist/index.d.ts"
87
+ },
88
+ "pnpm": {
89
+ "onlyBuiltDependencies": [
90
+ "sharp",
91
+ "esbuild",
92
+ "unrs-resolver"
93
+ ]
94
+ },
95
+ "registry": "https://registry.npmjs.org/"
96
+ }