@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "10.8.4",
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, Param, ParseBoolPipe, Post, Res, UseGuards } from '@nestjs/common';
2
- import { Args } from '@nestjs/graphql';
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
- @Param('allDevices', ParseBoolPipe) allDevices?: boolean,
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
- @Post()
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
- @Post()
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
- * JavaScript Web Token (JWT)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_NO_ONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_NO_ONE)
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
- @Restricted(RoleEnum.S_NO_ONE)
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
- @Restricted(RoleEnum.S_NO_ONE)
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
- @Restricted(RoleEnum.S_NO_ONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- @Restricted(RoleEnum.S_EVERYONE)
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
- exports: [AuthController, AuthResolver, CoreAuthModule],
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],