diff --git a/dev/src/collections/LocalUsers.ts b/dev/src/collections/LocalUsers.ts new file mode 100644 index 0000000..d2efb40 --- /dev/null +++ b/dev/src/collections/LocalUsers.ts @@ -0,0 +1,10 @@ +import { CollectionConfig } from "payload"; + +const Users: CollectionConfig = { + slug: "local-users", + auth: true, + admin: { useAsTitle: "email" }, + fields: [{ name: "email", type: "email", required: true }], +}; + +export default Users; diff --git a/dev/src/migrations/20241017_034629.ts b/dev/src/migrations/20241017_034629.ts deleted file mode 100644 index a1c5cb9..0000000 --- a/dev/src/migrations/20241017_034629.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { MigrateDownArgs, MigrateUpArgs, sql } from "@payloadcms/db-sqlite"; - -export async function up({ payload, req }: MigrateUpArgs): Promise { - await payload.db.drizzle.run(sql`CREATE TABLE \`users\` ( - \`id\` integer PRIMARY KEY NOT NULL, - \`email\` text NOT NULL, - \`sub\` text, - \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, - \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL - ); - `); - await payload.db.drizzle.run(sql`CREATE TABLE \`payload_locked_documents\` ( - \`id\` integer PRIMARY KEY NOT NULL, - \`global_slug\` text, - \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, - \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL - ); - `); - await payload.db.drizzle - .run(sql`CREATE TABLE \`payload_locked_documents_rels\` ( - \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - \`order\` integer, - \`parent_id\` integer NOT NULL, - \`path\` text NOT NULL, - \`users_id\` integer, - FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_locked_documents\`(\`id\`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade - ); - `); - await payload.db.drizzle.run(sql`CREATE TABLE \`payload_preferences\` ( - \`id\` integer PRIMARY KEY NOT NULL, - \`key\` text, - \`value\` text, - \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, - \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL - ); - `); - await payload.db.drizzle.run(sql`CREATE TABLE \`payload_preferences_rels\` ( - \`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, - \`order\` integer, - \`parent_id\` integer NOT NULL, - \`path\` text NOT NULL, - \`users_id\` integer, - FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_preferences\`(\`id\`) ON UPDATE no action ON DELETE cascade, - FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade - ); - `); - await payload.db.drizzle.run(sql`CREATE TABLE \`payload_migrations\` ( - \`id\` integer PRIMARY KEY NOT NULL, - \`name\` text, - \`batch\` numeric, - \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, - \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL - ); - `); - await payload.db.drizzle.run( - sql`CREATE INDEX \`users_sub_idx\` ON \`users\` (\`sub\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`users_created_at_idx\` ON \`users\` (\`created_at\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_locked_documents_global_slug_idx\` ON \`payload_locked_documents\` (\`global_slug\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_locked_documents_created_at_idx\` ON \`payload_locked_documents\` (\`created_at\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_locked_documents_rels_order_idx\` ON \`payload_locked_documents_rels\` (\`order\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_locked_documents_rels_parent_idx\` ON \`payload_locked_documents_rels\` (\`parent_id\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_locked_documents_rels_path_idx\` ON \`payload_locked_documents_rels\` (\`path\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_preferences_key_idx\` ON \`payload_preferences\` (\`key\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_preferences_created_at_idx\` ON \`payload_preferences\` (\`created_at\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_preferences_rels_order_idx\` ON \`payload_preferences_rels\` (\`order\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_preferences_rels_parent_idx\` ON \`payload_preferences_rels\` (\`parent_id\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_preferences_rels_path_idx\` ON \`payload_preferences_rels\` (\`path\`);`, - ); - await payload.db.drizzle.run( - sql`CREATE INDEX \`payload_migrations_created_at_idx\` ON \`payload_migrations\` (\`created_at\`);`, - ); -} - -export async function down({ payload, req }: MigrateDownArgs): Promise { - await payload.db.drizzle.run(sql`DROP TABLE \`users\`;`); - await payload.db.drizzle.run(sql`DROP TABLE \`payload_locked_documents\`;`); - await payload.db.drizzle.run( - sql`DROP TABLE \`payload_locked_documents_rels\`;`, - ); - await payload.db.drizzle.run(sql`DROP TABLE \`payload_preferences\`;`); - await payload.db.drizzle.run(sql`DROP TABLE \`payload_preferences_rels\`;`); - await payload.db.drizzle.run(sql`DROP TABLE \`payload_migrations\`;`); -} diff --git a/dev/src/migrations/20241017_034629.json b/dev/src/migrations/20250224_061957.json similarity index 64% rename from dev/src/migrations/20241017_034629.json rename to dev/src/migrations/20250224_061957.json index 292a237..e2f6966 100644 --- a/dev/src/migrations/20241017_034629.json +++ b/dev/src/migrations/20250224_061957.json @@ -2,6 +2,105 @@ "version": "6", "dialect": "sqlite", "tables": { + "local_users": { + "name": "local_users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "lock_until": { + "name": "lock_until", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "local_users_updated_at_idx": { + "name": "local_users_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "local_users_created_at_idx": { + "name": "local_users_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "local_users_email_idx": { + "name": "local_users_email_idx", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, "users": { "name": "users", "columns": { @@ -49,6 +148,11 @@ "columns": ["sub"], "isUnique": false }, + "users_updated_at_idx": { + "name": "users_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, "users_created_at_idx": { "name": "users_created_at_idx", "columns": ["created_at"], @@ -57,7 +161,8 @@ }, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} }, "payload_locked_documents": { "name": "payload_locked_documents", @@ -99,6 +204,11 @@ "columns": ["global_slug"], "isUnique": false }, + "payload_locked_documents_updated_at_idx": { + "name": "payload_locked_documents_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, "payload_locked_documents_created_at_idx": { "name": "payload_locked_documents_created_at_idx", "columns": ["created_at"], @@ -107,7 +217,8 @@ }, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} }, "payload_locked_documents_rels": { "name": "payload_locked_documents_rels", @@ -117,7 +228,7 @@ "type": "integer", "primaryKey": true, "notNull": true, - "autoincrement": true + "autoincrement": false }, "order": { "name": "order", @@ -140,6 +251,13 @@ "notNull": true, "autoincrement": false }, + "local_users_id": { + "name": "local_users_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "users_id": { "name": "users_id", "type": "integer", @@ -163,6 +281,16 @@ "name": "payload_locked_documents_rels_path_idx", "columns": ["path"], "isUnique": false + }, + "payload_locked_documents_rels_local_users_id_idx": { + "name": "payload_locked_documents_rels_local_users_id_idx", + "columns": ["local_users_id"], + "isUnique": false + }, + "payload_locked_documents_rels_users_id_idx": { + "name": "payload_locked_documents_rels_users_id_idx", + "columns": ["users_id"], + "isUnique": false } }, "foreignKeys": { @@ -175,6 +303,15 @@ "onDelete": "cascade", "onUpdate": "no action" }, + "payload_locked_documents_rels_local_users_fk": { + "name": "payload_locked_documents_rels_local_users_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "local_users", + "columnsFrom": ["local_users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, "payload_locked_documents_rels_users_fk": { "name": "payload_locked_documents_rels_users_fk", "tableFrom": "payload_locked_documents_rels", @@ -186,7 +323,8 @@ } }, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} }, "payload_preferences": { "name": "payload_preferences", @@ -235,6 +373,11 @@ "columns": ["key"], "isUnique": false }, + "payload_preferences_updated_at_idx": { + "name": "payload_preferences_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, "payload_preferences_created_at_idx": { "name": "payload_preferences_created_at_idx", "columns": ["created_at"], @@ -243,7 +386,8 @@ }, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} }, "payload_preferences_rels": { "name": "payload_preferences_rels", @@ -253,7 +397,7 @@ "type": "integer", "primaryKey": true, "notNull": true, - "autoincrement": true + "autoincrement": false }, "order": { "name": "order", @@ -276,6 +420,13 @@ "notNull": true, "autoincrement": false }, + "local_users_id": { + "name": "local_users_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "users_id": { "name": "users_id", "type": "integer", @@ -299,6 +450,16 @@ "name": "payload_preferences_rels_path_idx", "columns": ["path"], "isUnique": false + }, + "payload_preferences_rels_local_users_id_idx": { + "name": "payload_preferences_rels_local_users_id_idx", + "columns": ["local_users_id"], + "isUnique": false + }, + "payload_preferences_rels_users_id_idx": { + "name": "payload_preferences_rels_users_id_idx", + "columns": ["users_id"], + "isUnique": false } }, "foreignKeys": { @@ -311,6 +472,15 @@ "onDelete": "cascade", "onUpdate": "no action" }, + "payload_preferences_rels_local_users_fk": { + "name": "payload_preferences_rels_local_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "local_users", + "columnsFrom": ["local_users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, "payload_preferences_rels_users_fk": { "name": "payload_preferences_rels_users_fk", "tableFrom": "payload_preferences_rels", @@ -322,7 +492,8 @@ } }, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} }, "payload_migrations": { "name": "payload_migrations", @@ -366,6 +537,11 @@ } }, "indexes": { + "payload_migrations_updated_at_idx": { + "name": "payload_migrations_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, "payload_migrations_created_at_idx": { "name": "payload_migrations_created_at_idx", "columns": ["created_at"], @@ -374,9 +550,11 @@ }, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": {}, + "checkConstraints": {} } }, + "views": {}, "enums": {}, "_meta": { "tables": {}, @@ -385,6 +563,6 @@ "internal": { "indexes": {} }, - "id": "cedd7049-0d47-4c51-8734-2c3d70ac3c4d", + "id": "0653c2d0-b1b7-43ca-85ed-c2d2ca48aef3", "prevId": "00000000-0000-0000-0000-000000000000" } diff --git a/dev/src/migrations/20250224_061957.ts b/dev/src/migrations/20250224_061957.ts new file mode 100644 index 0000000..fcf4dc4 --- /dev/null +++ b/dev/src/migrations/20250224_061957.ts @@ -0,0 +1,156 @@ +import { MigrateDownArgs, MigrateUpArgs, sql } from "@payloadcms/db-sqlite"; + +export async function up({ db, payload, req }: MigrateUpArgs): Promise { + await db.run(sql`CREATE TABLE \`local_users\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`email\` text NOT NULL, + \`reset_password_token\` text, + \`reset_password_expiration\` text, + \`salt\` text, + \`hash\` text, + \`login_attempts\` numeric DEFAULT 0, + \`lock_until\` text + ); + `); + await db.run( + sql`CREATE INDEX \`local_users_updated_at_idx\` ON \`local_users\` (\`updated_at\`);`, + ); + await db.run( + sql`CREATE INDEX \`local_users_created_at_idx\` ON \`local_users\` (\`created_at\`);`, + ); + await db.run( + sql`CREATE UNIQUE INDEX \`local_users_email_idx\` ON \`local_users\` (\`email\`);`, + ); + await db.run(sql`CREATE TABLE \`users\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`email\` text NOT NULL, + \`sub\` text, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `); + await db.run(sql`CREATE INDEX \`users_sub_idx\` ON \`users\` (\`sub\`);`); + await db.run( + sql`CREATE INDEX \`users_updated_at_idx\` ON \`users\` (\`updated_at\`);`, + ); + await db.run( + sql`CREATE INDEX \`users_created_at_idx\` ON \`users\` (\`created_at\`);`, + ); + await db.run(sql`CREATE TABLE \`payload_locked_documents\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`global_slug\` text, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_global_slug_idx\` ON \`payload_locked_documents\` (\`global_slug\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_updated_at_idx\` ON \`payload_locked_documents\` (\`updated_at\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_created_at_idx\` ON \`payload_locked_documents\` (\`created_at\`);`, + ); + await db.run(sql`CREATE TABLE \`payload_locked_documents_rels\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order\` integer, + \`parent_id\` integer NOT NULL, + \`path\` text NOT NULL, + \`local_users_id\` integer, + \`users_id\` integer, + FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_locked_documents\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`local_users_id\`) REFERENCES \`local_users\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade + ); + `); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_order_idx\` ON \`payload_locked_documents_rels\` (\`order\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_parent_idx\` ON \`payload_locked_documents_rels\` (\`parent_id\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_path_idx\` ON \`payload_locked_documents_rels\` (\`path\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_local_users_id_idx\` ON \`payload_locked_documents_rels\` (\`local_users_id\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_locked_documents_rels_users_id_idx\` ON \`payload_locked_documents_rels\` (\`users_id\`);`, + ); + await db.run(sql`CREATE TABLE \`payload_preferences\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`key\` text, + \`value\` text, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `); + await db.run( + sql`CREATE INDEX \`payload_preferences_key_idx\` ON \`payload_preferences\` (\`key\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_updated_at_idx\` ON \`payload_preferences\` (\`updated_at\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_created_at_idx\` ON \`payload_preferences\` (\`created_at\`);`, + ); + await db.run(sql`CREATE TABLE \`payload_preferences_rels\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`order\` integer, + \`parent_id\` integer NOT NULL, + \`path\` text NOT NULL, + \`local_users_id\` integer, + \`users_id\` integer, + FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_preferences\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`local_users_id\`) REFERENCES \`local_users\`(\`id\`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade + ); + `); + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_order_idx\` ON \`payload_preferences_rels\` (\`order\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_parent_idx\` ON \`payload_preferences_rels\` (\`parent_id\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_path_idx\` ON \`payload_preferences_rels\` (\`path\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_local_users_id_idx\` ON \`payload_preferences_rels\` (\`local_users_id\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_preferences_rels_users_id_idx\` ON \`payload_preferences_rels\` (\`users_id\`);`, + ); + await db.run(sql`CREATE TABLE \`payload_migrations\` ( + \`id\` integer PRIMARY KEY NOT NULL, + \`name\` text, + \`batch\` numeric, + \`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + \`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `); + await db.run( + sql`CREATE INDEX \`payload_migrations_updated_at_idx\` ON \`payload_migrations\` (\`updated_at\`);`, + ); + await db.run( + sql`CREATE INDEX \`payload_migrations_created_at_idx\` ON \`payload_migrations\` (\`created_at\`);`, + ); +} + +export async function down({ + db, + payload, + req, +}: MigrateDownArgs): Promise { + await db.run(sql`DROP TABLE \`local_users\`;`); + await db.run(sql`DROP TABLE \`users\`;`); + await db.run(sql`DROP TABLE \`payload_locked_documents\`;`); + await db.run(sql`DROP TABLE \`payload_locked_documents_rels\`;`); + await db.run(sql`DROP TABLE \`payload_preferences\`;`); + await db.run(sql`DROP TABLE \`payload_preferences_rels\`;`); + await db.run(sql`DROP TABLE \`payload_migrations\`;`); +} diff --git a/dev/src/migrations/index.ts b/dev/src/migrations/index.ts index a94db8a..ca8a1ac 100644 --- a/dev/src/migrations/index.ts +++ b/dev/src/migrations/index.ts @@ -1,9 +1,9 @@ -import * as migration_20241017_034629 from "./20241017_034629"; +import * as migration_20250224_061957 from './20250224_061957'; export const migrations = [ { - up: migration_20241017_034629.up, - down: migration_20241017_034629.down, - name: "20241017_034629", + up: migration_20250224_061957.up, + down: migration_20250224_061957.down, + name: '20250224_061957' }, ]; diff --git a/dev/src/payload-types.ts b/dev/src/payload-types.ts index fa1a9a7..5ece452 100644 --- a/dev/src/payload-types.ts +++ b/dev/src/payload-types.ts @@ -62,10 +62,12 @@ export type SupportedTimezones = export interface Config { auth: { + 'local-users': LocalUserAuthOperations; users: UserAuthOperations; }; blocks: {}; collections: { + 'local-users': LocalUser; users: User; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; @@ -73,6 +75,7 @@ export interface Config { }; collectionsJoins: {}; collectionsSelect: { + 'local-users': LocalUsersSelect | LocalUsersSelect; users: UsersSelect | UsersSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; @@ -84,14 +87,36 @@ export interface Config { globals: {}; globalsSelect: {}; locale: null; - user: User & { - collection: 'users'; - }; + user: + | (LocalUser & { + collection: 'local-users'; + }) + | (User & { + collection: 'users'; + }); jobs: { tasks: unknown; workflows: unknown; }; } +export interface LocalUserAuthOperations { + forgotPassword: { + email: string; + password: string; + }; + login: { + email: string; + password: string; + }; + registerFirstUser: { + email: string; + password: string; + }; + unlock: { + email: string; + password: string; + }; +} export interface UserAuthOperations { forgotPassword: { email: string; @@ -110,6 +135,23 @@ export interface UserAuthOperations { password: string; }; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "local-users". + */ +export interface LocalUser { + id: number; + updatedAt: string; + createdAt: string; + email: string; + resetPasswordToken?: string | null; + resetPasswordExpiration?: string | null; + salt?: string | null; + hash?: string | null; + loginAttempts?: number | null; + lockUntil?: string | null; + password?: string | null; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users". @@ -127,15 +169,25 @@ export interface User { */ export interface PayloadLockedDocument { id: number; - document?: { - relationTo: 'users'; - value: number | User; - } | null; + document?: + | ({ + relationTo: 'local-users'; + value: number | LocalUser; + } | null) + | ({ + relationTo: 'users'; + value: number | User; + } | null); globalSlug?: string | null; - user: { - relationTo: 'users'; - value: number | User; - }; + user: + | { + relationTo: 'local-users'; + value: number | LocalUser; + } + | { + relationTo: 'users'; + value: number | User; + }; updatedAt: string; createdAt: string; } @@ -145,10 +197,15 @@ export interface PayloadLockedDocument { */ export interface PayloadPreference { id: number; - user: { - relationTo: 'users'; - value: number | User; - }; + user: + | { + relationTo: 'local-users'; + value: number | LocalUser; + } + | { + relationTo: 'users'; + value: number | User; + }; key?: string | null; value?: | { @@ -173,6 +230,21 @@ export interface PayloadMigration { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "local-users_select". + */ +export interface LocalUsersSelect { + updatedAt?: T; + createdAt?: T; + email?: T; + resetPasswordToken?: T; + resetPasswordExpiration?: T; + salt?: T; + hash?: T; + loginAttempts?: T; + lockUntil?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users_select". diff --git a/dev/src/payload.config.ts b/dev/src/payload.config.ts index 0613e10..3cbdea4 100644 --- a/dev/src/payload.config.ts +++ b/dev/src/payload.config.ts @@ -6,6 +6,7 @@ import sharp from "sharp"; import { fileURLToPath } from "url"; import { googleOAuth } from "../../examples/google"; import { zitadelOAuth } from "../../examples/zitadel"; +import LocalUsers from "./collections/LocalUsers"; import Users from "./collections/Users"; import { migrations } from "./migrations"; @@ -32,7 +33,7 @@ export default buildConfig({ prodMigrations: migrations, }), editor: lexicalEditor({}), - collections: [Users], + collections: [Users, LocalUsers], typescript: { outputFile: path.resolve(dirname, "payload-types.ts") }, plugins: [googleOAuth, zitadelOAuth], sharp, diff --git a/package.json b/package.json index 873535e..619b2d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload-oauth2", - "version": "1.0.12", + "version": "1.0.13", "type": "module", "homepage:": "https://github.com/WilsonLe/payload-oauth2", "repository": "https://github.com/WilsonLe/payload-oauth2", diff --git a/src/auth-strategy.ts b/src/auth-strategy.ts index db92829..6927f37 100644 --- a/src/auth-strategy.ts +++ b/src/auth-strategy.ts @@ -16,87 +16,92 @@ export const createAuthStrategy = ( const authStrategy: AuthStrategy = { name: pluginOptions.strategyName, authenticate: async ({ headers, payload }): Promise => { - const cookie = parseCookies(headers); - const token = cookie.get(`${payload.config.cookiePrefix}-token`); - if (!token) return { user: null }; - - let jwtUser: JWTPayload | null = null; try { - const secret = crypto - .createHash("sha256") - .update(payload.config.secret) - .digest("hex") - .slice(0, 32); - - const { payload: verifiedPayload } = await jwtVerify( - token, - new TextEncoder().encode(secret), - { algorithms: ["HS256"] }, - ); - jwtUser = verifiedPayload; - } catch (e: any) { - // Handle token expiration - if (e.code === "ERR_JWT_EXPIRED") return { user: null }; - throw e; - } - if (!jwtUser) return { user: null }; + const cookie = parseCookies(headers); + const token = cookie.get(`${payload.config.cookiePrefix}-token`); + if (!token) return { user: null }; - // Find the user by email from the verified jwt token - // coerce userCollection to CollectionSlug because it is already checked - // in `modify-auth-collection.ts` that it is a valud collection slug - const userCollection = ((typeof jwtUser.collection === "string" && - jwtUser.collection) || - pluginOptions.authCollection || - "users") as CollectionSlug; - let user: User | null = null; + let jwtUser: JWTPayload | null = null; + try { + const secret = crypto + .createHash("sha256") + .update(payload.config.secret) + .digest("hex") + .slice(0, 32); - if (pluginOptions.useEmailAsIdentity) { - if (typeof jwtUser.email !== "string") { - payload.logger.warn( - "Using email as identity but no email is found in jwt token", + const { payload: verifiedPayload } = await jwtVerify( + token, + new TextEncoder().encode(secret), + { algorithms: ["HS256"] }, ); - return { user: null }; + jwtUser = verifiedPayload; + } catch (e: any) { + // Handle token expiration + if (e.code === "ERR_JWT_EXPIRED") return { user: null }; + throw e; } - const usersQuery = await payload.find({ - collection: userCollection, - where: { email: { equals: jwtUser.email } }, - }); - if (usersQuery.docs.length === 0) { - // coerce to User because `userCollection` is a valid auth collection, checked by `modify-auth-collection.ts` already - user = (await payload.create({ + if (!jwtUser) return { user: null }; + + // Find the user by email from the verified jwt token + // coerce userCollection to CollectionSlug because it is already checked + // in `modify-auth-collection.ts` that it is a valud collection slug + const userCollection = ((typeof jwtUser.collection === "string" && + jwtUser.collection) || + pluginOptions.authCollection || + "users") as CollectionSlug; + let user: User | null = null; + + if (pluginOptions.useEmailAsIdentity) { + if (typeof jwtUser.email !== "string") { + payload.logger.warn( + "Using email as identity but no email is found in jwt token", + ); + return { user: null }; + } + const usersQuery = await payload.find({ collection: userCollection, - data: jwtUser as any, - })) as unknown as User; + where: { email: { equals: jwtUser.email } }, + }); + if (usersQuery.docs.length === 0) { + // coerce to User because `userCollection` is a valid auth collection, checked by `modify-auth-collection.ts` already + user = (await payload.create({ + collection: userCollection, + data: jwtUser as any, + })) as unknown as User; + } else { + // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; + user = usersQuery.docs[0] as unknown as User; + } } else { - // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; - user = usersQuery.docs[0] as unknown as User; - } - } else { - if (typeof jwtUser[subFieldName] !== "string") { - payload.logger.warn( - `No ${subFieldName} found in jwt token. Make sure the jwt token contains the ${subFieldName} field`, - ); - return { user: null }; - } - const usersQuery = await payload.find({ - collection: userCollection, - where: { [subFieldName]: { equals: jwtUser[subFieldName] } }, - }); - if (usersQuery.docs.length === 0) { - // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; - user = (await payload.create({ + if (typeof jwtUser[subFieldName] !== "string") { + payload.logger.warn( + `No ${subFieldName} found in jwt token. Make sure the jwt token contains the ${subFieldName} field`, + ); + return { user: null }; + } + const usersQuery = await payload.find({ collection: userCollection, - data: jwtUser as any, - })) as unknown as User; - } else { - // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; - user = usersQuery.docs[0] as unknown as User; + where: { [subFieldName]: { equals: jwtUser[subFieldName] } }, + }); + if (usersQuery.docs.length === 0) { + // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; + user = (await payload.create({ + collection: userCollection, + data: jwtUser as any, + })) as unknown as User; + } else { + // coerce to User because payload warns that some collection may not have property `collection` - i.e. `PayloadMigration; + user = usersQuery.docs[0] as unknown as User; + } } - } - user.collection = userCollection; + user.collection = userCollection; - // Return the user object - return { user }; + // Return the user object + return { user }; + } catch (e) { + payload.logger.error(e); + return { user: null }; + } }, }; return authStrategy;