create-lyrajs 1.1.2 → 2.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/package.json +2 -2
- package/template/backups/README.md +93 -0
- package/template/config/security.yaml +0 -1
- package/template/migrations/README.md +247 -0
- package/template/package.json +2 -6
- package/template/public/assets/styles/app.css +0 -0
- package/template/src/controller/AuthController.ts +100 -71
- package/template/src/controller/ErrorController.ts +126 -0
- package/template/src/controller/ExampleController.ts +28 -0
- package/template/src/controller/ExampleStaticController.ts +40 -0
- package/template/src/controller/UserController.ts +63 -44
- package/template/src/fixtures/{AppFixtures.ts → UserFixtures.ts} +4 -5
- package/template/src/jobs/ExampleJob.ts +63 -0
- package/template/src/middleware/YourMiddleware.ts +1 -1
- package/template/src/repository/UserRepository.ts +2 -3
- package/template/src/router/index.ts +3 -10
- package/template/src/router/routes/exampleRoutes.ts +7 -0
- package/template/src/router/routes/index.ts +4 -6
- package/template/src/server.ts +38 -27
- package/template/src/services/YourService.ts +3 -1
- package/template/src/templates/ExampleRender.tsx +20 -0
- package/template/src/templates/README.md +271 -0
- package/template/src/templates/layout/Base.tsx +21 -0
- package/template/src/templates/layout/Footer.tsx +7 -0
- package/template/src/templates/layout/Header.tsx +19 -0
- package/template/tsconfig.json +8 -4
- package/template/migrations/migration_1752052338457.sql +0 -4
- package/template/src/router/routes/authRoutes.ts +0 -13
- package/template/src/router/routes/userRoutes.ts +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lyrajs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI tool to create new LyraJS projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"cli",
|
|
13
13
|
"scaffold"
|
|
14
14
|
],
|
|
15
|
-
"author": "Matthieu Fergola
|
|
15
|
+
"author": "Matthieu Fergola",
|
|
16
16
|
"license": "GPL-3.0",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Database Backups
|
|
2
|
+
|
|
3
|
+
This directory stores automatic database backups created by the LyraJS migration system.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The `backups/` folder contains SQL dump files that serve as safety checkpoints for your database. These backups are automatically created before destructive migration operations to ensure data can be recovered if needed.
|
|
8
|
+
|
|
9
|
+
## When Backups Are Created
|
|
10
|
+
|
|
11
|
+
Backups are automatically generated in the following scenarios:
|
|
12
|
+
|
|
13
|
+
- **Before destructive migrations**: When running migrations marked as `isDestructive = true`
|
|
14
|
+
- **Before rollback operations**: When using `maestro migration:rollback`
|
|
15
|
+
- **Before refresh operations**: When using `maestro migration:refresh`
|
|
16
|
+
- **Before fresh migrations**: When using `maestro migration:fresh`
|
|
17
|
+
|
|
18
|
+
## Backup File Format
|
|
19
|
+
|
|
20
|
+
Backup files follow this naming convention:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
backup_YYYY-MM-DD_HH-MM-SS_<version>.sql
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Example: `backup_2026-01-05_14-30-45_1767607917120.sql`
|
|
27
|
+
|
|
28
|
+
Where:
|
|
29
|
+
- **Date/Time**: Timestamp of when the backup was created
|
|
30
|
+
- **Version**: Migration version number associated with the backup
|
|
31
|
+
|
|
32
|
+
## Managing Backups
|
|
33
|
+
|
|
34
|
+
### List All Backups
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx maestro list:backups
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Restore a Backup
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx maestro restore:backup <filename>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
```bash
|
|
48
|
+
npx maestro restore:backup backup_2026-01-05_14-30-45_1767607917120.sql
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Clean Up Old Backups
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx maestro cleanup:backups --days=30
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This removes backups older than 30 days (default: 7 days).
|
|
58
|
+
|
|
59
|
+
## Best Practices
|
|
60
|
+
|
|
61
|
+
1. **Keep backups**: Don't delete backups immediately after migrations succeed
|
|
62
|
+
2. **Regular cleanup**: Use `cleanup:backups` to manage disk space
|
|
63
|
+
3. **External backups**: Consider copying important backups to external storage
|
|
64
|
+
4. **Test restores**: Periodically test backup restoration in development
|
|
65
|
+
|
|
66
|
+
## Storage Considerations
|
|
67
|
+
|
|
68
|
+
- Backup files can be large depending on database size
|
|
69
|
+
- Regular cleanup is recommended to prevent disk space issues
|
|
70
|
+
- The migration system tracks backup paths in the `migrations` table
|
|
71
|
+
|
|
72
|
+
## Security
|
|
73
|
+
|
|
74
|
+
- Backup files contain your complete database schema and data
|
|
75
|
+
- Keep this directory secure and excluded from version control
|
|
76
|
+
- The `.gitignore` should include `backups/*.sql`
|
|
77
|
+
|
|
78
|
+
## Related Commands
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# View migration status with backup information
|
|
82
|
+
npx maestro migration:status
|
|
83
|
+
|
|
84
|
+
# Run migration with automatic backup
|
|
85
|
+
npx maestro migration:migrate
|
|
86
|
+
|
|
87
|
+
# Rollback with automatic backup
|
|
88
|
+
npx maestro migration:rollback
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
For more information, visit the [LyraJS Documentation](https://lyrajs.dev/).
|
|
@@ -14,7 +14,6 @@ security:
|
|
|
14
14
|
max_attempts: 5
|
|
15
15
|
message: 'Too many login attempts, please try again later'
|
|
16
16
|
access_control:
|
|
17
|
-
- { path: /api/auth/sign-out, roles: [ROLE_USER] }
|
|
18
17
|
- { path: /api/auth/user, roles: [ROLE_USER] }
|
|
19
18
|
- { path: /api/admin, roles: [ROLE_ADMIN] }
|
|
20
19
|
role_hierarchy:
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Database Migrations
|
|
2
|
+
|
|
3
|
+
This directory contains database migration files that track and version your database schema changes.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The `migrations/` folder stores TypeScript migration files that define how your database schema evolves over time. Each migration represents a specific change to your database structure.
|
|
8
|
+
|
|
9
|
+
## What Are Migrations?
|
|
10
|
+
|
|
11
|
+
Migrations are version control for your database. They allow you to:
|
|
12
|
+
|
|
13
|
+
- **Track schema changes**: Every database modification is recorded
|
|
14
|
+
- **Collaborate safely**: Team members can sync database changes through version control
|
|
15
|
+
- **Rollback changes**: Undo migrations if something goes wrong
|
|
16
|
+
- **Deploy consistently**: Apply the same schema changes across environments
|
|
17
|
+
|
|
18
|
+
## Migration File Structure
|
|
19
|
+
|
|
20
|
+
Migration files follow this naming convention:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Migration_<timestamp>.ts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Example: `Migration_1767607917120.ts`
|
|
27
|
+
|
|
28
|
+
Each migration file implements the `MigrationInterface` with three methods:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { MigrationInterface } from "@lyra-js/core"
|
|
32
|
+
|
|
33
|
+
export class Migration_1767607917120 implements MigrationInterface {
|
|
34
|
+
readonly version = "1767607917120"
|
|
35
|
+
readonly isDestructive = false
|
|
36
|
+
readonly canRunInParallel = false
|
|
37
|
+
|
|
38
|
+
async up(connection: any): Promise<void> {
|
|
39
|
+
// SQL to apply the migration
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async down(connection: any): Promise<void> {
|
|
43
|
+
// SQL to undo the migration
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async dryRun(connection: any): Promise<string[]> {
|
|
47
|
+
// Return SQL statements for preview
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Generating Migrations
|
|
53
|
+
|
|
54
|
+
### Automatic Generation (Recommended)
|
|
55
|
+
|
|
56
|
+
LyraJS automatically detects schema changes by comparing your entities to the database:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx maestro make:migration
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This command:
|
|
63
|
+
1. Introspects your current database schema
|
|
64
|
+
2. Compares it with your entity definitions
|
|
65
|
+
3. Generates migration code for detected differences
|
|
66
|
+
4. Includes smart rename detection to prevent data loss
|
|
67
|
+
|
|
68
|
+
### Manual Migration
|
|
69
|
+
|
|
70
|
+
For complex operations, you can create migrations manually by copying the file structure above.
|
|
71
|
+
|
|
72
|
+
## Running Migrations
|
|
73
|
+
|
|
74
|
+
### Apply Pending Migrations
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx maestro migration:migrate
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### View Migration Status
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx maestro migration:status
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Rollback Last Migration
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx maestro migration:rollback
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Rollback Multiple Migrations
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx maestro migration:rollback --steps=3
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Fresh Installation (⚠️ Destructive)
|
|
99
|
+
|
|
100
|
+
Drops all tables and re-runs all migrations:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx maestro migration:fresh
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Refresh Database (⚠️ Destructive)
|
|
107
|
+
|
|
108
|
+
Rolls back all migrations and re-runs them:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx maestro migration:refresh
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Migration Squashing
|
|
115
|
+
|
|
116
|
+
For mature projects with many migrations, you can combine them into a single baseline migration:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Squash all executed migrations
|
|
120
|
+
npx maestro migration:squash
|
|
121
|
+
|
|
122
|
+
# Squash up to a specific version
|
|
123
|
+
npx maestro migration:squash --to=1767607917120
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Squashed migrations:
|
|
127
|
+
- Reduce migration count for fresh installations
|
|
128
|
+
- Improve migration performance
|
|
129
|
+
- Keep the migrations folder organized
|
|
130
|
+
- Mark old migrations as archived
|
|
131
|
+
|
|
132
|
+
## Migration Features
|
|
133
|
+
|
|
134
|
+
### Smart Rename Detection
|
|
135
|
+
|
|
136
|
+
The migration generator can detect when columns or tables are renamed (instead of dropped and recreated):
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
? Detected potential rename: firstname -> first_name (85% confidence)
|
|
140
|
+
Do you want to rename instead of drop+create? (Y/n)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Automatic Backups
|
|
144
|
+
|
|
145
|
+
Backups are automatically created before destructive operations:
|
|
146
|
+
- The backup is saved to `backups/backup_YYYY-MM-DD_HH-MM-SS_<version>.sql`
|
|
147
|
+
- Tracked in the migrations table for easy restoration
|
|
148
|
+
|
|
149
|
+
### Migration Batching
|
|
150
|
+
|
|
151
|
+
Migrations are organized in batches, making it easy to rollback related changes together.
|
|
152
|
+
|
|
153
|
+
### Parallel Execution
|
|
154
|
+
|
|
155
|
+
Some migrations can run in parallel for better performance (set `canRunInParallel = true`).
|
|
156
|
+
|
|
157
|
+
## Best Practices
|
|
158
|
+
|
|
159
|
+
1. **Never modify executed migrations**: Create new migrations for changes
|
|
160
|
+
2. **Test migrations**: Always test in development before production
|
|
161
|
+
3. **Write reversible migrations**: Ensure `down()` properly undoes `up()`
|
|
162
|
+
4. **Use transactions**: Migrations run in transactions by default
|
|
163
|
+
5. **Keep migrations small**: One logical change per migration
|
|
164
|
+
6. **Review generated SQL**: Check auto-generated migrations before running
|
|
165
|
+
7. **Version control**: Commit migration files to your repository
|
|
166
|
+
|
|
167
|
+
## Common Operations
|
|
168
|
+
|
|
169
|
+
### Adding a Column
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 1. Add column to your entity
|
|
173
|
+
# 2. Generate migration
|
|
174
|
+
npx maestro make:migration
|
|
175
|
+
|
|
176
|
+
# 3. Review and run
|
|
177
|
+
npx maestro migration:migrate
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Creating a Table
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# 1. Create entity class
|
|
184
|
+
# 2. Generate migration
|
|
185
|
+
npx maestro make:migration
|
|
186
|
+
|
|
187
|
+
# 3. Run migration
|
|
188
|
+
npx maestro migration:migrate
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Renaming a Column
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# 1. Rename field in entity
|
|
195
|
+
# 2. Generate migration (will detect rename)
|
|
196
|
+
npx maestro make:migration
|
|
197
|
+
|
|
198
|
+
# 3. Confirm rename when prompted
|
|
199
|
+
# 4. Run migration
|
|
200
|
+
npx maestro migration:migrate
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Troubleshooting
|
|
204
|
+
|
|
205
|
+
### Migration Failed
|
|
206
|
+
|
|
207
|
+
If a migration fails:
|
|
208
|
+
1. Check the error message
|
|
209
|
+
2. Fix the migration file or database state
|
|
210
|
+
3. Rollback if needed: `npx maestro migration:rollback`
|
|
211
|
+
4. Re-run: `npx maestro migration:migrate`
|
|
212
|
+
|
|
213
|
+
### Out of Sync
|
|
214
|
+
|
|
215
|
+
If your database is out of sync with migrations:
|
|
216
|
+
1. Use `npx maestro migration:status` to check state
|
|
217
|
+
2. Manually fix the database or migrations table
|
|
218
|
+
3. Or use `npx maestro migration:fresh` (⚠️ destroys data)
|
|
219
|
+
|
|
220
|
+
### Restore from Backup
|
|
221
|
+
|
|
222
|
+
If something goes wrong:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npx maestro list:backups
|
|
226
|
+
npx maestro restore:backup <filename>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Related Commands
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Show all controllers
|
|
233
|
+
npx maestro show:controllers
|
|
234
|
+
|
|
235
|
+
# Show all entities
|
|
236
|
+
npx maestro show:entities
|
|
237
|
+
|
|
238
|
+
# Show all migrations
|
|
239
|
+
npx maestro show:migrations
|
|
240
|
+
|
|
241
|
+
# Show database structure
|
|
242
|
+
npx maestro show:routes
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
For more information, visit the [LyraJS Documentation](https://lyrajs.dev/).
|
package/template/package.json
CHANGED
|
@@ -7,13 +7,11 @@
|
|
|
7
7
|
"maestro": "maestro"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@lyra-js/core": "^
|
|
10
|
+
"@lyra-js/core": "^2.0.0",
|
|
11
11
|
"bcrypt": "^6.0.0",
|
|
12
|
-
"cookie-parser": "^1.4.7",
|
|
13
12
|
"cors": "^2.8.5",
|
|
14
13
|
"dotenv": "^16.5.0",
|
|
15
|
-
"
|
|
16
|
-
"express-rate-limit": "^8.2.1",
|
|
14
|
+
"esbuild": "^0.27.2",
|
|
17
15
|
"jsonwebtoken": "^9.0.2",
|
|
18
16
|
"mysql2": "^3.14.1",
|
|
19
17
|
"nodemailer": "^7.0.7",
|
|
@@ -22,9 +20,7 @@
|
|
|
22
20
|
},
|
|
23
21
|
"devDependencies": {
|
|
24
22
|
"@types/bcrypt": "^5.0.2",
|
|
25
|
-
"@types/cookie-parser": "^1.4.8",
|
|
26
23
|
"@types/cors": "^2.8.18",
|
|
27
|
-
"@types/express": "^5.0.1",
|
|
28
24
|
"@types/jsonwebtoken": "^9.0.9",
|
|
29
25
|
"@types/node": "^22.15.17",
|
|
30
26
|
"@types/nodemailer": "^6.4.17",
|
|
File without changes
|
|
@@ -1,44 +1,51 @@
|
|
|
1
|
-
import { AccessControl, SecurityConfig } from "@lyra-js/core"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { AccessControl, isAuthenticated, SecurityConfig } from "@lyra-js/core"
|
|
2
|
+
import {
|
|
3
|
+
Controller,
|
|
4
|
+
Delete,
|
|
5
|
+
Get,
|
|
6
|
+
Patch,
|
|
7
|
+
Post,
|
|
8
|
+
rateLimiter,
|
|
9
|
+
Route,
|
|
10
|
+
UnauthorizedException,
|
|
11
|
+
Validator
|
|
12
|
+
} from "@lyra-js/core"
|
|
7
13
|
|
|
8
14
|
import { User } from "@entity/User"
|
|
9
|
-
import { userRepository } from "@repository/UserRepository"
|
|
10
15
|
|
|
11
16
|
const securityConfig = new SecurityConfig().getConfig()
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
@Route({ path: "/auth" })
|
|
19
|
+
export class AuthController extends Controller {
|
|
20
|
+
@Post({ path: "/sign-up" })
|
|
21
|
+
async signUp() {
|
|
15
22
|
try {
|
|
16
|
-
const { username, firstname, lastname, email, password } = req.body
|
|
23
|
+
const { username, firstname, lastname, email, password } = this.req.body
|
|
17
24
|
|
|
18
25
|
if (!username || !firstname || !lastname || !email || !password) {
|
|
19
|
-
|
|
26
|
+
this.badRequest("Missing required fields")
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
if (!Validator.isUsernameValid(username)) {
|
|
23
|
-
|
|
30
|
+
this.badRequest("Invalid username")
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
if (!Validator.isEmailValid(email)) {
|
|
27
|
-
|
|
34
|
+
this.badRequest("Invalid email")
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
const isEmailUsed = await userRepository.findOneBy({ email })
|
|
37
|
+
const isEmailUsed = await this.userRepository.findOneBy({ email })
|
|
31
38
|
|
|
32
39
|
if (isEmailUsed) {
|
|
33
|
-
|
|
40
|
+
this.badRequest("Email already in use")
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
if (!Validator.isPasswordValid(password)) {
|
|
37
|
-
|
|
44
|
+
this.badRequest("Invalid password")
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
const user = new User()
|
|
41
|
-
const hashedPassword = await bcrypt.hash(password, 10)
|
|
48
|
+
const hashedPassword = await this.bcrypt.hash(password, 10)
|
|
42
49
|
|
|
43
50
|
user.username = username
|
|
44
51
|
user.firstname = firstname
|
|
@@ -47,45 +54,45 @@ export class AuthController {
|
|
|
47
54
|
user.password = hashedPassword
|
|
48
55
|
user.role = "ROLE_USER"
|
|
49
56
|
|
|
50
|
-
await userRepository.save(user)
|
|
57
|
+
await this.userRepository.save(user)
|
|
51
58
|
|
|
52
|
-
const registeredUser = await userRepository.findOneBy({ email })
|
|
59
|
+
const registeredUser = await this.userRepository.findOneBy({ email })
|
|
60
|
+
const { password: _, ...userWithoutPassword } = registeredUser || {}
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
res.status(201).json({ message: "User registered successfully", user: registeredUser })
|
|
62
|
+
this.res.status(201).json({ message: "User registered successfully", user: userWithoutPassword })
|
|
57
63
|
} catch (error) {
|
|
58
|
-
next(error)
|
|
64
|
+
this.next(error)
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
@Post({ path: "/sign-in", middlewares: [rateLimiter] })
|
|
69
|
+
async signIn() {
|
|
63
70
|
try {
|
|
64
|
-
const { email, password } = req.body
|
|
71
|
+
const { email, password } = this.req.body
|
|
65
72
|
|
|
66
73
|
if (!email || !password) {
|
|
67
|
-
|
|
74
|
+
this.badRequest("Missing required fields")
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
const user = await userRepository.findOneBy({ email })
|
|
77
|
+
const user = await this.userRepository.findOneBy({ email })
|
|
71
78
|
|
|
72
|
-
if (!user || !(user && (await bcrypt.compare(password, user.password)))) {
|
|
73
|
-
|
|
79
|
+
if (!user || !(user && (await this.bcrypt.compare(password, user.password)))) {
|
|
80
|
+
this.unauthorized("Invalid credentials")
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
const token = jwt.sign({ id: user.id }, securityConfig.jwt.secret_key as string, {
|
|
83
|
+
const token = this.jwt.sign({ id: user.id }, securityConfig.jwt.secret_key as string, {
|
|
77
84
|
algorithm: securityConfig.jwt.algorithm as string,
|
|
78
85
|
expiresIn: securityConfig.jwt.token_expiration
|
|
79
86
|
})
|
|
80
87
|
|
|
81
|
-
const refreshToken = jwt.sign({ id: user.id }, securityConfig.jwt.secret_key_refresh as string, {
|
|
88
|
+
const refreshToken = this.jwt.sign({ id: user.id }, securityConfig.jwt.secret_key_refresh as string, {
|
|
82
89
|
algorithm: securityConfig.jwt.algorithm as string,
|
|
83
90
|
expiresIn: securityConfig.jwt.refresh_token_expiration
|
|
84
91
|
})
|
|
85
92
|
|
|
86
|
-
await userRepository.save(user)
|
|
93
|
+
await this.userRepository.save(user)
|
|
87
94
|
|
|
88
|
-
res.cookie("Token", token, {
|
|
95
|
+
this.res.cookie("Token", token, {
|
|
89
96
|
sameSite: "Lax",
|
|
90
97
|
httpOnly: true,
|
|
91
98
|
secure: process.env.ENV === "production",
|
|
@@ -93,7 +100,7 @@ export class AuthController {
|
|
|
93
100
|
partitioned: false
|
|
94
101
|
})
|
|
95
102
|
|
|
96
|
-
res.cookie("RefreshToken", refreshToken, {
|
|
103
|
+
this.res.cookie("RefreshToken", refreshToken, {
|
|
97
104
|
sameSite: "Lax",
|
|
98
105
|
httpOnly: true,
|
|
99
106
|
secure: process.env.ENV === "production",
|
|
@@ -101,21 +108,24 @@ export class AuthController {
|
|
|
101
108
|
partitioned: false
|
|
102
109
|
})
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
const { password: _, ...userWithoutPassword } = user
|
|
105
112
|
|
|
106
|
-
res
|
|
113
|
+
this.res
|
|
114
|
+
.status(200)
|
|
115
|
+
.json({ message: "User authenticated in successfully", user: userWithoutPassword, token, refreshToken })
|
|
107
116
|
} catch (error) {
|
|
108
|
-
next(error)
|
|
117
|
+
this.next(error)
|
|
109
118
|
}
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
@Get({ path: "/user", middlewares: [isAuthenticated] })
|
|
122
|
+
async getAuthenticatedUser() {
|
|
113
123
|
try {
|
|
114
|
-
const user = req.user as User
|
|
124
|
+
const user = this.req.user as User
|
|
115
125
|
|
|
116
126
|
if (!user) throw new UnauthorizedException()
|
|
117
127
|
|
|
118
|
-
res.status(200).json({
|
|
128
|
+
this.res.status(200).json({
|
|
119
129
|
id: user.id,
|
|
120
130
|
firstname: user.firstname,
|
|
121
131
|
lastname: user.lastname,
|
|
@@ -123,40 +133,56 @@ export class AuthController {
|
|
|
123
133
|
role: user.role
|
|
124
134
|
})
|
|
125
135
|
} catch (error) {
|
|
126
|
-
next(error)
|
|
136
|
+
this.next(error)
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
@Get({ path: "/sign-out" })
|
|
141
|
+
async signOut() {
|
|
142
|
+
try {
|
|
143
|
+
this.res.clearCookie("Token")
|
|
144
|
+
this.res.clearCookie("RefreshToken")
|
|
145
|
+
return this.res.status(200).json({ message: "Unauthenticated successfully" })
|
|
146
|
+
} catch (error) {
|
|
147
|
+
this.next(error)
|
|
148
|
+
}
|
|
134
149
|
}
|
|
135
150
|
|
|
136
|
-
|
|
151
|
+
@Patch({ path: "/update-account", middlewares: [isAuthenticated] })
|
|
152
|
+
async updateProfile() {
|
|
137
153
|
try {
|
|
138
|
-
const { data }: { data: User } = req.body
|
|
139
|
-
const user = req.user as User
|
|
154
|
+
const { data }: { data: User } = this.req.body
|
|
155
|
+
const user = this.req.user as User
|
|
140
156
|
if (!user) throw new UnauthorizedException()
|
|
141
157
|
if (data?.id && data.id !== user.id) throw new UnauthorizedException()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
|
|
159
|
+
// Remove protected fields
|
|
160
|
+
const { role: _role, created_at: _created_at, password, ...updateData } = data
|
|
161
|
+
|
|
162
|
+
// Hash password if provided
|
|
163
|
+
const hashedPassword = password ? await this.bcrypt.hash(password, 10) : undefined
|
|
164
|
+
|
|
165
|
+
const finalData = {
|
|
166
|
+
...updateData,
|
|
167
|
+
...(hashedPassword && { password: hashedPassword }),
|
|
168
|
+
updated_at: new Date()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (user) await this.userRepository.save(finalData)
|
|
172
|
+
this.res.status(200).json({ message: "Users updated successfully" })
|
|
148
173
|
} catch (error) {
|
|
149
|
-
next(error)
|
|
174
|
+
this.next(error)
|
|
150
175
|
}
|
|
151
176
|
}
|
|
152
177
|
|
|
153
|
-
|
|
178
|
+
@Get({ path: "/refresh-token" })
|
|
179
|
+
async refreshToken() {
|
|
154
180
|
try {
|
|
155
181
|
const securityConfig = new SecurityConfig().getConfig()
|
|
156
|
-
let refreshToken = req.cookies.RefreshToken
|
|
182
|
+
let refreshToken = this.req.cookies.RefreshToken
|
|
157
183
|
if (!refreshToken) {
|
|
158
|
-
const authHeader = req.headers.authorization
|
|
159
|
-
if (authHeader && authHeader.startsWith(
|
|
184
|
+
const authHeader = this.req.headers.authorization
|
|
185
|
+
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
160
186
|
refreshToken = authHeader.substring(7)
|
|
161
187
|
}
|
|
162
188
|
}
|
|
@@ -167,38 +193,41 @@ export class AuthController {
|
|
|
167
193
|
|
|
168
194
|
if (!decoded || !decoded.id) throw new UnauthorizedException("Invalid refresh token")
|
|
169
195
|
|
|
170
|
-
const user = await userRepository.find(decoded.id)
|
|
196
|
+
const user = await this.userRepository.find(decoded.id)
|
|
171
197
|
|
|
172
198
|
if (!user) throw new UnauthorizedException("Invalid refresh token")
|
|
173
199
|
|
|
174
200
|
const token = await AccessControl.getNewToken(user)
|
|
175
201
|
|
|
176
|
-
res.cookie("Token", token, {
|
|
177
|
-
sameSite: "
|
|
202
|
+
this.res.cookie("Token", token, {
|
|
203
|
+
sameSite: "Lax",
|
|
178
204
|
httpOnly: true,
|
|
179
205
|
secure: process.env.ENV === "production",
|
|
180
206
|
maxAge: securityConfig.jwt.token_expiration * 1000,
|
|
181
207
|
partitioned: false
|
|
182
208
|
})
|
|
183
209
|
|
|
184
|
-
|
|
210
|
+
const { password: _, ...userWithoutPassword } = user
|
|
185
211
|
|
|
186
|
-
res
|
|
212
|
+
this.res
|
|
213
|
+
.status(200)
|
|
214
|
+
.json({ message: "User authenticated in successfully", user: userWithoutPassword, token, refreshToken })
|
|
187
215
|
} catch (_refreshError) {
|
|
188
|
-
return res.redirect(securityConfig.auth_routes.sign_out)
|
|
216
|
+
return this.res.redirect(securityConfig.auth_routes.sign_out)
|
|
189
217
|
}
|
|
190
218
|
}
|
|
191
219
|
|
|
192
|
-
|
|
193
|
-
|
|
220
|
+
@Delete({ path: "/delete-account", middlewares: [isAuthenticated] })
|
|
221
|
+
async removeUser() {
|
|
222
|
+
const user = this.req.user
|
|
194
223
|
|
|
195
224
|
if (!user) throw new UnauthorizedException()
|
|
196
225
|
|
|
197
|
-
await userRepository.delete(user.id)
|
|
226
|
+
await this.userRepository.delete(user.id)
|
|
198
227
|
|
|
199
|
-
res.clearCookie("Token")
|
|
200
|
-
res.clearCookie("RefreshToken")
|
|
228
|
+
this.res.clearCookie("Token")
|
|
229
|
+
this.res.clearCookie("RefreshToken")
|
|
201
230
|
|
|
202
|
-
res.status(200).json({ message: "User deleted successfully" })
|
|
231
|
+
this.res.status(200).json({ message: "User deleted successfully" })
|
|
203
232
|
}
|
|
204
233
|
}
|