@lenne.tech/nest-server 9.0.31 → 9.2.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/dist/config.env.js +41 -2
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/filters/http-exception-log.filter.d.ts +4 -0
- package/dist/core/common/filters/http-exception-log.filter.js +30 -0
- package/dist/core/common/filters/http-exception-log.filter.js.map +1 -0
- package/dist/core/common/helpers/db.helper.d.ts +6 -2
- package/dist/core/common/helpers/db.helper.js +9 -4
- package/dist/core/common/helpers/db.helper.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +3 -0
- package/dist/core/common/helpers/input.helper.js +30 -1
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/interceptors/check-security.interceptor.d.ts +5 -0
- package/dist/core/common/interceptors/check-security.interceptor.js +47 -0
- package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +17 -6
- package/dist/core/common/interfaces/service-options.interface.d.ts +1 -0
- package/dist/core/common/models/core-model.model.d.ts +1 -0
- package/dist/core/common/models/core-model.model.js +3 -0
- package/dist/core/common/models/core-model.model.js.map +1 -1
- package/dist/core/common/plugins/complexity.plugin.d.ts +9 -0
- package/dist/core/common/plugins/complexity.plugin.js +47 -0
- package/dist/core/common/plugins/complexity.plugin.js.map +1 -0
- package/dist/core/common/plugins/mongoose-id.plugin.d.ts +1 -2
- package/dist/core/common/plugins/mongoose-id.plugin.js +7 -2
- package/dist/core/common/plugins/mongoose-id.plugin.js.map +1 -1
- package/dist/core/common/services/config.service.d.ts +4 -4
- package/dist/core/common/services/config.service.js.map +1 -1
- package/dist/core/common/services/module.service.d.ts +5 -1
- package/dist/core/common/services/module.service.js +15 -3
- package/dist/core/common/services/module.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.model.d.ts +4 -1
- package/dist/core/modules/auth/core-auth.model.js +12 -1
- package/dist/core/modules/auth/core-auth.model.js.map +1 -1
- package/dist/core/modules/auth/core-auth.module.d.ts +3 -1
- package/dist/core/modules/auth/core-auth.module.js +7 -2
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.d.ts +22 -2
- package/dist/core/modules/auth/core-auth.resolver.js +77 -9
- package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
- package/dist/core/modules/auth/guards/auth.guard.d.ts +1 -1
- package/dist/core/modules/auth/guards/auth.guard.js +9 -4
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/guards/refresh-token.guard.d.ts +4 -0
- package/dist/core/modules/auth/guards/refresh-token.guard.js +18 -0
- package/dist/core/modules/auth/guards/refresh-token.guard.js.map +1 -0
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.d.ts +1 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js +5 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-in.input.js.map +1 -1
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.d.ts +1 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js +5 -0
- package/dist/core/modules/auth/inputs/core-auth-sign-up.input.js.map +1 -1
- package/dist/core/modules/auth/interfaces/core-auth-user.interface.d.ts +3 -0
- package/dist/core/modules/auth/interfaces/jwt-payload.interface.d.ts +1 -1
- package/dist/core/modules/auth/services/core-auth-user.service.d.ts +3 -0
- package/dist/core/modules/auth/services/core-auth-user.service.js.map +1 -1
- package/dist/core/modules/auth/services/core-auth.service.d.ts +23 -5
- package/dist/core/modules/auth/services/core-auth.service.js +121 -13
- package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
- package/dist/core/modules/auth/strategies/jwt-refresh.strategy.d.ts +12 -0
- package/dist/core/modules/auth/strategies/jwt-refresh.strategy.js +61 -0
- package/dist/core/modules/auth/strategies/jwt-refresh.strategy.js.map +1 -0
- package/dist/core/modules/auth/{jwt.strategy.d.ts → strategies/jwt.strategy.d.ts} +4 -3
- package/dist/core/modules/auth/{jwt.strategy.js → strategies/jwt.strategy.js} +12 -5
- package/dist/core/modules/auth/strategies/jwt.strategy.js.map +1 -0
- package/dist/core/modules/user/core-user.model.d.ts +2 -0
- package/dist/core/modules/user/core-user.model.js +12 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +2 -2
- package/dist/core/modules/user/core-user.service.js +2 -2
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core.module.js +12 -2
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/main.js +23 -0
- package/dist/main.js.map +1 -1
- package/dist/server/modules/auth/auth.model.js.map +1 -1
- package/dist/server/modules/auth/auth.resolver.d.ts +13 -5
- package/dist/server/modules/auth/auth.resolver.js +21 -12
- package/dist/server/modules/auth/auth.resolver.js.map +1 -1
- package/dist/server/modules/auth/auth.service.d.ts +2 -1
- package/dist/server/modules/auth/auth.service.js +7 -48
- package/dist/server/modules/auth/auth.service.js.map +1 -1
- package/dist/server/modules/file/file.module.js +3 -3
- package/dist/server/modules/file/file.module.js.map +1 -1
- package/dist/server/modules/user/user.model.d.ts +1 -0
- package/dist/server/modules/user/user.model.js +19 -0
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.service.js +1 -1
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/server/server.module.js +12 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +32 -27
- package/src/config.env.ts +41 -2
- package/src/core/common/filters/http-exception-log.filter.ts +27 -0
- package/src/core/common/helpers/db.helper.ts +15 -5
- package/src/core/common/helpers/input.helper.ts +49 -0
- package/src/core/common/interceptors/check-security.interceptor.ts +51 -0
- package/src/core/common/interfaces/server-options.interface.ts +75 -30
- package/src/core/common/interfaces/service-options.interface.ts +4 -0
- package/src/core/common/models/core-model.model.ts +7 -0
- package/src/core/common/plugins/complexity.plugin.ts +31 -0
- package/src/core/common/plugins/mongoose-id.plugin.js +4 -2
- package/src/core/common/services/config.service.ts +4 -4
- package/src/core/common/services/module.service.ts +26 -4
- package/src/core/modules/auth/core-auth.model.ts +15 -2
- package/src/core/modules/auth/core-auth.module.ts +8 -2
- package/src/core/modules/auth/core-auth.resolver.ts +93 -10
- package/src/core/modules/auth/guards/auth.guard.ts +12 -5
- package/src/core/modules/auth/guards/refresh-token.guard.ts +5 -0
- package/src/core/modules/auth/guards/roles.guard.ts +1 -1
- package/src/core/modules/auth/inputs/core-auth-sign-in.input.ts +3 -0
- package/src/core/modules/auth/inputs/core-auth-sign-up.input.ts +3 -0
- package/src/core/modules/auth/interfaces/core-auth-user.interface.ts +15 -0
- package/src/core/modules/auth/interfaces/jwt-payload.interface.ts +1 -1
- package/src/core/modules/auth/services/core-auth-user.service.ts +15 -0
- package/src/core/modules/auth/services/core-auth.service.ts +216 -18
- package/src/core/modules/auth/strategies/jwt-refresh.strategy.ts +56 -0
- package/src/core/modules/auth/{jwt.strategy.ts → strategies/jwt.strategy.ts} +16 -5
- package/src/core/modules/user/core-user.model.ts +17 -1
- package/src/core/modules/user/core-user.service.ts +2 -2
- package/src/core.module.ts +14 -2
- package/src/index.ts +6 -1
- package/src/main.ts +29 -0
- package/src/server/modules/auth/auth.model.ts +1 -1
- package/src/server/modules/auth/auth.resolver.ts +26 -8
- package/src/server/modules/auth/auth.service.ts +20 -61
- package/src/server/modules/file/file.module.ts +3 -3
- package/src/server/modules/user/user.model.ts +29 -0
- package/src/server/modules/user/user.service.ts +1 -1
- package/src/server/server.module.ts +12 -1
- package/dist/core/modules/auth/jwt.strategy.js.map +0 -1
|
@@ -7,11 +7,17 @@ import { prepareInput, prepareOutput } from '../helpers/service.helper';
|
|
|
7
7
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
8
8
|
import { CoreModel } from '../models/core-model.model';
|
|
9
9
|
import { FieldSelection } from '../types/field-selection.type';
|
|
10
|
+
import { ConfigService } from './config.service';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Module service class to be extended by concrete module services
|
|
13
14
|
*/
|
|
14
15
|
export abstract class ModuleService<T extends CoreModel = any> {
|
|
16
|
+
/**
|
|
17
|
+
* Config service, is used to determine certain behavior
|
|
18
|
+
*/
|
|
19
|
+
protected configService: ConfigService;
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* Main model constructor of the service, will be used as default for populate and mapping
|
|
17
23
|
*/
|
|
@@ -26,9 +32,11 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
26
32
|
* Set main properties
|
|
27
33
|
*/
|
|
28
34
|
protected constructor(options?: {
|
|
29
|
-
|
|
35
|
+
configService?: ConfigService;
|
|
36
|
+
mainDbModel?: Model<T & Document>;
|
|
30
37
|
mainModelConstructor?: new (...args: any[]) => T;
|
|
31
38
|
}) {
|
|
39
|
+
this.configService = options?.configService;
|
|
32
40
|
this.mainDbModel = options?.mainDbModel;
|
|
33
41
|
this.mainModelConstructor = options?.mainModelConstructor;
|
|
34
42
|
}
|
|
@@ -90,13 +98,21 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
90
98
|
...options?.serviceOptions,
|
|
91
99
|
};
|
|
92
100
|
|
|
101
|
+
// Set default for ignoreSelections if not set
|
|
102
|
+
const ignoreSelections = this.configService?.getFastButReadOnly('ignoreSelectionsForPopulate', false);
|
|
103
|
+
if (ignoreSelections) {
|
|
104
|
+
if (config.processFieldSelection.ignoreSelections === undefined) {
|
|
105
|
+
config.processFieldSelection.ignoreSelections = ignoreSelections;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
// Note force configuration
|
|
94
110
|
if (config.force) {
|
|
95
111
|
config.checkRights = false;
|
|
96
|
-
if (typeof config.prepareInput === 'object') {
|
|
112
|
+
if (config.prepareInput && typeof config.prepareInput === 'object') {
|
|
97
113
|
config.prepareInput.checkRoles = false;
|
|
98
114
|
}
|
|
99
|
-
if (typeof config.prepareOutput === 'object') {
|
|
115
|
+
if (config.prepareOutput && typeof config.prepareOutput === 'object') {
|
|
100
116
|
config.prepareOutput.removeSecrets = false;
|
|
101
117
|
}
|
|
102
118
|
}
|
|
@@ -104,6 +120,9 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
104
120
|
// Note populate
|
|
105
121
|
if (config.populate) {
|
|
106
122
|
config.fieldSelection = config.populate;
|
|
123
|
+
if (config.processFieldSelection?.ignoreSelections) {
|
|
124
|
+
config.processFieldSelection.ignoreSelections = false;
|
|
125
|
+
}
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
// Prepare input
|
|
@@ -220,6 +239,7 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
220
239
|
options: {
|
|
221
240
|
model?: new (...args: any[]) => T;
|
|
222
241
|
dbModel?: Model<T & Document>;
|
|
242
|
+
ignoreSelections?: boolean;
|
|
223
243
|
} = {}
|
|
224
244
|
) {
|
|
225
245
|
const config = {
|
|
@@ -227,6 +247,8 @@ export abstract class ModuleService<T extends CoreModel = any> {
|
|
|
227
247
|
dbModel: this.mainDbModel,
|
|
228
248
|
...options,
|
|
229
249
|
};
|
|
230
|
-
return popAndMap(data, fieldsSelection, config.model, config.dbModel
|
|
250
|
+
return popAndMap(data, fieldsSelection, config.model, config.dbModel, {
|
|
251
|
+
ignoreSelections: config.ignoreSelections,
|
|
252
|
+
});
|
|
231
253
|
}
|
|
232
254
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Field, ObjectType } from '@nestjs/graphql';
|
|
2
2
|
import { CoreModel } from '../../common/models/core-model.model';
|
|
3
|
+
import { CoreUserModel } from '../user/core-user.model';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* CoreAuth model for the response after the sign in
|
|
@@ -13,8 +14,20 @@ export class CoreAuthModel extends CoreModel {
|
|
|
13
14
|
/**
|
|
14
15
|
* JavaScript Web Token (JWT)
|
|
15
16
|
*/
|
|
16
|
-
@Field({ description: 'JavaScript Web Token (JWT)' })
|
|
17
|
-
token
|
|
17
|
+
@Field({ description: 'JavaScript Web Token (JWT)', nullable: true })
|
|
18
|
+
token?: string = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Refresh token
|
|
22
|
+
*/
|
|
23
|
+
@Field({ description: 'Refresh token', nullable: true })
|
|
24
|
+
refreshToken?: string = undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Current user
|
|
28
|
+
*/
|
|
29
|
+
@Field({ description: 'Current user' })
|
|
30
|
+
user: CoreUserModel = undefined;
|
|
18
31
|
|
|
19
32
|
// ===================================================================================================================
|
|
20
33
|
// Methods
|
|
@@ -3,7 +3,8 @@ import { APP_GUARD } from '@nestjs/core';
|
|
|
3
3
|
import { JwtModule, JwtModuleOptions } from '@nestjs/jwt';
|
|
4
4
|
import { PassportModule } from '@nestjs/passport';
|
|
5
5
|
import { RolesGuard } from './guards/roles.guard';
|
|
6
|
-
import {
|
|
6
|
+
import { JwtRefreshStrategy } from './strategies/jwt-refresh.strategy';
|
|
7
|
+
import { JwtStrategy } from './strategies/jwt.strategy';
|
|
7
8
|
import { CoreAuthUserService } from './services/core-auth-user.service';
|
|
8
9
|
import { CoreAuthService } from './services/core-auth.service';
|
|
9
10
|
import { PubSub } from 'graphql-subscriptions';
|
|
@@ -23,6 +24,7 @@ export class CoreAuthModule {
|
|
|
23
24
|
options: JwtModuleOptions & {
|
|
24
25
|
authService?: Type<CoreAuthService>;
|
|
25
26
|
jwtStrategy?: Type<JwtStrategy>;
|
|
27
|
+
jwtRefreshStrategy?: Type<JwtRefreshStrategy>;
|
|
26
28
|
imports?: Array<Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
|
|
27
29
|
providers?: Provider[];
|
|
28
30
|
}
|
|
@@ -56,6 +58,10 @@ export class CoreAuthModule {
|
|
|
56
58
|
provide: JwtStrategy,
|
|
57
59
|
useClass: options.jwtStrategy || JwtStrategy,
|
|
58
60
|
},
|
|
61
|
+
{
|
|
62
|
+
provide: JwtRefreshStrategy,
|
|
63
|
+
useClass: options.jwtRefreshStrategy || JwtRefreshStrategy,
|
|
64
|
+
},
|
|
59
65
|
];
|
|
60
66
|
if (Array.isArray(options?.providers)) {
|
|
61
67
|
providers = imports.concat(options.providers);
|
|
@@ -66,7 +72,7 @@ export class CoreAuthModule {
|
|
|
66
72
|
module: CoreAuthModule,
|
|
67
73
|
imports: imports,
|
|
68
74
|
providers: providers,
|
|
69
|
-
exports: [CoreAuthService, JwtModule, JwtStrategy, PassportModule, UserModule],
|
|
75
|
+
exports: [CoreAuthService, JwtModule, JwtStrategy, JwtRefreshStrategy, PassportModule, UserModule],
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UseGuards } from '@nestjs/common';
|
|
2
|
+
import { Args, Context, Info, Mutation, Resolver } from '@nestjs/graphql';
|
|
3
|
+
import { Response as ResponseType } from 'express';
|
|
2
4
|
import { GraphQLResolveInfo } from 'graphql';
|
|
5
|
+
import { GraphQLUser } from '../../common/decorators/graphql-user.decorator';
|
|
6
|
+
import { ConfigService } from '../../common/services/config.service';
|
|
3
7
|
import { CoreAuthModel } from './core-auth.model';
|
|
8
|
+
import { AuthGuard } from './guards/auth.guard';
|
|
9
|
+
import { CoreAuthSignInInput } from './inputs/core-auth-sign-in.input';
|
|
10
|
+
import { CoreAuthSignUpInput } from './inputs/core-auth-sign-up.input';
|
|
11
|
+
import { ICoreAuthUser } from './interfaces/core-auth-user.interface';
|
|
4
12
|
import { CoreAuthService } from './services/core-auth.service';
|
|
5
13
|
|
|
6
14
|
/**
|
|
@@ -11,21 +19,96 @@ export class CoreAuthResolver {
|
|
|
11
19
|
/**
|
|
12
20
|
* Import services
|
|
13
21
|
*/
|
|
14
|
-
constructor(protected readonly authService: CoreAuthService) {}
|
|
22
|
+
constructor(protected readonly authService: CoreAuthService, protected readonly configService: ConfigService) {}
|
|
15
23
|
|
|
16
24
|
// ===========================================================================
|
|
17
|
-
//
|
|
25
|
+
// Mutations
|
|
18
26
|
// ===========================================================================
|
|
19
27
|
|
|
20
28
|
/**
|
|
21
|
-
*
|
|
29
|
+
* Sign in user via email and password (on specific device)
|
|
22
30
|
*/
|
|
23
|
-
@
|
|
31
|
+
@Mutation((returns) => CoreAuthModel, {
|
|
32
|
+
description: 'Sign in user via email and password and get JWT tokens (for specific device)',
|
|
33
|
+
})
|
|
24
34
|
async signIn(
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
@
|
|
28
|
-
): Promise<
|
|
29
|
-
|
|
35
|
+
@Info() info: GraphQLResolveInfo,
|
|
36
|
+
@Context() ctx: { res: ResponseType },
|
|
37
|
+
@Args('input') input: CoreAuthSignInInput
|
|
38
|
+
): Promise<CoreAuthModel> {
|
|
39
|
+
const result = await this.authService.signIn(input, { fieldSelection: { info, select: 'signIn' } });
|
|
40
|
+
return this.processCookies(ctx, result);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Logout user (from specific device)
|
|
45
|
+
*/
|
|
46
|
+
@Mutation((returns) => CoreAuthModel, { description: 'Logout user (from specific device)' })
|
|
47
|
+
async logout(
|
|
48
|
+
@GraphQLUser() currentUser: ICoreAuthUser,
|
|
49
|
+
@Context() ctx: { res: ResponseType },
|
|
50
|
+
@Args('deviceId', { nullable: true }) deviceId?: string
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const result = await this.authService.logout({ currentUser, deviceId });
|
|
53
|
+
return this.processCookies(ctx, result);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Refresh token (for specific device)
|
|
58
|
+
*/
|
|
59
|
+
@UseGuards(AuthGuard('jwt-refresh'))
|
|
60
|
+
@Mutation((returns) => CoreAuthModel, { description: 'Refresh tokens (for specific device)' })
|
|
61
|
+
async refreshToken(
|
|
62
|
+
@GraphQLUser() user: ICoreAuthUser,
|
|
63
|
+
@Context() ctx: { res: ResponseType },
|
|
64
|
+
@Args('deviceId', { nullable: true }) deviceId?: string
|
|
65
|
+
): Promise<CoreAuthModel> {
|
|
66
|
+
const result = await this.authService.refreshTokens(user, deviceId);
|
|
67
|
+
return this.processCookies(ctx, result);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register a new user account (on specific device)
|
|
72
|
+
*/
|
|
73
|
+
@Mutation((returns) => CoreAuthModel, { description: 'Register a new user account (on specific device)' })
|
|
74
|
+
async signUp(
|
|
75
|
+
@Info() info: GraphQLResolveInfo,
|
|
76
|
+
@Context() ctx: { res: ResponseType },
|
|
77
|
+
@Args('input') input: CoreAuthSignUpInput
|
|
78
|
+
): Promise<CoreAuthModel> {
|
|
79
|
+
const result = await this.authService.signUp(input, { fieldSelection: { info, select: 'signUp' } });
|
|
80
|
+
return this.processCookies(ctx, result);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ===================================================================================================================
|
|
84
|
+
// Helper
|
|
85
|
+
// ===================================================================================================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Process cookies
|
|
89
|
+
*/
|
|
90
|
+
protected processCookies(ctx: { res: ResponseType }, result: any) {
|
|
91
|
+
// Check if cookie handling is activated
|
|
92
|
+
if (this.configService.getFastButReadOnly('cookies')) {
|
|
93
|
+
// Set cookies
|
|
94
|
+
if (typeof result !== 'object') {
|
|
95
|
+
ctx.res.cookie('token', '', { httpOnly: true });
|
|
96
|
+
ctx.res.cookie('refreshToken', '', { httpOnly: true });
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
ctx.res.cookie('token', result?.token || '', { httpOnly: true });
|
|
100
|
+
ctx.res.cookie('refreshToken', result?.refreshToken || '', { httpOnly: true });
|
|
101
|
+
|
|
102
|
+
// Remove tokens from result
|
|
103
|
+
if (result.token) {
|
|
104
|
+
delete result.token;
|
|
105
|
+
}
|
|
106
|
+
if (result.refreshToken) {
|
|
107
|
+
delete result.refreshToken;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Return prepared result
|
|
112
|
+
return result;
|
|
30
113
|
}
|
|
31
114
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext, Logger, mixin, Optional, UnauthorizedException } from '@nestjs/common';
|
|
2
|
+
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
2
3
|
import { AuthModuleOptions, Type } from '@nestjs/passport';
|
|
3
4
|
import { defaultOptions } from '@nestjs/passport/dist/options';
|
|
4
5
|
import { memoize } from '@nestjs/passport/dist/utils/memoize.util';
|
|
@@ -65,10 +66,7 @@ function createAuthGuard(type?: string): Type<CanActivate> {
|
|
|
65
66
|
|
|
66
67
|
const options = { ...defaultOptions, ...this.options };
|
|
67
68
|
const response = context?.switchToHttp()?.getResponse();
|
|
68
|
-
|
|
69
|
-
if (!request) {
|
|
70
|
-
request = context?.switchToHttp()?.getRequest();
|
|
71
|
-
}
|
|
69
|
+
const request = this.getRequest(context);
|
|
72
70
|
const passportFn = createPassportContext(request, response);
|
|
73
71
|
const user = await passportFn(type || this.options.defaultStrategy, options, (err, currentUser, info) =>
|
|
74
72
|
this.handleRequest(err, currentUser, info, context)
|
|
@@ -81,6 +79,15 @@ function createAuthGuard(type?: string): Type<CanActivate> {
|
|
|
81
79
|
* Prepare request
|
|
82
80
|
*/
|
|
83
81
|
getRequest<T = any>(context: ExecutionContext): T {
|
|
82
|
+
// Try to get request GraphQL context
|
|
83
|
+
try {
|
|
84
|
+
const ctx = GqlExecutionContext.create(context)?.getContext();
|
|
85
|
+
if (ctx?.req) {
|
|
86
|
+
return ctx.req;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {}
|
|
89
|
+
|
|
90
|
+
// Else return HTTP request
|
|
84
91
|
return context && context.switchToHttp() ? context.switchToHttp().getRequest() : null;
|
|
85
92
|
}
|
|
86
93
|
|
|
@@ -110,4 +117,4 @@ function createAuthGuard(type?: string): Type<CanActivate> {
|
|
|
110
117
|
/**
|
|
111
118
|
* Export AuthGuard
|
|
112
119
|
*/
|
|
113
|
-
export const AuthGuard: (type?: string) => Type<IAuthGuard> = memoize(createAuthGuard);
|
|
120
|
+
export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> = memoize(createAuthGuard);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext,
|
|
1
|
+
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
2
2
|
import { Reflector } from '@nestjs/core';
|
|
3
3
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
4
4
|
import { RoleEnum } from '../../../common/enums/role.enum';
|
|
@@ -10,6 +10,9 @@ export class CoreAuthSignInInput extends CoreInput {
|
|
|
10
10
|
// Properties
|
|
11
11
|
// ===================================================================================================================
|
|
12
12
|
|
|
13
|
+
@Field({ description: 'Device ID', nullable: true })
|
|
14
|
+
deviceId?: string = undefined;
|
|
15
|
+
|
|
13
16
|
@Field({ description: 'Email', nullable: false })
|
|
14
17
|
email: string = undefined;
|
|
15
18
|
|
|
@@ -10,6 +10,9 @@ export class CoreAuthSignUpInput extends CoreInput {
|
|
|
10
10
|
// Properties
|
|
11
11
|
// ===================================================================================================================
|
|
12
12
|
|
|
13
|
+
@Field({ description: 'Device ID', nullable: true })
|
|
14
|
+
deviceId?: string = undefined;
|
|
15
|
+
|
|
13
16
|
@Field({ description: 'Email', nullable: false })
|
|
14
17
|
email: string = undefined;
|
|
15
18
|
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Interface for user used in authorization module
|
|
3
3
|
*/
|
|
4
4
|
export interface ICoreAuthUser {
|
|
5
|
+
/**
|
|
6
|
+
* ID of the user
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Email of the user
|
|
7
12
|
*/
|
|
@@ -11,4 +16,14 @@ export interface ICoreAuthUser {
|
|
|
11
16
|
* Password of the user
|
|
12
17
|
*/
|
|
13
18
|
password: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Refresh token
|
|
22
|
+
*/
|
|
23
|
+
refreshToken?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Refresh tokens for different devices
|
|
27
|
+
*/
|
|
28
|
+
refreshTokens?: Record<string, string>;
|
|
14
29
|
}
|
|
@@ -5,6 +5,16 @@ import { ICoreAuthUser } from '../interfaces/core-auth-user.interface';
|
|
|
5
5
|
* Abstract class for user service in authorization module
|
|
6
6
|
*/
|
|
7
7
|
export abstract class CoreAuthUserService {
|
|
8
|
+
/**
|
|
9
|
+
* Create user
|
|
10
|
+
*/
|
|
11
|
+
abstract create(input: any, serviceOptions?: ServiceOptions): Promise<ICoreAuthUser>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get user via ID
|
|
15
|
+
*/
|
|
16
|
+
abstract get(id: string, serviceOptions?: ServiceOptions): Promise<ICoreAuthUser>;
|
|
17
|
+
|
|
8
18
|
/**
|
|
9
19
|
* Get user via email
|
|
10
20
|
*/
|
|
@@ -14,4 +24,9 @@ export abstract class CoreAuthUserService {
|
|
|
14
24
|
* Prepare output
|
|
15
25
|
*/
|
|
16
26
|
abstract prepareOutput(output: any, options?: ServiceOptions): Promise<ICoreAuthUser>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Update user
|
|
30
|
+
*/
|
|
31
|
+
abstract update(id: string, input: any, serviceOptions?: ServiceOptions): Promise<ICoreAuthUser>;
|
|
17
32
|
}
|
|
@@ -2,7 +2,13 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
|
2
2
|
import { JwtService } from '@nestjs/jwt';
|
|
3
3
|
import * as bcrypt from 'bcrypt';
|
|
4
4
|
import { sha256 } from 'js-sha256';
|
|
5
|
+
import { getStringIds } from '../../../common/helpers/db.helper';
|
|
6
|
+
import { prepareServiceOptions } from '../../../common/helpers/service.helper';
|
|
5
7
|
import { ServiceOptions } from '../../../common/interfaces/service-options.interface';
|
|
8
|
+
import { ConfigService } from '../../../common/services/config.service';
|
|
9
|
+
import { CoreAuthModel } from '../core-auth.model';
|
|
10
|
+
import { CoreAuthSignInInput } from '../inputs/core-auth-sign-in.input';
|
|
11
|
+
import { CoreAuthSignUpInput } from '../inputs/core-auth-sign-up.input';
|
|
6
12
|
import { ICoreAuthUser } from '../interfaces/core-auth-user.interface';
|
|
7
13
|
import { JwtPayload } from '../interfaces/jwt-payload.interface';
|
|
8
14
|
import { CoreAuthUserService } from './core-auth-user.service';
|
|
@@ -12,18 +18,82 @@ import { CoreAuthUserService } from './core-auth-user.service';
|
|
|
12
18
|
*/
|
|
13
19
|
@Injectable()
|
|
14
20
|
export class CoreAuthService {
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Integrate services
|
|
23
|
+
*/
|
|
24
|
+
constructor(
|
|
25
|
+
protected readonly userService: CoreAuthUserService,
|
|
26
|
+
protected readonly jwtService: JwtService,
|
|
27
|
+
protected readonly configService: ConfigService
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Decode JWT
|
|
32
|
+
*/
|
|
33
|
+
decodeJwt(token: string): JwtPayload {
|
|
34
|
+
return this.jwtService.decode(token) as JwtPayload;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Logout user (from device)
|
|
39
|
+
*/
|
|
40
|
+
async logout(serviceOptions: ServiceOptions & { deviceId?: string }): Promise<boolean> {
|
|
41
|
+
const user = serviceOptions.currentUser;
|
|
42
|
+
if (!serviceOptions.currentUser) {
|
|
43
|
+
throw new UnauthorizedException();
|
|
44
|
+
}
|
|
45
|
+
const deviceId = serviceOptions.deviceId;
|
|
46
|
+
if (deviceId) {
|
|
47
|
+
if (!user.refreshTokens[deviceId]) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
delete user.refreshTokens[deviceId];
|
|
51
|
+
await this.userService.update(user.id, { refreshTokens: user.refreshTokens }, serviceOptions);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
user.refreshToken = null;
|
|
55
|
+
user.refreshTokens = {};
|
|
56
|
+
await this.userService.update(
|
|
57
|
+
user.id,
|
|
58
|
+
{
|
|
59
|
+
refreshToken: user.refreshToken,
|
|
60
|
+
refreshTokens: user.refreshTokens,
|
|
61
|
+
},
|
|
62
|
+
serviceOptions
|
|
63
|
+
);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Refresh tokens
|
|
69
|
+
*/
|
|
70
|
+
async refreshTokens(user: ICoreAuthUser, deviceId?: string) {
|
|
71
|
+
// Create new tokens
|
|
72
|
+
const tokens = await this.getTokens(user.id);
|
|
73
|
+
await this.updateRefreshToken(user, tokens.refreshToken, { deviceId });
|
|
74
|
+
|
|
75
|
+
// Return
|
|
76
|
+
return CoreAuthModel.map({
|
|
77
|
+
...tokens,
|
|
78
|
+
user: await this.userService.prepareOutput(user),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
16
81
|
|
|
17
82
|
/**
|
|
18
83
|
* User sign in via email
|
|
19
84
|
*/
|
|
20
|
-
async signIn(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
85
|
+
async signIn(input: CoreAuthSignInInput, serviceOptions?: ServiceOptions): Promise<CoreAuthModel> {
|
|
86
|
+
// Prepare service options
|
|
87
|
+
const serviceOptionsForUserService = prepareServiceOptions(serviceOptions, {
|
|
88
|
+
// We need password, so we can't use prepare output handling and have to deactivate it
|
|
89
|
+
prepareOutput: null,
|
|
90
|
+
|
|
91
|
+
// Select user field for automatic populate handling via user service
|
|
92
|
+
subFieldSelection: 'user',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Inputs
|
|
96
|
+
const { email, password, deviceId } = input;
|
|
27
97
|
|
|
28
98
|
// Get user
|
|
29
99
|
const user = await this.userService.getViaEmail(email, serviceOptions);
|
|
@@ -34,25 +104,153 @@ export class CoreAuthService {
|
|
|
34
104
|
throw new UnauthorizedException();
|
|
35
105
|
}
|
|
36
106
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
107
|
+
// Set device ID
|
|
108
|
+
serviceOptionsForUserService.deviceId = input.deviceId;
|
|
109
|
+
|
|
110
|
+
// Return tokens and user
|
|
111
|
+
return this.getResult(user, serviceOptions);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register a new user account
|
|
116
|
+
*/
|
|
117
|
+
async signUp(input: CoreAuthSignUpInput, serviceOptions?: ServiceOptions): Promise<CoreAuthModel> {
|
|
118
|
+
// Prepare service options
|
|
119
|
+
const serviceOptionsForUserService = prepareServiceOptions(serviceOptions, {
|
|
120
|
+
// Select user field for automatic populate handling via user service
|
|
121
|
+
subFieldSelection: 'user',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Get and check user
|
|
125
|
+
const user = await this.userService.create(input, serviceOptionsForUserService);
|
|
126
|
+
if (!user) {
|
|
127
|
+
throw Error('Email Address already in use');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Set device ID
|
|
131
|
+
serviceOptionsForUserService.deviceId = input.deviceId;
|
|
132
|
+
|
|
133
|
+
// Return tokens and user
|
|
134
|
+
return this.getResult(user, serviceOptionsForUserService);
|
|
43
135
|
}
|
|
44
136
|
|
|
45
137
|
/**
|
|
46
138
|
* Validate user
|
|
47
139
|
*/
|
|
48
140
|
async validateUser(payload: JwtPayload): Promise<any> {
|
|
49
|
-
|
|
141
|
+
// Get user
|
|
142
|
+
const user = await this.userService.get(payload.id);
|
|
143
|
+
|
|
144
|
+
// Check if user exists and is logged in
|
|
145
|
+
if (!user?.refreshToken) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Return user
|
|
150
|
+
return user;
|
|
50
151
|
}
|
|
51
152
|
|
|
153
|
+
// ===================================================================================================================
|
|
154
|
+
// Helper
|
|
155
|
+
// ===================================================================================================================
|
|
156
|
+
|
|
52
157
|
/**
|
|
53
|
-
*
|
|
158
|
+
* Rest result with user and tokens
|
|
54
159
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
160
|
+
protected async getResult(user: ICoreAuthUser, serviceOptions: ServiceOptions & { deviceId?: string }) {
|
|
161
|
+
// Create new tokens
|
|
162
|
+
const tokens = await this.getTokens(user.id);
|
|
163
|
+
|
|
164
|
+
// Set refresh token
|
|
165
|
+
await this.updateRefreshToken(user, tokens.refreshToken, serviceOptions);
|
|
166
|
+
|
|
167
|
+
// Return tokens and user
|
|
168
|
+
return CoreAuthModel.map({
|
|
169
|
+
...tokens,
|
|
170
|
+
user: await this.userService.prepareOutput(user),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get secret from JWT or refresh config
|
|
176
|
+
*/
|
|
177
|
+
protected getSecretFromConfig(refresh?: boolean) {
|
|
178
|
+
let path = 'jwt';
|
|
179
|
+
if (refresh) {
|
|
180
|
+
path += '.refresh';
|
|
181
|
+
}
|
|
182
|
+
return (
|
|
183
|
+
this.configService.getFastButReadOnly(path + '.signInOptions.secret') ||
|
|
184
|
+
this.configService.getFastButReadOnly(path + '.signInOptions.secretOrPrivateKey') ||
|
|
185
|
+
this.configService.getFastButReadOnly(path + '.secret') ||
|
|
186
|
+
this.configService.getFastButReadOnly(path + '.secretOrPrivateKey')
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get JWT and refresh token
|
|
192
|
+
*/
|
|
193
|
+
protected async getTokens(userId: string) {
|
|
194
|
+
const [token, refreshToken] = await Promise.all([
|
|
195
|
+
this.jwtService.signAsync(
|
|
196
|
+
{ id: userId },
|
|
197
|
+
{
|
|
198
|
+
secret: this.getSecretFromConfig(false),
|
|
199
|
+
...this.configService.getFastButReadOnly('jwt.signInOptions', {}),
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
this.jwtService.signAsync(
|
|
203
|
+
{ id: userId },
|
|
204
|
+
{
|
|
205
|
+
secret: this.getSecretFromConfig(true),
|
|
206
|
+
...this.configService.getFastButReadOnly('jwt.refresh.signInOptions', {}),
|
|
207
|
+
}
|
|
208
|
+
),
|
|
209
|
+
]);
|
|
210
|
+
return {
|
|
211
|
+
token,
|
|
212
|
+
refreshToken,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update refresh token(s)
|
|
218
|
+
*/
|
|
219
|
+
protected async updateRefreshToken(
|
|
220
|
+
user: ICoreAuthUser,
|
|
221
|
+
refreshToken: string,
|
|
222
|
+
serviceOptions: ServiceOptions & { deviceId?: string } = {}
|
|
223
|
+
) {
|
|
224
|
+
const hashedRefreshToken = await bcrypt.hash(refreshToken, 10);
|
|
225
|
+
const deviceId = serviceOptions?.deviceId;
|
|
226
|
+
if (deviceId) {
|
|
227
|
+
if (!user.refreshTokens) {
|
|
228
|
+
user.refreshTokens = {};
|
|
229
|
+
}
|
|
230
|
+
user.refreshTokens[deviceId] = hashedRefreshToken;
|
|
231
|
+
|
|
232
|
+
// Refresh token must be set even if only a specific device is logged in, because of the check in the validateUser method
|
|
233
|
+
if (!user.refreshToken) {
|
|
234
|
+
user.refreshToken = hashedRefreshToken;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return await this.userService.update(
|
|
238
|
+
getStringIds(user),
|
|
239
|
+
{ refreshTokens: user.refreshTokens, refreshToken: user.refreshToken },
|
|
240
|
+
{
|
|
241
|
+
...serviceOptions,
|
|
242
|
+
force: true,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
user.refreshToken = hashedRefreshToken;
|
|
247
|
+
return await this.userService.update(
|
|
248
|
+
getStringIds(user),
|
|
249
|
+
{ refreshToken: hashedRefreshToken },
|
|
250
|
+
{
|
|
251
|
+
...serviceOptions,
|
|
252
|
+
force: true,
|
|
253
|
+
}
|
|
254
|
+
);
|
|
57
255
|
}
|
|
58
256
|
}
|