@lenne.tech/nest-server 10.8.4 → 10.8.6
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/dist/core/modules/auth/core-auth.controller.js +17 -11
- package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
- package/dist/core/modules/auth/core-auth.model.js +17 -3
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js +18 -4
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js.map +1 -1
- package/dist/core/modules/user/core-user.model.js +73 -12
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/server/modules/auth/auth.module.js +2 -1
- package/dist/server/modules/auth/auth.module.js.map +1 -1
- package/dist/server/server.module.js +2 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/core/modules/auth/core-auth.controller.ts +16 -11
- package/src/core/modules/auth/core-auth.model.ts +18 -4
- package/src/core/modules/auth/inputs/core-auth-sign-in.input.ts +18 -4
- package/src/core/modules/user/core-user.model.ts +73 -12
- package/src/server/modules/auth/auth.module.ts +2 -1
- package/src/server/server.module.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "10.8.
|
|
3
|
+
"version": "10.8.6",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@nestjs/passport": "10.0.3",
|
|
76
76
|
"@nestjs/platform-express": "10.4.15",
|
|
77
77
|
"@nestjs/schedule": "4.1.2",
|
|
78
|
+
"@nestjs/swagger": "8.1.1",
|
|
78
79
|
"@nestjs/terminus": "10.3.0",
|
|
79
80
|
"apollo-server-core": "3.13.0",
|
|
80
81
|
"apollo-server-express": "3.13.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Body, Controller, Get,
|
|
2
|
-
import {
|
|
1
|
+
import { Body, Controller, Get, ParseBoolPipe, Post, Query, Res, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { ApiBody, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
|
3
3
|
import { Response as ResponseType } from 'express';
|
|
4
4
|
|
|
5
5
|
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
|
@@ -29,14 +29,15 @@ export class CoreAuthController {
|
|
|
29
29
|
/**
|
|
30
30
|
* Logout user (from specific device)
|
|
31
31
|
*/
|
|
32
|
+
@ApiOperation({ description: 'Logs a user out from a specific device' })
|
|
33
|
+
@Get('logout')
|
|
32
34
|
@Roles(RoleEnum.S_EVERYONE)
|
|
33
35
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
34
|
-
@Get()
|
|
35
36
|
async logout(
|
|
36
37
|
@CurrentUser() currentUser: ICoreAuthUser,
|
|
37
38
|
@Tokens('token') token: string,
|
|
38
|
-
@Res() res: ResponseType,
|
|
39
|
-
@
|
|
39
|
+
@Res({ passthrough: true }) res: ResponseType,
|
|
40
|
+
@Query('allDevices', ParseBoolPipe) allDevices?: boolean,
|
|
40
41
|
): Promise<boolean> {
|
|
41
42
|
const result = await this.authService.logout(token, { allDevices, currentUser });
|
|
42
43
|
return this.processCookies(res, result);
|
|
@@ -45,13 +46,14 @@ export class CoreAuthController {
|
|
|
45
46
|
/**
|
|
46
47
|
* Refresh token (for specific device)
|
|
47
48
|
*/
|
|
49
|
+
@ApiResponse({ type: CoreAuthModel })
|
|
50
|
+
@Get('refresh-token')
|
|
48
51
|
@Roles(RoleEnum.S_EVERYONE)
|
|
49
52
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT_REFRESH))
|
|
50
|
-
@Get()
|
|
51
53
|
async refreshToken(
|
|
52
54
|
@CurrentUser() user: ICoreAuthUser,
|
|
53
55
|
@Tokens('refreshToken') refreshToken: string,
|
|
54
|
-
@Res() res: ResponseType,
|
|
56
|
+
@Res({ passthrough: true }) res: ResponseType,
|
|
55
57
|
): Promise<CoreAuthModel> {
|
|
56
58
|
const result = await this.authService.refreshTokens(user, refreshToken);
|
|
57
59
|
return this.processCookies(res, result);
|
|
@@ -60,9 +62,10 @@ export class CoreAuthController {
|
|
|
60
62
|
/**
|
|
61
63
|
* Sign in user via email and password (on specific device)
|
|
62
64
|
*/
|
|
65
|
+
@ApiOperation({ description: 'Sign in via email and password' })
|
|
66
|
+
@Post('signin')
|
|
63
67
|
@Roles(RoleEnum.S_EVERYONE)
|
|
64
|
-
@
|
|
65
|
-
async signIn(@Res() res: ResponseType, @Body('input') input: CoreAuthSignInInput): Promise<CoreAuthModel> {
|
|
68
|
+
async signIn(@Res({ passthrough: true }) res: ResponseType, @Body() input: CoreAuthSignInInput): Promise<CoreAuthModel> {
|
|
66
69
|
const result = await this.authService.signIn(input);
|
|
67
70
|
return this.processCookies(res, result);
|
|
68
71
|
}
|
|
@@ -70,9 +73,11 @@ export class CoreAuthController {
|
|
|
70
73
|
/**
|
|
71
74
|
* Register a new user account (on specific device)
|
|
72
75
|
*/
|
|
76
|
+
@ApiBody({ type: CoreAuthSignUpInput })
|
|
77
|
+
@ApiOperation({ description: 'Sign up via email and password' })
|
|
78
|
+
@Post('signup')
|
|
73
79
|
@Roles(RoleEnum.S_EVERYONE)
|
|
74
|
-
@
|
|
75
|
-
async signUp(@Res() res: ResponseType, @Args('input') input: CoreAuthSignUpInput): Promise<CoreAuthModel> {
|
|
80
|
+
async signUp(@Res() res: ResponseType, @Body() input: CoreAuthSignUpInput): Promise<CoreAuthModel> {
|
|
76
81
|
const result = await this.authService.signUp(input);
|
|
77
82
|
return this.processCookies(res, result);
|
|
78
83
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
3
|
|
|
3
4
|
import { Restricted } from '../../common/decorators/restricted.decorator';
|
|
4
5
|
import { RoleEnum } from '../../common/enums/role.enum';
|
|
@@ -16,24 +17,37 @@ export class CoreAuthModel extends CoreModel {
|
|
|
16
17
|
// ===================================================================================================================
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* JSON Web Token(JWT)
|
|
20
21
|
*/
|
|
22
|
+
@ApiProperty({
|
|
23
|
+
description: 'JSON Web Token(JWT) used for auth',
|
|
24
|
+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
25
|
+
})
|
|
26
|
+
@Field({ description: 'JSON Web Token(JWT)', nullable: true })
|
|
21
27
|
@Restricted(RoleEnum.S_EVERYONE)
|
|
22
|
-
@Field({ description: 'JavaScript Web Token (JWT)', nullable: true })
|
|
23
28
|
token?: string = undefined;
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* Refresh token
|
|
27
32
|
*/
|
|
28
|
-
@
|
|
33
|
+
@ApiProperty({
|
|
34
|
+
description: 'Refresh JSON Web Token(JWT) used for auth',
|
|
35
|
+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
|
36
|
+
})
|
|
29
37
|
@Field({ description: 'Refresh token', nullable: true })
|
|
38
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
30
39
|
refreshToken?: string = undefined;
|
|
31
40
|
|
|
32
41
|
/**
|
|
33
42
|
* Current user
|
|
34
43
|
*/
|
|
35
|
-
@
|
|
44
|
+
@ApiProperty({
|
|
45
|
+
description: 'User who signed in',
|
|
46
|
+
required: true,
|
|
47
|
+
type: () => CoreUserModel,
|
|
48
|
+
})
|
|
36
49
|
@Field({ description: 'Current user' })
|
|
50
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
37
51
|
user: CoreUserModel = undefined;
|
|
38
52
|
|
|
39
53
|
// ===================================================================================================================
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Field, InputType } from '@nestjs/graphql';
|
|
2
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
3
|
+
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
2
4
|
|
|
3
5
|
import { Restricted } from '../../../common/decorators/restricted.decorator';
|
|
4
6
|
import { RoleEnum } from '../../../common/enums/role.enum';
|
|
@@ -14,19 +16,31 @@ export class CoreAuthSignInInput extends CoreInput {
|
|
|
14
16
|
// Properties
|
|
15
17
|
// ===================================================================================================================
|
|
16
18
|
|
|
17
|
-
@
|
|
19
|
+
@ApiPropertyOptional({ description: 'Device ID (is created automatically if it is not set)' })
|
|
18
20
|
@Field({ description: 'Device ID (is created automatically if it is not set)', nullable: true })
|
|
21
|
+
@IsOptional()
|
|
22
|
+
@IsString()
|
|
23
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
19
24
|
deviceId?: string = undefined;
|
|
20
25
|
|
|
21
|
-
@
|
|
26
|
+
@ApiPropertyOptional({ description: 'Device description' })
|
|
22
27
|
@Field({ description: 'Device description', nullable: true })
|
|
28
|
+
@IsOptional()
|
|
29
|
+
@IsString()
|
|
30
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
23
31
|
deviceDescription?: string = undefined;
|
|
24
32
|
|
|
25
|
-
@
|
|
33
|
+
@ApiProperty()
|
|
26
34
|
@Field({ description: 'Email', nullable: false })
|
|
35
|
+
@IsEmail()
|
|
36
|
+
@IsNotEmpty()
|
|
37
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
27
38
|
email: string = undefined;
|
|
28
39
|
|
|
29
|
-
@
|
|
40
|
+
@ApiProperty()
|
|
30
41
|
@Field({ description: 'Password', nullable: false })
|
|
42
|
+
@IsNotEmpty()
|
|
43
|
+
@IsString()
|
|
44
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
31
45
|
password: string = undefined;
|
|
32
46
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
2
|
import { Schema as MongooseSchema, Prop, raw } from '@nestjs/mongoose';
|
|
3
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
3
4
|
import { IsOptional } from 'class-validator';
|
|
4
5
|
import { Document } from 'mongoose';
|
|
5
6
|
|
|
@@ -24,60 +25,67 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
24
25
|
/**
|
|
25
26
|
* E-Mail address of the user
|
|
26
27
|
*/
|
|
27
|
-
@
|
|
28
|
+
@ApiProperty()
|
|
28
29
|
@Field({ description: 'Email of the user', nullable: true })
|
|
29
30
|
@Prop({ index: true, lowercase: true, trim: true })
|
|
31
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
30
32
|
email: string = undefined;
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* First name of the user
|
|
34
36
|
*/
|
|
35
|
-
@
|
|
37
|
+
@ApiProperty()
|
|
36
38
|
@Field({ description: 'First name of the user', nullable: true })
|
|
37
39
|
@IsOptional()
|
|
38
40
|
@Prop()
|
|
41
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
39
42
|
firstName: string = undefined;
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* Last name of the user
|
|
43
46
|
*/
|
|
44
|
-
@
|
|
47
|
+
@ApiProperty()
|
|
45
48
|
@Field({ description: 'Last name of the user', nullable: true })
|
|
46
49
|
@IsOptional()
|
|
47
50
|
@Prop()
|
|
51
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
48
52
|
lastName: string = undefined;
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
55
|
* Password of the user
|
|
52
56
|
*/
|
|
53
|
-
@
|
|
57
|
+
@ApiProperty()
|
|
54
58
|
@Prop()
|
|
59
|
+
@Restricted(RoleEnum.S_NO_ONE)
|
|
55
60
|
password: string = undefined;
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
63
|
* Roles of the user
|
|
59
64
|
*/
|
|
60
|
-
@
|
|
65
|
+
@ApiProperty()
|
|
61
66
|
@Field(type => [String], { description: 'Roles of the user', nullable: true })
|
|
62
67
|
@IsOptional()
|
|
63
68
|
@Prop([String])
|
|
69
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
64
70
|
roles: string[] = undefined;
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
73
|
* Username of the user
|
|
68
74
|
*/
|
|
69
|
-
@
|
|
75
|
+
@ApiProperty()
|
|
70
76
|
@Field({ description: 'Username of the user', nullable: true })
|
|
71
77
|
@IsOptional()
|
|
72
78
|
@Prop()
|
|
79
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
73
80
|
username: string = undefined;
|
|
74
81
|
|
|
75
82
|
/**
|
|
76
83
|
* Password reset token of the user
|
|
77
84
|
*/
|
|
78
|
-
@
|
|
85
|
+
@ApiProperty()
|
|
79
86
|
@IsOptional()
|
|
80
87
|
@Prop()
|
|
88
|
+
@Restricted(RoleEnum.S_NO_ONE)
|
|
81
89
|
passwordResetToken: string = undefined;
|
|
82
90
|
|
|
83
91
|
/**
|
|
@@ -85,42 +93,95 @@ export abstract class CoreUserModel extends CorePersistenceModel {
|
|
|
85
93
|
* key: Token
|
|
86
94
|
* value: TokenData
|
|
87
95
|
*/
|
|
88
|
-
@
|
|
96
|
+
@ApiProperty({ isArray: true })
|
|
89
97
|
@IsOptional()
|
|
90
98
|
@Prop(raw({}))
|
|
99
|
+
@Restricted(RoleEnum.S_NO_ONE)
|
|
100
|
+
@ApiProperty({
|
|
101
|
+
additionalProperties: {
|
|
102
|
+
properties: {
|
|
103
|
+
deviceDescription: { description: 'Description of the device from which the token was generated', nullable: true, type: 'string' },
|
|
104
|
+
deviceId: { description: 'ID of the device from which the token was generated', nullable: true, type: 'string' },
|
|
105
|
+
tokenId: { description: 'Token ID to make sure that there is only one RefreshToken for each device', nullable: false, type: 'string' },
|
|
106
|
+
},
|
|
107
|
+
type: 'object',
|
|
108
|
+
},
|
|
109
|
+
description: 'Refresh tokens for devices (key: Token, value: TokenData)',
|
|
110
|
+
example: {
|
|
111
|
+
'49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
|
|
112
|
+
deviceDescription: null,
|
|
113
|
+
deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
|
|
114
|
+
tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
|
|
115
|
+
},
|
|
116
|
+
'e9e60a3e-2004-479f-8e79-13a0d1981d76': {
|
|
117
|
+
deviceDescription: null,
|
|
118
|
+
deviceId: 'e9e60a3e-2004-479f-8e79-13a0d1981d76',
|
|
119
|
+
tokenId: '0604aa59-4fc8-4848-9fe7-c12d9cdf6ec0',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
type: 'object',
|
|
123
|
+
})
|
|
91
124
|
refreshTokens: Record<string, CoreTokenData> = undefined;
|
|
92
125
|
|
|
93
126
|
/**
|
|
94
127
|
* Temporary token for parallel requests during the token refresh process
|
|
95
128
|
* See sameTokenIdPeriod in configuration
|
|
96
129
|
*/
|
|
97
|
-
@
|
|
130
|
+
@ApiProperty()
|
|
98
131
|
@IsOptional()
|
|
99
132
|
@Prop(raw({}))
|
|
133
|
+
@Restricted(RoleEnum.S_NO_ONE)
|
|
134
|
+
@ApiProperty({
|
|
135
|
+
additionalProperties: {
|
|
136
|
+
properties: {
|
|
137
|
+
createdAt: { description: 'Token Created At', example: 1740037703939, format: 'int64', nullable: true, type: 'number' },
|
|
138
|
+
deviceId: { description: 'ID of the device from which the token was generated', nullable: true, type: 'string' },
|
|
139
|
+
tokenId: { description: 'Token ID to make sure that there is only one RefreshToken for each device', nullable: false, type: 'string' },
|
|
140
|
+
},
|
|
141
|
+
type: 'object',
|
|
142
|
+
},
|
|
143
|
+
description: 'Temporary token for parallel requests during the token refresh process',
|
|
144
|
+
example: { // 👈 Add explicit example keys
|
|
145
|
+
'49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb': {
|
|
146
|
+
createdAt: 1740037703939,
|
|
147
|
+
deviceId: '49b5c7d6-94ae-4efe-b377-9b50d1a9c2cb',
|
|
148
|
+
tokenId: '50937407-4282-480e-8679-14ecc113f9c7',
|
|
149
|
+
},
|
|
150
|
+
'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae': {
|
|
151
|
+
createdAt: 1740037703940,
|
|
152
|
+
deviceId: 'f83ae5f6-90bf-4b4e-b318-651e0eaa67ae',
|
|
153
|
+
tokenId: '4f0dc3c5-e74e-41f4-9bd9-642869462c1e',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
type: 'object',
|
|
157
|
+
})
|
|
100
158
|
tempTokens: Record<string, { createdAt: number; deviceId: string; tokenId: string }> = undefined;
|
|
101
159
|
|
|
102
160
|
/**
|
|
103
161
|
* Verification token of the user
|
|
104
162
|
*/
|
|
105
|
-
@
|
|
163
|
+
@ApiProperty()
|
|
106
164
|
@IsOptional()
|
|
107
165
|
@Prop()
|
|
166
|
+
@Restricted(RoleEnum.S_NO_ONE)
|
|
108
167
|
verificationToken: string = undefined;
|
|
109
168
|
|
|
110
169
|
/**
|
|
111
170
|
* Verification of the user
|
|
112
171
|
*/
|
|
113
|
-
@
|
|
172
|
+
@ApiProperty()
|
|
114
173
|
@Field(type => Boolean, { description: 'Verification state of the user', nullable: true })
|
|
115
174
|
@Prop({ type: Boolean })
|
|
175
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
116
176
|
verified: boolean = undefined;
|
|
117
177
|
|
|
118
178
|
/**
|
|
119
179
|
* Verification date
|
|
120
180
|
*/
|
|
121
|
-
@
|
|
181
|
+
@ApiProperty()
|
|
122
182
|
@Field({ description: 'Verified date', nullable: true })
|
|
123
183
|
@Prop()
|
|
184
|
+
@Restricted(RoleEnum.S_EVERYONE)
|
|
124
185
|
verifiedAt: Date = undefined;
|
|
125
186
|
|
|
126
187
|
// ===================================================================================================================
|
|
@@ -20,7 +20,8 @@ export class AuthModule {
|
|
|
20
20
|
*/
|
|
21
21
|
static forRoot(options: JwtModuleOptions): DynamicModule {
|
|
22
22
|
return {
|
|
23
|
-
|
|
23
|
+
controllers: [AuthController],
|
|
24
|
+
exports: [AuthController, AuthResolver, CoreAuthModule, AuthService],
|
|
24
25
|
imports: [
|
|
25
26
|
CoreAuthModule.forRoot(UserModule, UserService, {
|
|
26
27
|
...options,
|
|
@@ -8,6 +8,7 @@ import { DateScalar } from '../core/common/scalars/date.scalar';
|
|
|
8
8
|
import { JSON } from '../core/common/scalars/json.scalar';
|
|
9
9
|
import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
|
|
10
10
|
import { CronJobs } from './common/services/cron-jobs.service';
|
|
11
|
+
import { AuthController } from './modules/auth/auth.controller';
|
|
11
12
|
import { AuthModule } from './modules/auth/auth.module';
|
|
12
13
|
import { FileModule } from './modules/file/file.module';
|
|
13
14
|
import { ServerController } from './server.controller';
|
|
@@ -20,7 +21,7 @@ import { ServerController } from './server.controller';
|
|
|
20
21
|
*/
|
|
21
22
|
@Module({
|
|
22
23
|
// Include REST controllers
|
|
23
|
-
controllers: [ServerController],
|
|
24
|
+
controllers: [ServerController, AuthController],
|
|
24
25
|
|
|
25
26
|
// Export modules for reuse in other modules
|
|
26
27
|
exports: [CoreModule, AuthModule, FileModule],
|