vperms-testing 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +94 -0
- package/frontend/bun.lock +252 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +22 -0
- package/frontend/src/App.jsx +79 -0
- package/frontend/src/components/Layout.jsx +58 -0
- package/frontend/src/context/AuthContext.jsx +7 -0
- package/frontend/src/hooks/useApi.jsx +35 -0
- package/frontend/src/index.css +462 -0
- package/frontend/src/main.jsx +10 -0
- package/frontend/src/pages/Dashboard.jsx +148 -0
- package/frontend/src/pages/Login.jsx +109 -0
- package/frontend/src/pages/Permissions.jsx +150 -0
- package/frontend/src/pages/Roles.jsx +263 -0
- package/frontend/src/pages/Users.jsx +171 -0
- package/frontend/vite.config.js +15 -0
- package/package.json +25 -0
- package/prisma/schema.prisma +104 -0
- package/query +0 -0
- package/server/index.js +57 -0
- package/server/middleware/auth.js +65 -0
- package/server/routes/auth.js +157 -0
- package/server/routes/permissions.js +64 -0
- package/server/routes/roles.js +208 -0
- package/server/routes/users.js +191 -0
- package/server/seed.js +167 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useApi } from '../hooks/useApi';
|
|
3
|
+
|
|
4
|
+
export default function Users() {
|
|
5
|
+
const api = useApi();
|
|
6
|
+
const [users, setUsers] = useState([]);
|
|
7
|
+
const [roles, setRoles] = useState([]);
|
|
8
|
+
const [loading, setLoading] = useState(true);
|
|
9
|
+
const [error, setError] = useState('');
|
|
10
|
+
const [selectedUser, setSelectedUser] = useState(null);
|
|
11
|
+
const [assignRoleId, setAssignRoleId] = useState('');
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
loadData();
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const loadData = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const [usersRes, rolesRes] = await Promise.all([
|
|
20
|
+
api.get('/users'),
|
|
21
|
+
api.get('/roles'),
|
|
22
|
+
]);
|
|
23
|
+
setUsers(usersRes.users || []);
|
|
24
|
+
setRoles(rolesRes.roles || []);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
setError(err.message);
|
|
27
|
+
} finally {
|
|
28
|
+
setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const assignRole = async () => {
|
|
33
|
+
if (!selectedUser || !assignRoleId) return;
|
|
34
|
+
try {
|
|
35
|
+
await api.post(`/users/${selectedUser.id}/roles`, { roleId: assignRoleId });
|
|
36
|
+
setAssignRoleId('');
|
|
37
|
+
loadData();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setError(err.message);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const removeRole = async (userId, roleId) => {
|
|
44
|
+
try {
|
|
45
|
+
await api.delete(`/users/${userId}/roles/${roleId}`);
|
|
46
|
+
loadData();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
setError(err.message);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<div className="page-header">
|
|
55
|
+
<div>
|
|
56
|
+
<h1 className="page-title">Users</h1>
|
|
57
|
+
<p className="page-subtitle">Manage users and their roles</p>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{error && <div className="message message-error">{error}</div>}
|
|
62
|
+
|
|
63
|
+
{loading ? (
|
|
64
|
+
<div className="loading"><div className="spinner"></div></div>
|
|
65
|
+
) : (
|
|
66
|
+
<div className="grid grid-2">
|
|
67
|
+
<div className="card">
|
|
68
|
+
<div className="card-header">
|
|
69
|
+
<h3 className="card-title">All Users</h3>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="card-body">
|
|
72
|
+
<div className="table-container">
|
|
73
|
+
<table>
|
|
74
|
+
<thead>
|
|
75
|
+
<tr>
|
|
76
|
+
<th>Name</th>
|
|
77
|
+
<th>Email</th>
|
|
78
|
+
<th>Roles</th>
|
|
79
|
+
<th>Actions</th>
|
|
80
|
+
</tr>
|
|
81
|
+
</thead>
|
|
82
|
+
<tbody>
|
|
83
|
+
{users.map(user => (
|
|
84
|
+
<tr key={user.id}>
|
|
85
|
+
<td>{user.name || '-'}</td>
|
|
86
|
+
<td>{user.email}</td>
|
|
87
|
+
<td>
|
|
88
|
+
{user.roles?.map(role => (
|
|
89
|
+
<span key={role} className="badge badge-primary" style={{ marginRight: '0.25rem' }}>
|
|
90
|
+
{role}
|
|
91
|
+
</span>
|
|
92
|
+
))}
|
|
93
|
+
</td>
|
|
94
|
+
<td>
|
|
95
|
+
<button
|
|
96
|
+
className="btn btn-secondary btn-sm"
|
|
97
|
+
onClick={() => setSelectedUser(user)}
|
|
98
|
+
>
|
|
99
|
+
Manage
|
|
100
|
+
</button>
|
|
101
|
+
</td>
|
|
102
|
+
</tr>
|
|
103
|
+
))}
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="card">
|
|
111
|
+
<div className="card-header">
|
|
112
|
+
<h3 className="card-title">
|
|
113
|
+
{selectedUser ? `Manage: ${selectedUser.name || selectedUser.email}` : 'Select a user'}
|
|
114
|
+
</h3>
|
|
115
|
+
</div>
|
|
116
|
+
<div className="card-body">
|
|
117
|
+
{selectedUser ? (
|
|
118
|
+
<>
|
|
119
|
+
<div className="form-group">
|
|
120
|
+
<label className="form-label">Assign Role</label>
|
|
121
|
+
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
122
|
+
<select
|
|
123
|
+
className="form-input"
|
|
124
|
+
value={assignRoleId}
|
|
125
|
+
onChange={(e) => setAssignRoleId(e.target.value)}
|
|
126
|
+
>
|
|
127
|
+
<option value="">Select role...</option>
|
|
128
|
+
{roles.map(role => (
|
|
129
|
+
<option key={role.id} value={role.id}>{role.name}</option>
|
|
130
|
+
))}
|
|
131
|
+
</select>
|
|
132
|
+
<button className="btn btn-primary" onClick={assignRole}>Assign</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div style={{ marginTop: '1.5rem' }}>
|
|
137
|
+
<label className="form-label">Current Roles</label>
|
|
138
|
+
{selectedUser.roles?.length > 0 ? (
|
|
139
|
+
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
|
140
|
+
{selectedUser.roles.map(roleName => {
|
|
141
|
+
const role = roles.find(r => r.name === roleName);
|
|
142
|
+
return role ? (
|
|
143
|
+
<div key={role.id} className="badge badge-primary" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
|
144
|
+
{roleName}
|
|
145
|
+
<button
|
|
146
|
+
onClick={() => removeRole(selectedUser.id, role.id)}
|
|
147
|
+
style={{ background: 'none', border: 'none', color: 'inherit', cursor: 'pointer' }}
|
|
148
|
+
>
|
|
149
|
+
✕
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
) : (
|
|
153
|
+
<span key={roleName} className="badge badge-primary">{roleName}</span>
|
|
154
|
+
);
|
|
155
|
+
})}
|
|
156
|
+
</div>
|
|
157
|
+
) : (
|
|
158
|
+
<p style={{ color: 'var(--text-secondary)' }}>No roles assigned</p>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</>
|
|
162
|
+
) : (
|
|
163
|
+
<p className="empty-state">Select a user from the list to manage their roles</p>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 5173,
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:3001',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vperms-testing",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Test project for @faryzal2020/v-perms package",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun run --watch server/index.js",
|
|
8
|
+
"dev:frontend": "cd frontend && bun run dev",
|
|
9
|
+
"db:push": "bunx prisma db push",
|
|
10
|
+
"db:generate": "bunx prisma generate",
|
|
11
|
+
"db:studio": "bunx prisma studio",
|
|
12
|
+
"db:seed": "bun run server/seed.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@prisma/client": "^5.22.0",
|
|
16
|
+
"hono": "^4.6.0",
|
|
17
|
+
"@hono/node-server": "^1.13.0",
|
|
18
|
+
"@faryzal2020/v-perms": "file:../v-perms",
|
|
19
|
+
"bcryptjs": "^2.4.3",
|
|
20
|
+
"jsonwebtoken": "^9.0.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"prisma": "^5.22.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model User {
|
|
11
|
+
id String @id @default(cuid())
|
|
12
|
+
email String @unique
|
|
13
|
+
name String?
|
|
14
|
+
password String
|
|
15
|
+
createdAt DateTime @default(now())
|
|
16
|
+
updatedAt DateTime @updatedAt
|
|
17
|
+
|
|
18
|
+
userRoles UserRole[]
|
|
19
|
+
userPermissions UserPermission[]
|
|
20
|
+
|
|
21
|
+
@@map("users")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
model Role {
|
|
25
|
+
id String @id @default(cuid())
|
|
26
|
+
name String @unique
|
|
27
|
+
description String?
|
|
28
|
+
priority Int @default(0)
|
|
29
|
+
isDefault Boolean @default(false)
|
|
30
|
+
createdAt DateTime @default(now())
|
|
31
|
+
updatedAt DateTime @updatedAt
|
|
32
|
+
|
|
33
|
+
userRoles UserRole[]
|
|
34
|
+
rolePermissions RolePermission[]
|
|
35
|
+
inheritedRoles RoleInheritance[] @relation("ParentRole")
|
|
36
|
+
inheritsFrom RoleInheritance[] @relation("ChildRole")
|
|
37
|
+
|
|
38
|
+
@@map("roles")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
model Permission {
|
|
42
|
+
id String @id @default(cuid())
|
|
43
|
+
key String @unique
|
|
44
|
+
description String?
|
|
45
|
+
category String?
|
|
46
|
+
createdAt DateTime @default(now())
|
|
47
|
+
|
|
48
|
+
rolePermissions RolePermission[]
|
|
49
|
+
userPermissions UserPermission[]
|
|
50
|
+
|
|
51
|
+
@@index([category])
|
|
52
|
+
@@map("permissions")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
model UserRole {
|
|
56
|
+
userId String
|
|
57
|
+
roleId String
|
|
58
|
+
assignedAt DateTime @default(now())
|
|
59
|
+
|
|
60
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
61
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
62
|
+
|
|
63
|
+
@@id([userId, roleId])
|
|
64
|
+
@@map("user_roles")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
model RolePermission {
|
|
68
|
+
roleId String
|
|
69
|
+
permissionId String
|
|
70
|
+
granted Boolean @default(true)
|
|
71
|
+
assignedAt DateTime @default(now())
|
|
72
|
+
|
|
73
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
74
|
+
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
|
75
|
+
|
|
76
|
+
@@id([roleId, permissionId])
|
|
77
|
+
@@map("role_permissions")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
model UserPermission {
|
|
81
|
+
userId String
|
|
82
|
+
permissionId String
|
|
83
|
+
granted Boolean @default(true)
|
|
84
|
+
assignedAt DateTime @default(now())
|
|
85
|
+
|
|
86
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
87
|
+
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
|
88
|
+
|
|
89
|
+
@@id([userId, permissionId])
|
|
90
|
+
@@map("user_permissions")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
model RoleInheritance {
|
|
94
|
+
roleId String
|
|
95
|
+
inheritsFromId String
|
|
96
|
+
priority Int @default(0)
|
|
97
|
+
createdAt DateTime @default(now())
|
|
98
|
+
|
|
99
|
+
role Role @relation("ParentRole", fields: [roleId], references: [id], onDelete: Cascade)
|
|
100
|
+
inheritsFrom Role @relation("ChildRole", fields: [inheritsFromId], references: [id], onDelete: Cascade)
|
|
101
|
+
|
|
102
|
+
@@id([roleId, inheritsFromId])
|
|
103
|
+
@@map("role_inheritance")
|
|
104
|
+
}
|
package/query
ADDED
|
File without changes
|
package/server/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { cors } from 'hono/cors';
|
|
3
|
+
import { serve } from '@hono/node-server';
|
|
4
|
+
import { PrismaClient } from '@prisma/client';
|
|
5
|
+
import { createPermissionSystem } from '@faryzal2020/v-perms';
|
|
6
|
+
import authRoutes from './routes/auth.js';
|
|
7
|
+
import userRoutes from './routes/users.js';
|
|
8
|
+
import roleRoutes from './routes/roles.js';
|
|
9
|
+
import permissionRoutes from './routes/permissions.js';
|
|
10
|
+
|
|
11
|
+
// Initialize Prisma
|
|
12
|
+
const prisma = new PrismaClient();
|
|
13
|
+
|
|
14
|
+
// Initialize Permission System
|
|
15
|
+
const perms = createPermissionSystem(prisma, {
|
|
16
|
+
debug: true,
|
|
17
|
+
enableCache: false, // No Redis for this demo
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Create Hono app
|
|
21
|
+
const app = new Hono();
|
|
22
|
+
|
|
23
|
+
// Middleware
|
|
24
|
+
app.use('*', cors({
|
|
25
|
+
origin: 'http://localhost:5173',
|
|
26
|
+
credentials: true,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Make prisma and perms available in context
|
|
30
|
+
app.use('*', async (c, next) => {
|
|
31
|
+
c.set('prisma', prisma);
|
|
32
|
+
c.set('perms', perms);
|
|
33
|
+
await next();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Health check
|
|
37
|
+
app.get('/', (c) => {
|
|
38
|
+
return c.json({ message: 'v-perms Test API', status: 'running' });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Routes
|
|
42
|
+
app.route('/api/auth', authRoutes);
|
|
43
|
+
app.route('/api/users', userRoutes);
|
|
44
|
+
app.route('/api/roles', roleRoutes);
|
|
45
|
+
app.route('/api/permissions', permissionRoutes);
|
|
46
|
+
|
|
47
|
+
// Start server
|
|
48
|
+
const port = process.env.PORT || 3001;
|
|
49
|
+
|
|
50
|
+
console.log(`🚀 Server starting on port ${port}`);
|
|
51
|
+
|
|
52
|
+
serve({
|
|
53
|
+
fetch: app.fetch,
|
|
54
|
+
port: Number(port),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(`✅ Server running at http://localhost:${port}`);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-dev-key';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Middleware to verify JWT token
|
|
7
|
+
*/
|
|
8
|
+
export async function authMiddleware(c, next) {
|
|
9
|
+
const authHeader = c.req.header('Authorization');
|
|
10
|
+
|
|
11
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
12
|
+
return c.json({ error: 'No token provided' }, 401);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const token = authHeader.split(' ')[1];
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
19
|
+
c.set('user', decoded);
|
|
20
|
+
await next();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return c.json({ error: 'Invalid token' }, 401);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Middleware to check if user has permission
|
|
28
|
+
*/
|
|
29
|
+
export function requirePermission(permission) {
|
|
30
|
+
return async (c, next) => {
|
|
31
|
+
const user = c.get('user');
|
|
32
|
+
const perms = c.get('perms');
|
|
33
|
+
|
|
34
|
+
if (!user) {
|
|
35
|
+
return c.json({ error: 'Unauthorized' }, 401);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const hasPermission = await perms.can(user.id, permission);
|
|
40
|
+
|
|
41
|
+
if (!hasPermission) {
|
|
42
|
+
return c.json({
|
|
43
|
+
error: 'Forbidden',
|
|
44
|
+
required: permission
|
|
45
|
+
}, 403);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await next();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Permission check error:', error);
|
|
51
|
+
return c.json({ error: 'Permission check failed' }, 500);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate JWT token
|
|
58
|
+
*/
|
|
59
|
+
export function generateToken(user) {
|
|
60
|
+
return jwt.sign(
|
|
61
|
+
{ id: user.id, email: user.email },
|
|
62
|
+
JWT_SECRET,
|
|
63
|
+
{ expiresIn: '24h' }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import bcrypt from 'bcryptjs';
|
|
3
|
+
import { generateToken, authMiddleware } from '../middleware/auth.js';
|
|
4
|
+
|
|
5
|
+
const router = new Hono();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /api/auth/register
|
|
9
|
+
* Register a new user
|
|
10
|
+
*/
|
|
11
|
+
router.post('/register', async (c) => {
|
|
12
|
+
const prisma = c.get('prisma');
|
|
13
|
+
const perms = c.get('perms');
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { email, password, name } = await c.req.json();
|
|
17
|
+
|
|
18
|
+
// Validate input
|
|
19
|
+
if (!email || !password) {
|
|
20
|
+
return c.json({ error: 'Email and password required' }, 400);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if user exists
|
|
24
|
+
const existing = await prisma.user.findUnique({
|
|
25
|
+
where: { email },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (existing) {
|
|
29
|
+
return c.json({ error: 'Email already registered' }, 400);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Hash password
|
|
33
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
34
|
+
|
|
35
|
+
// Create user
|
|
36
|
+
const user = await prisma.user.create({
|
|
37
|
+
data: {
|
|
38
|
+
email,
|
|
39
|
+
password: hashedPassword,
|
|
40
|
+
name: name || null,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Assign default role if exists
|
|
45
|
+
try {
|
|
46
|
+
const defaultRole = await prisma.role.findFirst({
|
|
47
|
+
where: { isDefault: true },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (defaultRole) {
|
|
51
|
+
await perms.assignRole(defaultRole.id, user.id);
|
|
52
|
+
}
|
|
53
|
+
} catch (roleError) {
|
|
54
|
+
console.log('No default role to assign');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate token
|
|
58
|
+
const token = generateToken(user);
|
|
59
|
+
|
|
60
|
+
return c.json({
|
|
61
|
+
message: 'Registration successful',
|
|
62
|
+
user: { id: user.id, email: user.email, name: user.name },
|
|
63
|
+
token,
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Registration error:', error);
|
|
67
|
+
return c.json({ error: 'Registration failed' }, 500);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* POST /api/auth/login
|
|
73
|
+
* Login user
|
|
74
|
+
*/
|
|
75
|
+
router.post('/login', async (c) => {
|
|
76
|
+
const prisma = c.get('prisma');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const { email, password } = await c.req.json();
|
|
80
|
+
|
|
81
|
+
// Validate input
|
|
82
|
+
if (!email || !password) {
|
|
83
|
+
return c.json({ error: 'Email and password required' }, 400);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find user
|
|
87
|
+
const user = await prisma.user.findUnique({
|
|
88
|
+
where: { email },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!user) {
|
|
92
|
+
return c.json({ error: 'Invalid credentials' }, 401);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check password
|
|
96
|
+
const validPassword = await bcrypt.compare(password, user.password);
|
|
97
|
+
|
|
98
|
+
if (!validPassword) {
|
|
99
|
+
return c.json({ error: 'Invalid credentials' }, 401);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Generate token
|
|
103
|
+
const token = generateToken(user);
|
|
104
|
+
|
|
105
|
+
return c.json({
|
|
106
|
+
message: 'Login successful',
|
|
107
|
+
user: { id: user.id, email: user.email, name: user.name },
|
|
108
|
+
token,
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Login error:', error);
|
|
112
|
+
return c.json({ error: 'Login failed' }, 500);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* GET /api/auth/me
|
|
118
|
+
* Get current user info with roles and permissions
|
|
119
|
+
*/
|
|
120
|
+
router.get('/me', authMiddleware, async (c) => {
|
|
121
|
+
const prisma = c.get('prisma');
|
|
122
|
+
const perms = c.get('perms');
|
|
123
|
+
const authUser = c.get('user');
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const user = await prisma.user.findUnique({
|
|
127
|
+
where: { id: authUser.id },
|
|
128
|
+
select: {
|
|
129
|
+
id: true,
|
|
130
|
+
email: true,
|
|
131
|
+
name: true,
|
|
132
|
+
createdAt: true,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!user) {
|
|
137
|
+
return c.json({ error: 'User not found' }, 404);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get user's roles
|
|
141
|
+
const roles = await perms.manager.getUserRoles(user.id);
|
|
142
|
+
|
|
143
|
+
// Get user's permissions
|
|
144
|
+
const permissions = await perms.manager.getUserPermissions(user.id);
|
|
145
|
+
|
|
146
|
+
return c.json({
|
|
147
|
+
user,
|
|
148
|
+
roles: roles.map(r => ({ id: r.id, name: r.name, priority: r.priority })),
|
|
149
|
+
permissions,
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('Get user error:', error);
|
|
153
|
+
return c.json({ error: 'Failed to get user' }, 500);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export default router;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { authMiddleware, requirePermission } from '../middleware/auth.js';
|
|
3
|
+
|
|
4
|
+
const router = new Hono();
|
|
5
|
+
|
|
6
|
+
// All routes require authentication
|
|
7
|
+
router.use('*', authMiddleware);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/permissions
|
|
11
|
+
* List all permissions
|
|
12
|
+
*/
|
|
13
|
+
router.get('/', requirePermission('permissions.list'), async (c) => {
|
|
14
|
+
const perms = c.get('perms');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const permissions = await perms.manager.listPermissions();
|
|
18
|
+
return c.json({ permissions });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('List permissions error:', error);
|
|
21
|
+
return c.json({ error: 'Failed to list permissions' }, 500);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* POST /api/permissions
|
|
27
|
+
* Create a new permission
|
|
28
|
+
*/
|
|
29
|
+
router.post('/', requirePermission('permissions.create'), async (c) => {
|
|
30
|
+
const perms = c.get('perms');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const { key, description, category } = await c.req.json();
|
|
34
|
+
|
|
35
|
+
if (!key) {
|
|
36
|
+
return c.json({ error: 'Permission key required' }, 400);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const permission = await perms.createPermission(key, description, category);
|
|
40
|
+
return c.json({ message: 'Permission created', permission });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Create permission error:', error);
|
|
43
|
+
return c.json({ error: error.message || 'Failed to create permission' }, 500);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* DELETE /api/permissions/:key
|
|
49
|
+
* Delete a permission
|
|
50
|
+
*/
|
|
51
|
+
router.delete('/:key', requirePermission('permissions.delete'), async (c) => {
|
|
52
|
+
const perms = c.get('perms');
|
|
53
|
+
const key = c.req.param('key');
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await perms.deletePermission(key);
|
|
57
|
+
return c.json({ message: 'Permission deleted' });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Delete permission error:', error);
|
|
60
|
+
return c.json({ error: error.message || 'Failed to delete permission' }, 500);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export default router;
|