Implement secure user authentication with role-based access control

Integrates user authentication using bcrypt, session management with PostgreSQL, and role-based access control for enhanced security and user management.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 91318273-c764-4fd4-be04-bdc12c38af32
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/ab880d72-1a34-4d36-b5be-9f44df85ed1a.jpg
This commit is contained in:
michaelschal committed 2025-07-09 18:44:20 +00:00
1 parent 42b249e5b0
commit 5c01234fc4
16 files changed
+2384 -827

No files matched your search

+456
View File
@@ -0,0 +1,456 @@
import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useToast } from "@/hooks/use-toast";
import { apiRequest } from "@/lib/queryClient";
import { insertUserSchema, insertStoreSchema, type User, type Store } from "@shared/schema";
import { Users, Building2, Plus, Edit } from "lucide-react";
import { z } from "zod";
const createUserSchema = insertUserSchema.extend({
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Les mots de passe ne correspondent pas",
path: ["confirmPassword"],
});
type CreateUserData = z.infer<typeof createUserSchema>;
export default function Administration() {
const { toast } = useToast();
const queryClient = useQueryClient();
const [isUserDialogOpen, setIsUserDialogOpen] = useState(false);
const [isStoreDialogOpen, setIsStoreDialogOpen] = useState(false);
// Queries
const { data: users = [], isLoading: usersLoading } = useQuery<User[]>({
queryKey: ['/api/admin/users'],
});
const { data: stores = [], isLoading: storesLoading } = useQuery<Store[]>({
queryKey: ['/api/admin/stores'],
});
// Mutations
const createUserMutation = useMutation({
mutationFn: (userData: CreateUserData) => {
const { confirmPassword, ...data } = userData;
return apiRequest('/api/admin/users', {
method: 'POST',
body: JSON.stringify(data),
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/admin/users'] });
setIsUserDialogOpen(false);
toast({ title: "Utilisateur créé avec succès" });
},
onError: () => {
toast({ title: "Erreur", description: "Impossible de créer l'utilisateur", variant: "destructive" });
},
});
const createStoreMutation = useMutation({
mutationFn: (storeData: z.infer<typeof insertStoreSchema>) =>
apiRequest('/api/admin/stores', {
method: 'POST',
body: JSON.stringify(storeData),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/admin/stores'] });
setIsStoreDialogOpen(false);
toast({ title: "Magasin créé avec succès" });
},
onError: () => {
toast({ title: "Erreur", description: "Impossible de créer le magasin", variant: "destructive" });
},
});
// Forms
const userForm = useForm<CreateUserData>({
resolver: zodResolver(createUserSchema),
defaultValues: {
username: "",
password: "",
confirmPassword: "",
email: "",
firstName: "",
lastName: "",
role: "employee",
storeId: undefined,
isActive: true,
},
});
const storeForm = useForm<z.infer<typeof insertStoreSchema>>({
resolver: zodResolver(insertStoreSchema),
defaultValues: {
name: "",
address: "",
phone: "",
email: "",
isActive: true,
},
});
const onCreateUser = (data: CreateUserData) => {
createUserMutation.mutate(data);
};
const onCreateStore = (data: z.infer<typeof insertStoreSchema>) => {
createStoreMutation.mutate(data);
};
const getRoleBadge = (role: string) => {
const colors = {
admin: "bg-red-100 text-red-800",
manager: "bg-blue-100 text-blue-800",
employee: "bg-green-100 text-green-800",
};
return colors[role as keyof typeof colors] || "bg-gray-100 text-gray-800";
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold">Administration</h2>
</div>
<Tabs defaultValue="users" className="space-y-4">
<TabsList>
<TabsTrigger value="users" className="flex items-center gap-2">
<Users className="h-4 w-4" />
Utilisateurs
</TabsTrigger>
<TabsTrigger value="stores" className="flex items-center gap-2">
<Building2 className="h-4 w-4" />
Magasins
</TabsTrigger>
</TabsList>
<TabsContent value="users">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
<CardTitle>Gestion des Utilisateurs</CardTitle>
<Dialog open={isUserDialogOpen} onOpenChange={setIsUserDialogOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="h-4 w-4 mr-2" />
Nouvel Utilisateur
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Créer un Utilisateur</DialogTitle>
</DialogHeader>
<Form {...userForm}>
<form onSubmit={userForm.handleSubmit(onCreateUser)} className="space-y-4">
<FormField
control={userForm.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Nom d'utilisateur</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 gap-4">
<FormField
control={userForm.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>Prénom</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={userForm.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Nom</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={userForm.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={userForm.control}
name="role"
render={({ field }) => (
<FormItem>
<FormLabel>Rôle</FormLabel>
<Select value={field.value} onValueChange={field.onChange}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="admin">Administrateur</SelectItem>
<SelectItem value="manager">Manager</SelectItem>
<SelectItem value="employee">Employé</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={userForm.control}
name="storeId"
render={({ field }) => (
<FormItem>
<FormLabel>Magasin</FormLabel>
<Select value={field.value?.toString()} onValueChange={(value) => field.onChange(parseInt(value))}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Sélectionner un magasin" />
</SelectTrigger>
</FormControl>
<SelectContent>
{stores.map((store) => (
<SelectItem key={store.id} value={store.id.toString()}>
{store.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={userForm.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Mot de passe</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={userForm.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirmer le mot de passe</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={createUserMutation.isPending}>
{createUserMutation.isPending ? "Création..." : "Créer"}
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
{usersLoading ? (
<p>Chargement...</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Utilisateur</TableHead>
<TableHead>Email</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Magasin</TableHead>
<TableHead>Statut</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>
<div>
<p className="font-medium">{user.firstName} {user.lastName}</p>
<p className="text-sm text-gray-500">{user.username}</p>
</div>
</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getRoleBadge(user.role)}`}>
{user.role === 'admin' ? 'Administrateur' :
user.role === 'manager' ? 'Manager' : 'Employé'}
</span>
</TableCell>
<TableCell>
{stores.find(s => s.id === user.storeId)?.name || '-'}
</TableCell>
<TableCell>
{user.isActive ? (
<span className="text-green-600">Actif</span>
) : (
<span className="text-red-600">Inactif</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="stores">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
<CardTitle>Gestion des Magasins</CardTitle>
<Dialog open={isStoreDialogOpen} onOpenChange={setIsStoreDialogOpen}>
<DialogTrigger asChild>
<Button>
<Plus className="h-4 w-4 mr-2" />
Nouveau Magasin
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Créer un Magasin</DialogTitle>
</DialogHeader>
<Form {...storeForm}>
<form onSubmit={storeForm.handleSubmit(onCreateStore)} className="space-y-4">
<FormField
control={storeForm.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nom du magasin</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={storeForm.control}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Adresse</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={storeForm.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Téléphone</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={storeForm.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={createStoreMutation.isPending}>
{createStoreMutation.isPending ? "Création..." : "Créer"}
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
{storesLoading ? (
<p>Chargement...</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Nom</TableHead>
<TableHead>Adresse</TableHead>
<TableHead>Téléphone</TableHead>
<TableHead>Email</TableHead>
<TableHead>Statut</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{stores.map((store) => (
<TableRow key={store.id}>
<TableCell className="font-medium">{store.name}</TableCell>
<TableCell>{store.address}</TableCell>
<TableCell>{store.phone}</TableCell>
<TableCell>{store.email}</TableCell>
<TableCell>
{store.isActive ? (
<span className="text-green-600">Actif</span>
) : (
<span className="text-red-600">Inactif</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}