mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
User role access.
This commit is contained in:
@@ -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;
|
||||||
@@ -59,7 +59,7 @@ model User {
|
|||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime? @map("email_verified")
|
emailVerified DateTime? @map("email_verified")
|
||||||
image String?
|
image String?
|
||||||
role String?
|
role Role
|
||||||
password String?
|
password String?
|
||||||
authMethod String? @map("auth_method")
|
authMethod String? @map("auth_method")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
@@ -71,6 +71,12 @@ model User {
|
|||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
pending
|
||||||
|
user
|
||||||
|
admin
|
||||||
|
}
|
||||||
|
|
||||||
model Agent {
|
model Agent {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
slug String @unique
|
slug String @unique
|
||||||
|
|||||||
+41
-21
@@ -5,9 +5,7 @@ import Credentials from "next-auth/providers/credentials";
|
|||||||
import {env} from "@/env.mjs";
|
import {env} from "@/env.mjs";
|
||||||
import GoogleProvider from "next-auth/providers/google";
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
class InvalidLoginError extends CredentialsSignin {
|
|
||||||
code = "Invalid identifier or password"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
@@ -32,7 +30,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!user) {
|
if (!user || user.role === "pending") {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const isValid = await argon2.verify(user.password, credentials.password)
|
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,
|
clientId: env.AUTH_GOOGLE_ID,
|
||||||
clientSecret: env.AUTH_GOOGLE_SECRET,
|
clientSecret: env.AUTH_GOOGLE_SECRET,
|
||||||
allowDangerousEmailAccountLinking: true,
|
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 {
|
return {
|
||||||
// role: profile.email === "soluce.technologies@gmail.com" ? "admin" : "user",
|
role: role,
|
||||||
name: profile.name,
|
name: profile.name,
|
||||||
email: profile.email,
|
email: profile.email,
|
||||||
image: profile.picture,
|
image: profile.picture,
|
||||||
|
email_verified: profile.email_verified,
|
||||||
authMethod: "google",
|
authMethod: "google",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -81,26 +89,38 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
session.user = token;
|
session.user = token;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
async signIn({ account, user }) {
|
async signIn({ account, user, profile }) {
|
||||||
console.log(account)
|
const existingUser = await prisma.user.findFirst({
|
||||||
// const authMethod = account.provider === 'credentials' ? 'credentials' : 'oauth';
|
where: { email: user.email },
|
||||||
user = await prisma.user.findFirst({
|
});
|
||||||
where: {
|
|
||||||
email: user.email,
|
if (!existingUser) {
|
||||||
}
|
// Create a new user with a pending role
|
||||||
})
|
await prisma.user.create({
|
||||||
if (!user) {
|
data: {
|
||||||
return true
|
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({
|
await prisma.user.update({
|
||||||
where: { id: user.id },
|
where: { id: existingUser.id },
|
||||||
data: {
|
data: {
|
||||||
authMethod: account.provider
|
authMethod: account.provider,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
|
return true; // Allow login
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,20 @@ export const registerUserAction = action
|
|||||||
const user = await prisma.user.findUnique({ where: { email: parsedInput.email } });
|
const user = await prisma.user.findUnique({ where: { email: parsedInput.email } });
|
||||||
console.log(user);
|
console.log(user);
|
||||||
if (!user && parsedInput.password === parsedInput.confirmPassword) {
|
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({
|
const new_user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
name: parsedInput.name,
|
name: parsedInput.name,
|
||||||
email: parsedInput.email,
|
email: parsedInput.email,
|
||||||
password: await hashPassword(parsedInput.password),
|
password: await hashPassword(parsedInput.password),
|
||||||
|
role: role
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
|
|
||||||
export const UserSchema = z.object({
|
export const UserSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string().optional(),
|
||||||
email: z.string(),
|
email: z.string().optional(),
|
||||||
|
role: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type UserType = z.infer<typeof UserSchema>;
|
export type UserType = z.infer<typeof UserSchema>;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||||
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
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 {User, Settings} from "@prisma/client";
|
||||||
import {backupColumns} from "@/features/backup/columns";
|
import {backupColumns} from "@/features/backup/columns";
|
||||||
import {SettingsEmailTab} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/SettingsEmailTab";
|
import {SettingsEmailTab} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/SettingsEmailTab";
|
||||||
|
|||||||
@@ -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<User>[] = [
|
|
||||||
{
|
|
||||||
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 <Badge variant="outline">{row.getValue("authMethod")}</Badge>
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
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";
|
import {User} from "@prisma/client";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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<User>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "role",
|
||||||
|
header: "Role",
|
||||||
|
cell: ({row}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const [role, setRole] = useState<string>(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 <Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => handleUpdateRole()}
|
||||||
|
variant="outline">{role}</Badge>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 <Badge variant="outline">{row.getValue("authMethod")}</Badge>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user