@node-c/domain-iam 1.0.0-alpha9 → 1.0.0-beta1
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/common/definitions/common.constants.d.ts +7 -1
- package/dist/common/definitions/common.constants.js +6 -0
- package/dist/common/definitions/common.constants.js.map +1 -1
- package/dist/module/iam.module.js.map +1 -1
- package/dist/services/authentication/iam.authentication.definitions.d.ts +79 -16
- package/dist/services/authentication/iam.authentication.definitions.js +6 -9
- package/dist/services/authentication/iam.authentication.definitions.js.map +1 -1
- package/dist/services/authentication/iam.authentication.service.d.ts +13 -5
- package/dist/services/authentication/iam.authentication.service.js +32 -3
- package/dist/services/authentication/iam.authentication.service.js.map +1 -1
- package/dist/services/authenticationOAuth2/iam.authenticationOAuth2.definitions.d.ts +38 -0
- package/dist/services/{authenticationLocal/iam.authenticationLocal.definitions.js → authenticationOAuth2/iam.authenticationOAuth2.definitions.js} +1 -1
- package/dist/services/authenticationOAuth2/iam.authenticationOAuth2.definitions.js.map +1 -0
- package/dist/services/authenticationOAuth2/iam.authenticationOAuth2.service.d.ts +25 -0
- package/dist/services/authenticationOAuth2/iam.authenticationOAuth2.service.js +300 -0
- package/dist/services/authenticationOAuth2/iam.authenticationOAuth2.service.js.map +1 -0
- package/dist/services/authenticationOAuth2/index.d.ts +2 -0
- package/dist/services/authenticationOAuth2/index.js +19 -0
- package/dist/services/authenticationOAuth2/index.js.map +1 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.definitions.d.ts +12 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.definitions.js +3 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.definitions.js.map +1 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.service.d.ts +15 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.service.js +142 -0
- package/dist/services/authenticationUserLocal/iam.authenticationUserLocal.service.js.map +1 -0
- package/dist/services/authenticationUserLocal/index.d.ts +2 -0
- package/dist/services/{authenticationLocal → authenticationUserLocal}/index.js +2 -2
- package/dist/services/authenticationUserLocal/index.js.map +1 -0
- package/dist/services/authorization/iam.authorization.definitions.d.ts +33 -23
- package/dist/services/authorization/iam.authorization.definitions.js +7 -0
- package/dist/services/authorization/iam.authorization.definitions.js.map +1 -1
- package/dist/services/authorization/iam.authorization.service.d.ts +29 -13
- package/dist/services/authorization/iam.authorization.service.js +233 -125
- package/dist/services/authorization/iam.authorization.service.js.map +1 -1
- package/dist/services/index.d.ts +4 -2
- package/dist/services/index.js +4 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/mfa/iam.mfa.definitions.d.ts +21 -0
- package/dist/services/mfa/iam.mfa.definitions.js +8 -0
- package/dist/services/mfa/iam.mfa.definitions.js.map +1 -0
- package/dist/services/mfa/iam.mfa.service.d.ts +10 -0
- package/dist/services/mfa/iam.mfa.service.js +32 -0
- package/dist/services/mfa/iam.mfa.service.js.map +1 -0
- package/dist/services/mfa/index.d.ts +2 -0
- package/dist/services/{users → mfa}/index.js +2 -2
- package/dist/services/mfa/index.js.map +1 -0
- package/dist/services/tokenManager/iam.tokenManager.definitions.d.ts +14 -3
- package/dist/services/tokenManager/iam.tokenManager.definitions.js.map +1 -1
- package/dist/services/tokenManager/iam.tokenManager.service.d.ts +24 -9
- package/dist/services/tokenManager/iam.tokenManager.service.js +113 -44
- package/dist/services/tokenManager/iam.tokenManager.service.js.map +1 -1
- package/dist/services/userManager/iam.userManager.definitions.d.ts +45 -0
- package/dist/services/userManager/iam.userManager.definitions.js +8 -0
- package/dist/services/userManager/iam.userManager.definitions.js.map +1 -0
- package/dist/services/userManager/iam.userManager.service.d.ts +33 -0
- package/dist/services/userManager/iam.userManager.service.js +332 -0
- package/dist/services/userManager/iam.userManager.service.js.map +1 -0
- package/dist/services/userManager/index.d.ts +2 -0
- package/dist/services/userManager/index.js +19 -0
- package/dist/services/userManager/index.js.map +1 -0
- package/package.json +10 -8
- package/src/common/definitions/common.constants.ts +16 -0
- package/src/common/definitions/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/module/iam.definitions.ts +15 -0
- package/src/module/iam.module.ts +29 -0
- package/src/module/index.ts +2 -0
- package/src/services/authentication/iam.authentication.definitions.ts +100 -0
- package/src/services/authentication/iam.authentication.service.ts +105 -0
- package/src/services/authentication/index.ts +2 -0
- package/src/services/authenticationOAuth2/iam.authenticationOAuth2.definitions.ts +72 -0
- package/src/services/authenticationOAuth2/iam.authenticationOAuth2.service.ts +352 -0
- package/src/services/authenticationOAuth2/index.ts +2 -0
- package/src/services/authenticationUserLocal/iam.authenticationUserLocal.definitions.ts +29 -0
- package/src/services/authenticationUserLocal/iam.authenticationUserLocal.service.ts +173 -0
- package/src/services/authenticationUserLocal/index.ts +2 -0
- package/src/services/authorization/iam.authorization.definitions.ts +55 -0
- package/src/services/authorization/iam.authorization.service.ts +387 -0
- package/src/services/authorization/index.ts +2 -0
- package/src/services/index.ts +7 -0
- package/src/services/mfa/iam.mfa.definitions.ts +28 -0
- package/src/services/mfa/iam.mfa.service.ts +40 -0
- package/src/services/mfa/index.ts +2 -0
- package/src/services/tokenManager/iam.tokenManager.definitions.ts +61 -0
- package/src/services/tokenManager/iam.tokenManager.service.ts +292 -0
- package/src/services/tokenManager/index.ts +2 -0
- package/src/services/userManager/iam.userManager.definitions.ts +73 -0
- package/src/services/userManager/iam.userManager.service.ts +463 -0
- package/src/services/userManager/index.ts +2 -0
- package/dist/services/authenticationLocal/iam.authenticationLocal.definitions.d.ts +0 -11
- package/dist/services/authenticationLocal/iam.authenticationLocal.definitions.js.map +0 -1
- package/dist/services/authenticationLocal/iam.authenticationLocal.service.d.ts +0 -10
- package/dist/services/authenticationLocal/iam.authenticationLocal.service.js +0 -70
- package/dist/services/authenticationLocal/iam.authenticationLocal.service.js.map +0 -1
- package/dist/services/authenticationLocal/index.d.ts +0 -2
- package/dist/services/authenticationLocal/index.js.map +0 -1
- package/dist/services/users/iam.users.definitions.d.ts +0 -30
- package/dist/services/users/iam.users.definitions.js +0 -8
- package/dist/services/users/iam.users.definitions.js.map +0 -1
- package/dist/services/users/iam.users.service.d.ts +0 -16
- package/dist/services/users/iam.users.service.js +0 -93
- package/dist/services/users/iam.users.service.js.map +0 -1
- package/dist/services/users/index.d.ts +0 -2
- package/dist/services/users/index.js.map +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ApplicationError, ConfigProviderService, LoggerService } from '@node-c/core';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
IAMMFACompleteData,
|
|
5
|
+
IAMMFACompleteOptions,
|
|
6
|
+
IAMMFACompleteResult,
|
|
7
|
+
IAMMFAInitiateData,
|
|
8
|
+
IAMMFAInitiateOptions,
|
|
9
|
+
IAMMFAInitiateResult
|
|
10
|
+
} from './iam.mfa.definitions';
|
|
11
|
+
|
|
12
|
+
// TODO: local MFA implementation
|
|
13
|
+
export class IAMMFAService<CompleteContext extends object, InitiateContext extends object = object> {
|
|
14
|
+
constructor(
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
protected configProvider: ConfigProviderService,
|
|
17
|
+
// eslint-disable-next-line no-unused-vars
|
|
18
|
+
protected logger: LoggerService,
|
|
19
|
+
// eslint-disable-next-line no-unused-vars
|
|
20
|
+
protected moduleName: string
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
async complete(
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
25
|
+
_data: IAMMFACompleteData,
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
27
|
+
_options: IAMMFACompleteOptions<CompleteContext>
|
|
28
|
+
): Promise<IAMMFACompleteResult> {
|
|
29
|
+
throw new ApplicationError(`[${this.moduleName}][IAMMFAService]: Method "complete" not implemented.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async initiate(
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
34
|
+
_data: IAMMFAInitiateData,
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
36
|
+
_options: IAMMFAInitiateOptions<InitiateContext>
|
|
37
|
+
): Promise<IAMMFAInitiateResult> {
|
|
38
|
+
throw new ApplicationError(`[${this.moduleName}][IAMMFAService]: Method "initiate" not implemented.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DomainCreateOptions } from '@node-c/core';
|
|
2
|
+
|
|
3
|
+
import { IAMAuthenticationType, IAMAuthenticationVerifyExternalAccessTokenResult } from '../authentication';
|
|
4
|
+
|
|
5
|
+
export interface BaseTokenEntityFields {
|
|
6
|
+
externalToken?: string;
|
|
7
|
+
externalTokenAuthService?: IAMAuthenticationType;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DecodedTokenContent<TokenEntityFields> = {
|
|
11
|
+
exp?: number;
|
|
12
|
+
iat: number;
|
|
13
|
+
data?: TokenEntityFields & BaseTokenEntityFields;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type TokenEntity<TokenEntityFields extends object> = {
|
|
17
|
+
token: string;
|
|
18
|
+
type: TokenType;
|
|
19
|
+
} & TokenEntityFields &
|
|
20
|
+
BaseTokenEntityFields;
|
|
21
|
+
|
|
22
|
+
export type TokenManagerCreateData<TokenEntityFields extends object> = Partial<
|
|
23
|
+
Omit<TokenEntity<TokenEntityFields>, 'token'>
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
export type TokenManagerCreateOptions = {
|
|
27
|
+
expiresInMinutes?: number;
|
|
28
|
+
identifierDataField?: string;
|
|
29
|
+
persist?: boolean;
|
|
30
|
+
purgeOldFromData?: boolean;
|
|
31
|
+
tokenContentOnlyFields?: string[];
|
|
32
|
+
ttl?: number;
|
|
33
|
+
} & DomainCreateOptions;
|
|
34
|
+
|
|
35
|
+
export enum TokenType {
|
|
36
|
+
// eslint-disable-next-line no-unused-vars
|
|
37
|
+
Access = 'access',
|
|
38
|
+
// eslint-disable-next-line no-unused-vars
|
|
39
|
+
Refresh = 'refresh'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TokenManagerVerifyResult<TokenEntityFields> {
|
|
43
|
+
content?: DecodedTokenContent<TokenEntityFields>;
|
|
44
|
+
externalTokenData?: IAMAuthenticationVerifyExternalAccessTokenResult;
|
|
45
|
+
error?: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface VerifyAccessTokenOptions {
|
|
49
|
+
deleteFromStoreIfExpired?: boolean;
|
|
50
|
+
identifierDataField?: string;
|
|
51
|
+
newTokenExpiresInMinutes?: number;
|
|
52
|
+
persistNewToken?: boolean;
|
|
53
|
+
purgeStoreOnRenew?: boolean;
|
|
54
|
+
refreshToken?: string;
|
|
55
|
+
refreshTokenAccessTokenIdentifierDataField?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface VerifyAccessTokenReturnData<TokenEntityFields> {
|
|
59
|
+
content?: DecodedTokenContent<TokenEntityFields>;
|
|
60
|
+
newToken?: string;
|
|
61
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppConfigDomainIAM,
|
|
3
|
+
ApplicationError,
|
|
4
|
+
ConfigProviderService,
|
|
5
|
+
DataEntityService,
|
|
6
|
+
DomainCreateOptions,
|
|
7
|
+
DomainCreateResult,
|
|
8
|
+
DomainEntityService,
|
|
9
|
+
GenericObject,
|
|
10
|
+
LoggerService,
|
|
11
|
+
setNested
|
|
12
|
+
} from '@node-c/core';
|
|
13
|
+
|
|
14
|
+
import * as jwt from 'jsonwebtoken';
|
|
15
|
+
import ld from 'lodash';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
DecodedTokenContent,
|
|
19
|
+
TokenEntity,
|
|
20
|
+
TokenManagerCreateData,
|
|
21
|
+
TokenManagerCreateOptions,
|
|
22
|
+
TokenManagerVerifyResult,
|
|
23
|
+
TokenType,
|
|
24
|
+
VerifyAccessTokenOptions,
|
|
25
|
+
VerifyAccessTokenReturnData
|
|
26
|
+
} from './iam.tokenManager.definitions';
|
|
27
|
+
|
|
28
|
+
import { Constants } from '../../common/definitions';
|
|
29
|
+
import { IAMAuthenticationService, IAMAuthenticationType } from '../authentication';
|
|
30
|
+
import { IAMAuthenticationOAuth2Service } from '../authenticationOAuth2';
|
|
31
|
+
import { IAMAuthenticationUserLocalService } from '../authenticationUserLocal';
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Service for managing local access and refresh JWTs.
|
|
35
|
+
*/
|
|
36
|
+
export class IAMTokenManagerService<TokenEntityFields extends object> {
|
|
37
|
+
constructor(
|
|
38
|
+
// eslint-disable-next-line no-unused-vars
|
|
39
|
+
// protected authServices: Record<string, IAMAuthenticationService<object, object>>,
|
|
40
|
+
// eslint-disable-next-line no-unused-vars
|
|
41
|
+
protected authServices: {
|
|
42
|
+
[IAMAuthenticationType.OAuth2]?: IAMAuthenticationOAuth2Service<object, object>;
|
|
43
|
+
[IAMAuthenticationType.UserLocal]?: IAMAuthenticationUserLocalService<object, object>;
|
|
44
|
+
} & { [serviceName: string]: IAMAuthenticationService<object, object> },
|
|
45
|
+
// eslint-disable-next-line no-unused-vars
|
|
46
|
+
protected configProvider: ConfigProviderService,
|
|
47
|
+
// eslint-disable-next-line no-unused-vars
|
|
48
|
+
protected domainTokensEntityService: DomainEntityService<
|
|
49
|
+
TokenEntity<TokenEntityFields>,
|
|
50
|
+
DataEntityService<TokenEntity<TokenEntityFields>>
|
|
51
|
+
>,
|
|
52
|
+
// eslint-disable-next-line no-unused-vars
|
|
53
|
+
protected logger: LoggerService,
|
|
54
|
+
// eslint-disable-next-line no-unused-vars
|
|
55
|
+
protected moduleName: string
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
async create(
|
|
59
|
+
data: TokenManagerCreateData<TokenEntityFields>,
|
|
60
|
+
options: TokenManagerCreateOptions
|
|
61
|
+
): Promise<DomainCreateResult<TokenEntity<TokenEntityFields>>> {
|
|
62
|
+
const { configProvider, logger, moduleName, domainTokensEntityService } = this;
|
|
63
|
+
const moduleConfig = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
64
|
+
const { type, ...tokenData } = data;
|
|
65
|
+
const { expiresInMinutes, identifierDataField, persist, purgeOldFromData, tokenContentOnlyFields } = options;
|
|
66
|
+
const signOptions = {} as jwt.SignOptions;
|
|
67
|
+
let secret: string;
|
|
68
|
+
// Leaving this big and ugly if-statement as is, in case we need to expand it in the future.
|
|
69
|
+
if (type === TokenType.Access) {
|
|
70
|
+
secret = moduleConfig.jwtAccessSecret;
|
|
71
|
+
if (expiresInMinutes) {
|
|
72
|
+
signOptions.expiresIn = expiresInMinutes * 60;
|
|
73
|
+
} else if (moduleConfig.accessTokenExpiryTimeInMinutes) {
|
|
74
|
+
signOptions.expiresIn = moduleConfig.accessTokenExpiryTimeInMinutes * 60;
|
|
75
|
+
}
|
|
76
|
+
} else if (type === TokenType.Refresh) {
|
|
77
|
+
secret = moduleConfig.jwtRefreshSecret;
|
|
78
|
+
if (expiresInMinutes) {
|
|
79
|
+
signOptions.expiresIn = expiresInMinutes * 60;
|
|
80
|
+
} else if (moduleConfig.refreshTokenExpiryTimeInMinutes) {
|
|
81
|
+
signOptions.expiresIn = moduleConfig.refreshTokenExpiryTimeInMinutes * 60;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
throw new ApplicationError(`[TokenManager.create]: Invalid token type - "${type}".`);
|
|
85
|
+
}
|
|
86
|
+
const token = await new Promise<string>((resolve, reject) => {
|
|
87
|
+
jwt.sign({ data }, secret, signOptions, (err, token) => {
|
|
88
|
+
if (err) {
|
|
89
|
+
logger.error(err);
|
|
90
|
+
reject(new ApplicationError('Failed to sign token.'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
resolve(token as string);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
const objectToSave = { ...tokenData, token, type } as TokenEntity<TokenEntityFields>;
|
|
97
|
+
if (tokenContentOnlyFields?.length) {
|
|
98
|
+
tokenContentOnlyFields.forEach(fieldName =>
|
|
99
|
+
setNested(objectToSave, fieldName, undefined, { removeNestedFieldEscapeSign: true })
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
// save the token in the data system of choice
|
|
103
|
+
// TODO: multi-data isn't handled well here (or, actually, at all)
|
|
104
|
+
if (persist) {
|
|
105
|
+
if (purgeOldFromData && identifierDataField) {
|
|
106
|
+
const identifierValue = ld.get(data, identifierDataField);
|
|
107
|
+
if (typeof identifierValue !== 'undefined' && typeof identifierValue !== 'object') {
|
|
108
|
+
await domainTokensEntityService.delete(
|
|
109
|
+
{
|
|
110
|
+
filters: { [identifierDataField]: identifierValue, type }
|
|
111
|
+
},
|
|
112
|
+
{ requirePrimaryKeys: true }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await domainTokensEntityService.create(objectToSave, { ttl: signOptions.expiresIn } as DomainCreateOptions);
|
|
117
|
+
}
|
|
118
|
+
return { result: objectToSave };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// TODO: delete from store at the end
|
|
122
|
+
async verifyAccessToken(
|
|
123
|
+
token: string,
|
|
124
|
+
options?: VerifyAccessTokenOptions
|
|
125
|
+
): Promise<VerifyAccessTokenReturnData<TokenEntityFields>> {
|
|
126
|
+
const { configProvider, domainTokensEntityService, logger, moduleName } = this;
|
|
127
|
+
const moduleConfig = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
128
|
+
const {
|
|
129
|
+
deleteFromStoreIfExpired,
|
|
130
|
+
identifierDataField,
|
|
131
|
+
newTokenExpiresInMinutes,
|
|
132
|
+
persistNewToken,
|
|
133
|
+
purgeStoreOnRenew,
|
|
134
|
+
refreshToken,
|
|
135
|
+
refreshTokenAccessTokenIdentifierDataField
|
|
136
|
+
} = options || {};
|
|
137
|
+
// decode the token
|
|
138
|
+
const { content, error, externalTokenData } = await this.verify(token, moduleConfig.jwtAccessSecret, {
|
|
139
|
+
// TODO: make this configurable
|
|
140
|
+
verifyExternal: true
|
|
141
|
+
});
|
|
142
|
+
const externalAccessTokenExpired = !!externalTokenData?.error;
|
|
143
|
+
const internalAccessTokenExpired = error === Constants.TOKEN_EXPIRED_ERROR;
|
|
144
|
+
let errorMessageToLog: string | undefined;
|
|
145
|
+
let externalRenewEnabled = false;
|
|
146
|
+
let newToken: string | undefined;
|
|
147
|
+
let refreshTokenContent: DecodedTokenContent<object> | undefined;
|
|
148
|
+
let renewEnabled = false;
|
|
149
|
+
let throwError = true;
|
|
150
|
+
// check whether the local and/or external access tokens have expired
|
|
151
|
+
if (internalAccessTokenExpired || externalAccessTokenExpired) {
|
|
152
|
+
// prepare renewal if the necessary data is present
|
|
153
|
+
if (identifierDataField && content?.data) {
|
|
154
|
+
if (refreshToken && refreshTokenAccessTokenIdentifierDataField) {
|
|
155
|
+
// internal refresh token verification
|
|
156
|
+
const { content: rtc, error: refreshTokenError } = await this.verify(
|
|
157
|
+
refreshToken,
|
|
158
|
+
moduleConfig.jwtRefreshSecret
|
|
159
|
+
);
|
|
160
|
+
refreshTokenContent = rtc;
|
|
161
|
+
if (!refreshTokenContent) {
|
|
162
|
+
errorMessageToLog = '[IAMTokenManagerService.verifyAccessToken]: Empty internal refresh token.';
|
|
163
|
+
} else if (refreshTokenError) {
|
|
164
|
+
errorMessageToLog = refreshTokenError as string;
|
|
165
|
+
// delete the refresh token from the store
|
|
166
|
+
if (deleteFromStoreIfExpired && refreshTokenContent.data) {
|
|
167
|
+
const identifierValue = ld.get(refreshTokenContent.data, refreshTokenAccessTokenIdentifierDataField);
|
|
168
|
+
if (typeof identifierValue !== 'undefined' && typeof identifierValue !== 'object') {
|
|
169
|
+
await domainTokensEntityService.delete(
|
|
170
|
+
{
|
|
171
|
+
filters: { [refreshTokenAccessTokenIdentifierDataField]: identifierValue, token: refreshToken }
|
|
172
|
+
},
|
|
173
|
+
{ requirePrimaryKeys: true }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const refreshTokenCheckValue = ld.get(content.data, refreshTokenAccessTokenIdentifierDataField);
|
|
179
|
+
if (refreshTokenCheckValue !== refreshToken) {
|
|
180
|
+
errorMessageToLog = '[IAMTokenManagerService.verifyAccessToken]: Mismatched internal refresh token.';
|
|
181
|
+
} else {
|
|
182
|
+
renewEnabled = true;
|
|
183
|
+
throwError = false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// external token renewal preparation
|
|
187
|
+
if (externalAccessTokenExpired) {
|
|
188
|
+
if (refreshTokenContent?.data?.externalToken) {
|
|
189
|
+
externalRenewEnabled = true;
|
|
190
|
+
renewEnabled = true;
|
|
191
|
+
throwError = false;
|
|
192
|
+
} else {
|
|
193
|
+
errorMessageToLog = '[IAMTokenManagerService.verifyAccessToken]: Missing external refresh token.';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// no renewal - delete from store if enabled and prepare to throw an error
|
|
198
|
+
else {
|
|
199
|
+
errorMessageToLog =
|
|
200
|
+
'[IAMTokenManagerService.verifyAccessToken]: Access token expired & no refresh token data present or configured.';
|
|
201
|
+
if (deleteFromStoreIfExpired) {
|
|
202
|
+
const identifierValue = ld.get(content.data, identifierDataField);
|
|
203
|
+
if (typeof identifierValue !== 'undefined' && typeof identifierValue !== 'object') {
|
|
204
|
+
await domainTokensEntityService.delete(
|
|
205
|
+
{
|
|
206
|
+
filters: { [identifierDataField]: identifierValue, token }
|
|
207
|
+
},
|
|
208
|
+
{ requirePrimaryKeys: true }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// otherwise, simply throw an error
|
|
215
|
+
else {
|
|
216
|
+
errorMessageToLog = '[IAMTokenManagerService.verify]: Internal access token expired.';
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
throwError = false;
|
|
220
|
+
}
|
|
221
|
+
if (throwError) {
|
|
222
|
+
logger.error(errorMessageToLog);
|
|
223
|
+
throw new ApplicationError('Expired access token.');
|
|
224
|
+
}
|
|
225
|
+
// renewal
|
|
226
|
+
if (content?.data && renewEnabled) {
|
|
227
|
+
const tokenData: TokenManagerCreateData<GenericObject<unknown>> = { ...content.data, type: TokenType.Access };
|
|
228
|
+
if (refreshToken && refreshTokenAccessTokenIdentifierDataField) {
|
|
229
|
+
tokenData[refreshTokenAccessTokenIdentifierDataField] = refreshToken;
|
|
230
|
+
}
|
|
231
|
+
if (externalRenewEnabled) {
|
|
232
|
+
const externalAccessTokenRenewalResult = await this.authServices[
|
|
233
|
+
refreshTokenContent!.data!.externalTokenAuthService!
|
|
234
|
+
]!.refreshExternalAccessToken({
|
|
235
|
+
accessToken: content.data!.externalToken!,
|
|
236
|
+
refreshToken: refreshTokenContent!.data!.externalToken!
|
|
237
|
+
});
|
|
238
|
+
if (externalAccessTokenRenewalResult.error) {
|
|
239
|
+
// TODO: delete from store
|
|
240
|
+
logger.error(errorMessageToLog);
|
|
241
|
+
throw new ApplicationError('Expired access token.');
|
|
242
|
+
}
|
|
243
|
+
// TODO: save the new refresh token, if such exists
|
|
244
|
+
tokenData.externalToken = externalAccessTokenRenewalResult.newAccessToken;
|
|
245
|
+
}
|
|
246
|
+
const { result } = await this.create(tokenData as TokenManagerCreateData<TokenEntityFields>, {
|
|
247
|
+
expiresInMinutes: newTokenExpiresInMinutes,
|
|
248
|
+
identifierDataField,
|
|
249
|
+
persist: persistNewToken,
|
|
250
|
+
purgeOldFromData: purgeStoreOnRenew
|
|
251
|
+
});
|
|
252
|
+
newToken = result.token;
|
|
253
|
+
}
|
|
254
|
+
return { content, newToken };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected async verify(
|
|
258
|
+
token: string,
|
|
259
|
+
secret: string,
|
|
260
|
+
options?: { forceVerifyExternal?: boolean; verifyExternal?: boolean }
|
|
261
|
+
): Promise<TokenManagerVerifyResult<TokenEntityFields>> {
|
|
262
|
+
const { configProvider, moduleName } = this;
|
|
263
|
+
const moduleConfig = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
264
|
+
const { forceVerifyExternal, verifyExternal } = options || {};
|
|
265
|
+
const data = await new Promise<{ content?: DecodedTokenContent<TokenEntityFields>; error?: unknown }>(resolve => {
|
|
266
|
+
jwt.verify(token, secret, (err, decoded) => {
|
|
267
|
+
if (err) {
|
|
268
|
+
resolve({ content: decoded as DecodedTokenContent<TokenEntityFields>, error: err });
|
|
269
|
+
}
|
|
270
|
+
resolve({ content: decoded as DecodedTokenContent<TokenEntityFields> });
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
// TODO: move this logic to the verifyAccessToken method.
|
|
274
|
+
const returnData: TokenManagerVerifyResult<TokenEntityFields> = { ...data };
|
|
275
|
+
const tokenPayload = data.content?.data;
|
|
276
|
+
if (verifyExternal && tokenPayload?.externalToken && tokenPayload?.externalTokenAuthService) {
|
|
277
|
+
const authServiceConfig = moduleConfig.authServiceSettings?.[tokenPayload?.externalTokenAuthService];
|
|
278
|
+
if (authServiceConfig?.processExternalTokensOnVerify || forceVerifyExternal) {
|
|
279
|
+
const authService = this.authServices[tokenPayload?.externalTokenAuthService];
|
|
280
|
+
if (!authService) {
|
|
281
|
+
throw new ApplicationError(
|
|
282
|
+
`[IAMTokenManagerService.verify]: Auth service ${tokenPayload?.externalTokenAuthService} not configured.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
returnData.externalTokenData = await authService.verifyExternalAccessToken({
|
|
286
|
+
accessToken: tokenPayload?.externalToken
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return returnData;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppConfigCommonDomainIAMAuthServiceConfigCompleteSettings,
|
|
3
|
+
AppConfigCommonDomainIAMAuthServiceConfigInitiateSettings,
|
|
4
|
+
AppConfigDomainIAMAuthenticationStep,
|
|
5
|
+
DomainFindOnePrivateOptions,
|
|
6
|
+
GenericObject
|
|
7
|
+
} from '@node-c/core';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
IAMAuthenticationCompleteResult,
|
|
11
|
+
IAMAuthenticationInitiateResult,
|
|
12
|
+
IAMAuthenticationService,
|
|
13
|
+
IAMAuthenticationType
|
|
14
|
+
} from '../authentication';
|
|
15
|
+
import { AuthorizationUser } from '../authorization';
|
|
16
|
+
import { IAMMFAType } from '../mfa';
|
|
17
|
+
|
|
18
|
+
export interface IAMUserManagerCreateAccessTokenOptions<AuthData = unknown> {
|
|
19
|
+
auth: {
|
|
20
|
+
mfaType?: IAMMFAType;
|
|
21
|
+
type: IAMAuthenticationType | string;
|
|
22
|
+
} & AuthData;
|
|
23
|
+
filters?: GenericObject;
|
|
24
|
+
mainFilterField: string;
|
|
25
|
+
rememberUser?: boolean;
|
|
26
|
+
step?: AppConfigDomainIAMAuthenticationStep;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type IAMUserManagerCreateAccessTokenReturnData<UserData> =
|
|
30
|
+
| {
|
|
31
|
+
accessToken: string;
|
|
32
|
+
refreshToken?: string;
|
|
33
|
+
user: UserData;
|
|
34
|
+
}
|
|
35
|
+
| { nextStepsRequired: boolean };
|
|
36
|
+
|
|
37
|
+
export type IAMUserManagerExecuteStepData<AuthData = unknown> = Omit<
|
|
38
|
+
IAMUserManagerCreateAccessTokenOptions<AuthData>,
|
|
39
|
+
'rememberUser' | 'step'
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
export interface IAMUserManagerExecuteStepOptions<User extends object> {
|
|
43
|
+
authService: IAMAuthenticationService<User, User>;
|
|
44
|
+
name: AppConfigDomainIAMAuthenticationStep;
|
|
45
|
+
stepConfig:
|
|
46
|
+
| AppConfigCommonDomainIAMAuthServiceConfigCompleteSettings
|
|
47
|
+
| AppConfigCommonDomainIAMAuthServiceConfigInitiateSettings;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface IAMUserManagerExecuteStepResult<User extends object> {
|
|
51
|
+
stepResult: IAMAuthenticationCompleteResult | IAMAuthenticationInitiateResult;
|
|
52
|
+
user: IAMUserManagerUserWithPermissionsData<User, unknown> | null;
|
|
53
|
+
userFilterField?: string | undefined;
|
|
54
|
+
userFilterValue?: unknown | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface IAMUserManagerGetUserWithPermissionsDataOptions extends DomainFindOnePrivateOptions {
|
|
58
|
+
keepPassword?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type IAMUserManagerUserWithPermissionsData<UserData, AuthorizationPointId> =
|
|
62
|
+
AuthorizationUser<AuthorizationPointId> & UserData;
|
|
63
|
+
|
|
64
|
+
export interface IAMUserManagerUserTokenEnityFields<UserId = unknown> {
|
|
65
|
+
refreshToken?: string;
|
|
66
|
+
userId: UserId;
|
|
67
|
+
user?: IAMUserManagerUserWithPermissionsData<object, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export enum IAMUserManagerUserTokenUserIdentifier {
|
|
71
|
+
// eslint-disable-next-line no-unused-vars
|
|
72
|
+
FieldName = 'userId'
|
|
73
|
+
}
|