From 897f906631119c7265ed841fe1475a53587392a1 Mon Sep 17 00:00:00 2001 From: javayhu Date: Sun, 23 Feb 2025 23:23:01 +0800 Subject: [PATCH] feat: add better auth plugin admin to support user role and user ban --- drizzle/0001_shocking_wild_pack.sql | 5 + drizzle/meta/0001_snapshot.json | 349 ++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/db/schema.ts | 9 +- src/lib/auth-client.ts | 4 + src/lib/auth.ts | 6 + 6 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 drizzle/0001_shocking_wild_pack.sql create mode 100644 drizzle/meta/0001_snapshot.json diff --git a/drizzle/0001_shocking_wild_pack.sql b/drizzle/0001_shocking_wild_pack.sql new file mode 100644 index 0000000..1e2c792 --- /dev/null +++ b/drizzle/0001_shocking_wild_pack.sql @@ -0,0 +1,5 @@ +ALTER TABLE "session" ADD COLUMN "impersonated_by" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "role" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "banned" boolean;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "ban_reason" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "ban_expires" timestamp; \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..1e41806 --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,349 @@ +{ + "id": "53387f51-ed0d-4c44-b8ed-5af936eef75e", + "prevId": "46c47915-d6d4-465f-9006-c016fb9d0c1f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e68f37d..9c8b329 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1740226999095, "tag": "0000_secret_silver_centurion", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1740323860731, + "tag": "0001_shocking_wild_pack", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema.ts b/src/db/schema.ts index e028842..58aaf92 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -7,7 +7,11 @@ export const user = pgTable("user", { emailVerified: boolean('email_verified').notNull(), image: text('image'), createdAt: timestamp('created_at').notNull(), - updatedAt: timestamp('updated_at').notNull() + updatedAt: timestamp('updated_at').notNull(), + role: text('role'), + banned: boolean('banned'), + banReason: text('ban_reason'), + banExpires: timestamp('ban_expires') }); export const session = pgTable("session", { @@ -18,7 +22,8 @@ export const session = pgTable("session", { updatedAt: timestamp('updated_at').notNull(), ipAddress: text('ip_address'), userAgent: text('user_agent'), - userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }) + userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }), + impersonatedBy: text('impersonated_by') }); export const account = pgTable("account", { diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index 9d614e2..e1e1f1d 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -1,5 +1,9 @@ import { createAuthClient } from "better-auth/react"; +import { adminClient } from "better-auth/client/plugins"; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_APP_URL!, + plugins: [ + adminClient(), + ] }) diff --git a/src/lib/auth.ts b/src/lib/auth.ts index a550e3b..eed141a 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -4,6 +4,7 @@ import db from "@/db/index"; import { user, session, account, verification } from "@/db/schema"; import { siteConfig } from "@/config/site"; import { resend } from "@/lib/email/resend"; +import { admin } from "better-auth/plugins"; const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev"; @@ -67,4 +68,9 @@ export const auth = betterAuth({ clientSecret: process.env.GITHUB_CLIENT_SECRET!, } }, + plugins: [ + // https://www.better-auth.com/docs/plugins/admin + // user role and user banned status + admin(), + ] });