From e3fe6d1c80b06753496f820f07de0636ad3cdc94 Mon Sep 17 00:00:00 2001 From: charles-gauthereau Date: Sat, 23 Nov 2024 16:05:00 +0100 Subject: [PATCH] User role access. --- .../20241123102722_2024_11_23/migration.sql | 12 ++++ prisma/schema.prisma | 8 ++- src/auth/auth.ts | 62 +++++++++++------ .../RegisterForm/register-form.action.ts | 9 +++ .../Profile/UserForm/user-form.schema.ts | 5 +- .../Settings/SettingsTabs/SettingsTabs.tsx | 2 +- .../Settings/SettingsTabs/columns-users.tsx | 30 -------- .../SettingsUsersTab/SettingsUsersTab.tsx | 2 +- .../SettingsUsersTab/columns-users.tsx | 68 +++++++++++++++++++ .../settings-user-tab.action.ts | 2 + 10 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 prisma/migrations/20241123102722_2024_11_23/migration.sql delete mode 100644 src/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users.tsx create mode 100644 src/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users.tsx create mode 100644 src/components/wrappers/Dashboard/Settings/SettingsUsersTab/settings-user-tab.action.ts diff --git a/prisma/migrations/20241123102722_2024_11_23/migration.sql b/prisma/migrations/20241123102722_2024_11_23/migration.sql new file mode 100644 index 00000000..7daa2d85 --- /dev/null +++ b/prisma/migrations/20241123102722_2024_11_23/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `role` to the `users` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('pending', 'user', 'admin'); + +-- AlterTable +ALTER TABLE "users" DROP COLUMN "role", +ADD COLUMN "role" "Role" NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 02ce26ce..659af2a8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -59,7 +59,7 @@ model User { email String? @unique emailVerified DateTime? @map("email_verified") image String? - role String? + role Role password String? authMethod String? @map("auth_method") createdAt DateTime @default(now()) @map("created_at") @@ -71,6 +71,12 @@ model User { @@map("users") } +enum Role { + pending + user + admin +} + model Agent { id String @id @default(cuid()) slug String @unique diff --git a/src/auth/auth.ts b/src/auth/auth.ts index f5999e2a..f3d59169 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -5,9 +5,7 @@ import Credentials from "next-auth/providers/credentials"; import {env} from "@/env.mjs"; import GoogleProvider from "next-auth/providers/google"; -class InvalidLoginError extends CredentialsSignin { - code = "Invalid identifier or password" -} + export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ adapter: PrismaAdapter(prisma), @@ -32,7 +30,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ email: credentials.email, } }) - if (!user) { + if (!user || user.role === "pending") { return null } const isValid = await argon2.verify(user.password, credentials.password) @@ -46,12 +44,22 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ clientId: env.AUTH_GOOGLE_ID, clientSecret: env.AUTH_GOOGLE_SECRET, allowDangerousEmailAccountLinking: true, - profile(profile) { + async profile(profile) { + + const users = await prisma.user.findMany({ + where: { + deleted: {not: true}, + } + }) + const role = users.length > 0 ? "pending" : "admin" + + return { - // role: profile.email === "soluce.technologies@gmail.com" ? "admin" : "user", + role: role, name: profile.name, email: profile.email, image: profile.picture, + email_verified: profile.email_verified, authMethod: "google", } }, @@ -81,26 +89,38 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({ session.user = token; return session; }, - async signIn({ account, user }) { - console.log(account) - // const authMethod = account.provider === 'credentials' ? 'credentials' : 'oauth'; - user = await prisma.user.findFirst({ - where: { - email: user.email, - } - }) - if (!user) { - return true + async signIn({ account, user, profile }) { + const existingUser = await prisma.user.findFirst({ + where: { email: user.email }, + }); + + if (!existingUser) { + // Create a new user with a pending role + await prisma.user.create({ + data: { + email: user.email, + name: user.name, + image: user.image, + role: "pending", + authMethod: account.provider, + }, + }); + return false; // Prevent login if role is pending } - console.log(user) - // Update the user in the database with the auth method + + if (existingUser.role === "pending") { + return false; // Prevent login if role is pending + } + + // Update auth method if user exists await prisma.user.update({ - where: { id: user.id }, + where: { id: existingUser.id }, data: { - authMethod: account.provider + authMethod: account.provider, }, }); - return true; + + return true; // Allow login }, } diff --git a/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts b/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts index 78fb480d..a7e18805 100644 --- a/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts +++ b/src/components/wrappers/Auth/Register/RegisterForm/register-form.action.ts @@ -11,11 +11,20 @@ export const registerUserAction = action const user = await prisma.user.findUnique({ where: { email: parsedInput.email } }); console.log(user); if (!user && parsedInput.password === parsedInput.confirmPassword) { + + const users = await prisma.user.findMany({ + where:{ + deleted: { not: true }, + } + }) + const role = users.length > 0 ? "pending" : "admin" + const new_user = await prisma.user.create({ data: { name: parsedInput.name, email: parsedInput.email, password: await hashPassword(parsedInput.password), + role: role }, }); return { diff --git a/src/components/wrappers/Dashboard/Profile/UserForm/user-form.schema.ts b/src/components/wrappers/Dashboard/Profile/UserForm/user-form.schema.ts index ab91c203..082be886 100644 --- a/src/components/wrappers/Dashboard/Profile/UserForm/user-form.schema.ts +++ b/src/components/wrappers/Dashboard/Profile/UserForm/user-form.schema.ts @@ -1,8 +1,9 @@ import {z} from "zod"; export const UserSchema = z.object({ - name: z.string(), - email: z.string(), + name: z.string().optional(), + email: z.string().optional(), + role: z.string().optional(), }); export type UserType = z.infer; diff --git a/src/components/wrappers/Dashboard/Settings/SettingsTabs/SettingsTabs.tsx b/src/components/wrappers/Dashboard/Settings/SettingsTabs/SettingsTabs.tsx index 0a972538..9beba942 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsTabs/SettingsTabs.tsx +++ b/src/components/wrappers/Dashboard/Settings/SettingsTabs/SettingsTabs.tsx @@ -1,7 +1,7 @@ "use client" import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination"; -import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users"; +import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users"; import {User, Settings} from "@prisma/client"; import {backupColumns} from "@/features/backup/columns"; import {SettingsEmailTab} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/SettingsEmailTab"; diff --git a/src/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users.tsx b/src/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users.tsx deleted file mode 100644 index f882139c..00000000 --- a/src/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client" - -import {ColumnDef} from "@tanstack/react-table" -import {Badge} from "@/components/ui/badge"; -import {User} from "@prisma/client"; - -export const usersColumns: ColumnDef[] = [ - { - accessorKey: "name", - header: "Name" - }, - { - accessorKey: "email", - header: "Email" - }, - { - accessorKey: "createdAt", - header: "Created At", - cell: ({row}) => { - return new Date(row.getValue("createdAt")).toLocaleString("fr-FR"); - }, - }, - { - accessorKey: "authMethod", - header: "Method", - cell: ({row}) => { - return {row.getValue("authMethod")} - }, - } -] \ No newline at end of file diff --git a/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/SettingsUsersTab.tsx b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/SettingsUsersTab.tsx index e8a23667..cd6cc802 100644 --- a/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/SettingsUsersTab.tsx +++ b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/SettingsUsersTab.tsx @@ -1,5 +1,5 @@ import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination"; -import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users"; +import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users"; import {User} from "@prisma/client"; diff --git a/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users.tsx b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users.tsx new file mode 100644 index 00000000..baa4ff4a --- /dev/null +++ b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/columns-users.tsx @@ -0,0 +1,68 @@ +"use client" + +import {ColumnDef} from "@tanstack/react-table" +import {Badge} from "@/components/ui/badge"; +import {User} from "@prisma/client"; +import {updateUserAction} from "@/components/wrappers/Dashboard/Profile/UserForm/user-form.action"; +import {useMutation} from "@tanstack/react-query"; +import {toast} from "sonner"; +import {useRouter} from "next/navigation"; +import {useState} from "react"; + +export const usersColumns: ColumnDef[] = [ + { + accessorKey: "role", + header: "Role", + cell: ({row}) => { + const router = useRouter(); + const [role, setRole] = useState(row.getValue("role")) + + const updateMutation = useMutation({ + mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}), + onSuccess: () => { + toast.success(`User updated successfully.`); + router.refresh() + + }, + onError: () => { + toast.error(`An error occurred while updating user information.`); + }, + }); + + const handleUpdateRole = async () => { + const nextRole = role === "admin" ? "pending" + : role === "pending" ? "user" + : "admin"; + setRole(nextRole); + await updateMutation.mutateAsync() + }; + + return handleUpdateRole()} + variant="outline">{role} + }, + }, + { + accessorKey: "name", + header: "Name" + }, + { + accessorKey: "email", + header: "Email" + }, + { + accessorKey: "createdAt", + header: "Created At", + cell: ({row}) => { + return new Date(row.getValue("createdAt")).toLocaleString("fr-FR"); + }, + }, + { + accessorKey: "authMethod", + header: "Method", + cell: ({row}) => { + return {row.getValue("authMethod")} + }, + } +] \ No newline at end of file diff --git a/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/settings-user-tab.action.ts b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/settings-user-tab.action.ts new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/src/components/wrappers/Dashboard/Settings/SettingsUsersTab/settings-user-tab.action.ts @@ -0,0 +1,2 @@ + +