Skip to content

Commit d77d61c

Browse files
committed
feat: added rbac flow to the apis | partial cmt
1 parent 7252d2c commit d77d61c

File tree

15 files changed

+266
-30
lines changed

15 files changed

+266
-30
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ docs/examples
22
node_modules/
33
.env
44
*.pdf
5-
dist/
5+
dist/

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,34 @@ The documentation process is automated using `swagger-ui-express` and `swagger-j
9090
During the build process the application will handle the route comments and generate the final `OpenApi` specification for the `Swagger UI`.
9191

9292
After that you will be able to access: `localhost:3000/docs` in your browser and see the docs.
93+
94+
> The application will be available at `http://localhost:3000`.
95+
96+
## Prisma ORM
97+
98+
This project uses Prisma ORM (Object-Relational Mapping), at every modifications related to the database schema,
99+
a new migration is needed, create a new one running:
100+
101+
```shell
102+
npx prisma migrate dev
103+
```
104+
105+
After that generate a new prisma client instance with:
106+
107+
```shell
108+
npx prisma generate
109+
```
110+
111+
## Authenticating and RBAC
112+
113+
This projects uses `jwt` for authenticating users and managing session, also it uses Role-Based Access Control for limiting some aspects of the API, such as users management.
114+
115+
The current supported roles are: [`admin`, `user`]:
116+
117+
| Endpoint | Admin | User |
118+
| --------------------------- | --------------------- | --------------------- |
119+
| GET users/ | <ul><li>[x]</li></ul> | <ul><li>[x]</li></ul> |
120+
| PATCH users/ | <ul><li>[x]</li></ul> | <ul><li>[x]</li></ul> |
121+
| PATCH users/change-role/:id | <ul><li>[x]</li></ul> | |
122+
| GET users/all | <ul><li>[x]</li></ul> | |
123+
| DELETE users/:id | <ul><li>[x]</li></ul> | |

docs/prompts/generate-openapi-jsdocs.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
Generate the JSDocs comments using OpenAPI for the following API specs:
22

33
```yaml
4-
endpoint name: /users/all
5-
# path parameter type: string
6-
method: GET
4+
endpoint name: /users/change-role/{id}
5+
path parameter type: string
6+
method: Delete
7+
tags: [Users]
78
request body: {
8-
"name": "John Doe",
9-
"email": "[email protected]",
9+
"role": ["admin", "user"]
1010
}
11-
summary: Returns all users
11+
summary: Update a user role and return it, this route is used only by admins
1212
200_response: {
1313
"success": true,
1414
"data": [
1515
{
1616
"id": "367b2539-bef4-412b-b94d-c9d2178dcdaa",
1717
"name": "John Doe",
1818
"email": "[email protected]",
19-
"createdAt": "2024-09-30T21:04:18.656Z"
19+
"createdAt": "2024-09-30T21:04:18.656Z",
20+
"role": "user"
2021
}
2122
]
2223
}
23-
# 400_response: {
24-
# "success": false,
25-
# "error": {
26-
# "message": [
27-
# "Invalid email address",
28-
# "name field is required"
29-
# ]
30-
# }
31-
# }
24+
400_response: {
25+
"success": false,
26+
"error": {
27+
"message": [
28+
"Role is required.",
29+
]
30+
}
31+
}
3232
headers: {
3333
"Content-Type": "application/json",
3434
"Authorization": "Bearer <token>"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Users" ADD COLUMN "role" TEXT;

prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ model Users {
1919
email String @unique
2020
password String
2121
name String?
22+
role String?
2223
}

src/common/middlewares/body-validation.middleware.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ export const userCredentialsSchema = z.object({
1414

1515
export const updateUserSchema = z.object({
1616
email: z.string().email('Invalid email address'),
17-
name: z.string().min(1, 'name field is required'),
17+
name: z.string().min(1, 'Name field is required'),
18+
})
19+
20+
export const changeUserRoleSchema = z.object({
21+
role: z.enum(['admin', 'user'], { required_error: 'Role is required' }),
1822
})
1923

2024
export const translateTextSchema = z.object({
2125
text: z.string().min(1, 'text field is required'),
22-
language: z.string().min(1, 'language field is required'),
26+
language: z.string().min(1, 'Language field is required'),
2327
})
2428

2529
export const validateRequestBody = (schema: z.ZodSchema<any>) => {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { NextFunction, Request, Response } from 'express'
2+
3+
import { userDatProvider } from '@/di'
4+
import { UserRoles } from '@/modules/users/core'
5+
6+
export function hasPermission(role: UserRoles) {
7+
const userProvider = userDatProvider
8+
9+
return (req: Request, res: Response, next: NextFunction) => {
10+
const { user } = req.body
11+
12+
userProvider.getUserById(user.id).then((user) => {
13+
if (!user) {
14+
return res.status(403).json({
15+
success: false,
16+
error: {
17+
message: 'Invalid access token provided, please sign-in again.',
18+
},
19+
})
20+
}
21+
22+
if (user?.role !== role) {
23+
return res.status(403).json({
24+
status: false,
25+
error: `You need to be a ${role} to access this functionality.`,
26+
})
27+
}
28+
29+
return next()
30+
})
31+
}
32+
}

src/common/middlewares/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './body-validation.middleware'
2+
export * from './check-permission.middleware'
23
export * from './is-authenticated.middleware'

src/modules/auth/application/controllers/auth.controller.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ export class AuthController {
1010
constructor(private _userDataProvider: UserDataProvider) {}
1111

1212
register = async (req: Request<UserParams>, res: Response) => {
13-
const { password, ...user } = req.body
13+
const { password, role, ...user } = req.body
1414
const encryptedPassword = this._encryptPassword(password)
15+
const currentRole = role ?? 'user'
1516

1617
this._userDataProvider
1718
.insert({
1819
...user,
1920
password: encryptedPassword,
21+
role: currentRole,
2022
})
2123
.then((user) => {
2224
return res.status(201).json({
@@ -72,6 +74,7 @@ export class AuthController {
7274
name: user.name,
7375
email: user.email,
7476
createdAt: user.createdAt,
77+
role: user.role,
7578
},
7679
token: this._generateAccessToken(user),
7780
},

0 commit comments

Comments
 (0)