@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,173 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AppConfigDomainIAM,
|
|
5
|
+
AppConfigDomainIAMAuthenticationStep,
|
|
6
|
+
ApplicationError,
|
|
7
|
+
ConfigProviderService,
|
|
8
|
+
LoggerService
|
|
9
|
+
} from '@node-c/core';
|
|
10
|
+
|
|
11
|
+
import ld from 'lodash';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
IAMAuthenticationUserLocalCompleteData,
|
|
15
|
+
IAMAuthenticationUserLocalCompleteOptions,
|
|
16
|
+
IAMAuthenticationUserLocalCompleteResult,
|
|
17
|
+
IAMAuthenticationUserLocalGetUserCreateAccessTokenConfigResult,
|
|
18
|
+
IAMAuthenticationUserLocalInitiateData,
|
|
19
|
+
IAMAuthenticationUserLocalInitiateOptions,
|
|
20
|
+
IAMAuthenticationUserLocalInitiateResult
|
|
21
|
+
} from './iam.authenticationUserLocal.definitions';
|
|
22
|
+
|
|
23
|
+
import { IAMAuthenticationService } from '../authentication';
|
|
24
|
+
import { IAMMFAService, IAMMFAType } from '../mfa';
|
|
25
|
+
|
|
26
|
+
// TODO: add a LocalSecret service to take care of the hashing logic and reuse it here
|
|
27
|
+
export class IAMAuthenticationUserLocalService<
|
|
28
|
+
CompleteContext extends object,
|
|
29
|
+
InitiateContext extends object
|
|
30
|
+
> extends IAMAuthenticationService<CompleteContext, InitiateContext> {
|
|
31
|
+
constructor(
|
|
32
|
+
protected configProvider: ConfigProviderService,
|
|
33
|
+
protected logger: LoggerService,
|
|
34
|
+
protected moduleName: string,
|
|
35
|
+
// eslint-disable-next-line no-unused-vars
|
|
36
|
+
protected serviceName: string,
|
|
37
|
+
// eslint-disable-next-line no-unused-vars
|
|
38
|
+
protected mfaServices?: Record<IAMMFAType, IAMMFAService<object, object>>
|
|
39
|
+
) {
|
|
40
|
+
super(configProvider, logger, moduleName);
|
|
41
|
+
this.isLocal = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async complete(
|
|
45
|
+
data: IAMAuthenticationUserLocalCompleteData,
|
|
46
|
+
options: IAMAuthenticationUserLocalCompleteOptions<CompleteContext>
|
|
47
|
+
): Promise<IAMAuthenticationUserLocalCompleteResult> {
|
|
48
|
+
const { configProvider, logger, moduleName, mfaServices, serviceName } = this;
|
|
49
|
+
const { defaultUserIdentifierField } = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
50
|
+
const { mfaData, mfaType } = data;
|
|
51
|
+
const { context, mfaOptions } = options;
|
|
52
|
+
const userIdentifierField = options.contextIdentifierField || defaultUserIdentifierField;
|
|
53
|
+
const userIdentifierValue = context[userIdentifierField as keyof CompleteContext];
|
|
54
|
+
let mfaUsed = false;
|
|
55
|
+
let mfaValid = false;
|
|
56
|
+
if (mfaType) {
|
|
57
|
+
const mfaService = mfaServices?.[mfaType];
|
|
58
|
+
if (!mfaService) {
|
|
59
|
+
logger.error(
|
|
60
|
+
`[${moduleName}][${serviceName}]: Login attempt failed for user "${userIdentifierValue}" - MFA service ${mfaType} not configured.`
|
|
61
|
+
);
|
|
62
|
+
throw new ApplicationError('Authentication failed.');
|
|
63
|
+
}
|
|
64
|
+
if (!mfaData) {
|
|
65
|
+
logger.error(
|
|
66
|
+
`[${moduleName}][${serviceName}]: Login attempt failed for user "${userIdentifierValue}" - no MFA data provided.`
|
|
67
|
+
);
|
|
68
|
+
throw new ApplicationError('Authentication failed.');
|
|
69
|
+
}
|
|
70
|
+
const mfaResult = await mfaService.complete(mfaData, { ...(mfaOptions || {}), context });
|
|
71
|
+
mfaUsed = true;
|
|
72
|
+
mfaValid = mfaResult.valid;
|
|
73
|
+
}
|
|
74
|
+
return { mfaUsed, mfaValid, valid: true };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getUserCreateAccessTokenConfig(): IAMAuthenticationUserLocalGetUserCreateAccessTokenConfigResult {
|
|
78
|
+
const { configProvider, moduleName, serviceName } = this;
|
|
79
|
+
const moduleConfig = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
80
|
+
const { steps } = moduleConfig.authServiceSettings![serviceName];
|
|
81
|
+
const defaultConfig: IAMAuthenticationUserLocalGetUserCreateAccessTokenConfigResult = {
|
|
82
|
+
[AppConfigDomainIAMAuthenticationStep.Complete]: {
|
|
83
|
+
cache: {
|
|
84
|
+
settings: {
|
|
85
|
+
cacheFieldName: 'userId',
|
|
86
|
+
inputFieldName: 'options.context.id'
|
|
87
|
+
},
|
|
88
|
+
use: {
|
|
89
|
+
options: { overwrite: true, use: true }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
findUser: true,
|
|
93
|
+
findUserBeforeAuth: true,
|
|
94
|
+
validWithoutUser: false
|
|
95
|
+
},
|
|
96
|
+
[AppConfigDomainIAMAuthenticationStep.Initiate]: {
|
|
97
|
+
cache: {
|
|
98
|
+
populate: {
|
|
99
|
+
options: [{ cacheFieldName: 'context', inputFieldName: 'options.context' }]
|
|
100
|
+
},
|
|
101
|
+
settings: {
|
|
102
|
+
cacheFieldName: 'userId',
|
|
103
|
+
inputFieldName: 'options.context.id'
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
findUser: true,
|
|
107
|
+
findUserBeforeAuth: true,
|
|
108
|
+
validWithoutUser: false
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return ld.merge(defaultConfig, steps || {});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async initiate(
|
|
115
|
+
data: IAMAuthenticationUserLocalInitiateData,
|
|
116
|
+
options: IAMAuthenticationUserLocalInitiateOptions<InitiateContext>
|
|
117
|
+
): Promise<IAMAuthenticationUserLocalInitiateResult> {
|
|
118
|
+
const { configProvider, logger, moduleName, mfaServices, serviceName } = this;
|
|
119
|
+
const moduleConfig = configProvider.config.domain[moduleName] as AppConfigDomainIAM;
|
|
120
|
+
const { secretKeyHMACAlgorithm, hashingSecret } = moduleConfig.authServiceSettings![serviceName].secretKey!;
|
|
121
|
+
const { mfaData, mfaType, password: authPassword } = data;
|
|
122
|
+
const {
|
|
123
|
+
context,
|
|
124
|
+
context: { password: userPassword },
|
|
125
|
+
mfaOptions
|
|
126
|
+
} = options;
|
|
127
|
+
const userIdentifierField = options.contextIdentifierField || moduleConfig.defaultUserIdentifierField;
|
|
128
|
+
const userIdentifierValue = context[userIdentifierField as keyof InitiateContext];
|
|
129
|
+
let mfaUsed = false;
|
|
130
|
+
let mfaValid = false;
|
|
131
|
+
let wrongPassword = false;
|
|
132
|
+
if (!secretKeyHMACAlgorithm || !hashingSecret || !userPassword) {
|
|
133
|
+
wrongPassword = true;
|
|
134
|
+
logger.error(
|
|
135
|
+
`[${moduleName}][${serviceName}]: secretKeyHMACAlgorithm, hashingSecret and/or userPassword not provided.`
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
const computedPassword = crypto
|
|
139
|
+
.createHmac(secretKeyHMACAlgorithm, hashingSecret)
|
|
140
|
+
.update(`${authPassword}`)
|
|
141
|
+
.digest('hex')
|
|
142
|
+
.toString();
|
|
143
|
+
if (computedPassword !== userPassword) {
|
|
144
|
+
wrongPassword = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (wrongPassword) {
|
|
148
|
+
logger.error(
|
|
149
|
+
`[${moduleName}][${serviceName}]: Login attempt failed for user "${userIdentifierValue}" - wrong password.`
|
|
150
|
+
);
|
|
151
|
+
throw new ApplicationError('Authentication failed.');
|
|
152
|
+
}
|
|
153
|
+
if (mfaType) {
|
|
154
|
+
const mfaService = mfaServices?.[mfaType];
|
|
155
|
+
if (!mfaService) {
|
|
156
|
+
logger.error(
|
|
157
|
+
`[${moduleName}][${serviceName}]: Login attempt failed for user "${userIdentifierValue}" - MFA service ${mfaType} not configured.`
|
|
158
|
+
);
|
|
159
|
+
throw new ApplicationError('Authentication failed.');
|
|
160
|
+
}
|
|
161
|
+
if (!mfaData) {
|
|
162
|
+
logger.error(
|
|
163
|
+
`[${moduleName}][${serviceName}]: Login attempt failed for user "${userIdentifierValue}" - no MFA data provided.`
|
|
164
|
+
);
|
|
165
|
+
throw new ApplicationError('Authentication failed.');
|
|
166
|
+
}
|
|
167
|
+
const mfaResult = await mfaService.initiate(mfaData, { ...(mfaOptions || {}), context });
|
|
168
|
+
mfaUsed = true;
|
|
169
|
+
mfaValid = mfaResult.valid;
|
|
170
|
+
}
|
|
171
|
+
return { mfaUsed, mfaValid, valid: true };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GenericObject } from '@node-c/core';
|
|
2
|
+
|
|
3
|
+
export enum AuthorizationCheckErrorCode {
|
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
|
5
|
+
FGANoAccessToModule = 'FGA_NO_ACCESS',
|
|
6
|
+
// eslint-disable-next-line no-unused-vars
|
|
7
|
+
RBACNoAccessToModule = 'RBAC_NO_ACCESS_TO_MODULE',
|
|
8
|
+
// eslint-disable-next-line no-unused-vars
|
|
9
|
+
RBACNoAccessToResource = 'RBAC_NO_ACCESS_TO_RESOURCE'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AuthorizationPoint<Id> {
|
|
13
|
+
allowedInputData?: GenericObject;
|
|
14
|
+
allowedOutputData?: GenericObject;
|
|
15
|
+
forbiddenInputData?: GenericObject;
|
|
16
|
+
forbiddenOutputData?: GenericObject;
|
|
17
|
+
id: Id;
|
|
18
|
+
inputDataFieldName?: string;
|
|
19
|
+
moduleName: string;
|
|
20
|
+
name: string;
|
|
21
|
+
requiredStaticData?: GenericObject;
|
|
22
|
+
resources?: string[];
|
|
23
|
+
// required when resources is set
|
|
24
|
+
resourceContext?: string;
|
|
25
|
+
userFieldName?: string;
|
|
26
|
+
// userTypes: GenericObject[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuthorizationStaticCheckAccessOptions {
|
|
30
|
+
moduleName: string;
|
|
31
|
+
resource?: string;
|
|
32
|
+
resourceContext?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AuthorizationStaticCheckAccessResult {
|
|
36
|
+
authorizationPoints: GenericObject<AuthorizationPoint<unknown>>;
|
|
37
|
+
errorCode?: AuthorizationCheckErrorCode;
|
|
38
|
+
hasAccess: boolean;
|
|
39
|
+
inputDataToBeMutated: GenericObject;
|
|
40
|
+
noMatchForResource: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AuthorizationUser<AuthorizationPointId> {
|
|
44
|
+
currentAuthorizationPoints: GenericObject<AuthorizationPoint<AuthorizationPointId>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface AuthorizeApiKeyData {
|
|
48
|
+
apiKey: string;
|
|
49
|
+
signature?: string;
|
|
50
|
+
signatureContent?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AuthorizeApiKeyOptions {
|
|
54
|
+
config: { apiKey?: string; apiSecret?: string; apiSecretAlgorithm?: string };
|
|
55
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ApplicationError,
|
|
5
|
+
DataEntityService,
|
|
6
|
+
DomainEntityService,
|
|
7
|
+
DomainEntityServiceDefaultData,
|
|
8
|
+
DomainMethod,
|
|
9
|
+
GenericObject,
|
|
10
|
+
LoggerService,
|
|
11
|
+
getNested,
|
|
12
|
+
setNested
|
|
13
|
+
} from '@node-c/core';
|
|
14
|
+
|
|
15
|
+
import ld from 'lodash';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
AuthorizationCheckErrorCode,
|
|
19
|
+
AuthorizationStaticCheckAccessOptions,
|
|
20
|
+
AuthorizationStaticCheckAccessResult,
|
|
21
|
+
AuthorizationUser,
|
|
22
|
+
AuthorizeApiKeyData,
|
|
23
|
+
AuthorizeApiKeyOptions,
|
|
24
|
+
AuthorizationPoint as BaseAuthorizationPoint
|
|
25
|
+
} from './iam.authorization.definitions';
|
|
26
|
+
|
|
27
|
+
import { DecodedTokenContent, IAMTokenManagerService } from '../tokenManager';
|
|
28
|
+
|
|
29
|
+
export class IAMAuthorizationService<
|
|
30
|
+
AuthorizationPoint extends BaseAuthorizationPoint<unknown> = BaseAuthorizationPoint<unknown>,
|
|
31
|
+
Data extends DomainEntityServiceDefaultData<Partial<AuthorizationPoint>> = DomainEntityServiceDefaultData<
|
|
32
|
+
Partial<AuthorizationPoint>
|
|
33
|
+
>,
|
|
34
|
+
TokenManager extends IAMTokenManagerService<object> = IAMTokenManagerService<object>
|
|
35
|
+
> extends DomainEntityService<
|
|
36
|
+
AuthorizationPoint,
|
|
37
|
+
DataEntityService<AuthorizationPoint>,
|
|
38
|
+
Data,
|
|
39
|
+
Record<string, DataEntityService<Partial<AuthorizationPoint>>> | undefined
|
|
40
|
+
> {
|
|
41
|
+
constructor(
|
|
42
|
+
protected dataAuthorizationPointsService: DataEntityService<AuthorizationPoint>,
|
|
43
|
+
protected defaultMethods: string[] = [DomainMethod.Find],
|
|
44
|
+
protected logger: LoggerService,
|
|
45
|
+
protected additionalDataEntityServices?: GenericObject<DataEntityService<Partial<AuthorizationPoint>>>,
|
|
46
|
+
// eslint-disable-next-line no-unused-vars
|
|
47
|
+
protected tokenManager?: TokenManager
|
|
48
|
+
) {
|
|
49
|
+
super(dataAuthorizationPointsService, defaultMethods, logger, additionalDataEntityServices);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async authorizeApiKey(data: AuthorizeApiKeyData, options: AuthorizeApiKeyOptions): Promise<{ valid: boolean }> {
|
|
53
|
+
const { logger } = this;
|
|
54
|
+
const { apiKey, signature, signatureContent } = data;
|
|
55
|
+
const {
|
|
56
|
+
config: { apiKey: expectedApiKey, apiSecret, apiSecretAlgorithm }
|
|
57
|
+
} = options;
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
logger.error('Missing api key.');
|
|
60
|
+
return { valid: false };
|
|
61
|
+
}
|
|
62
|
+
if (apiKey !== expectedApiKey) {
|
|
63
|
+
logger.error('Invalid api key.');
|
|
64
|
+
return { valid: false };
|
|
65
|
+
}
|
|
66
|
+
if (apiSecret && apiSecretAlgorithm) {
|
|
67
|
+
if (!signature) {
|
|
68
|
+
logger.error('Missing authorization signature.');
|
|
69
|
+
return { valid: false };
|
|
70
|
+
}
|
|
71
|
+
if (!signatureContent) {
|
|
72
|
+
logger.error('Missing authorization signature content.');
|
|
73
|
+
return { valid: false };
|
|
74
|
+
}
|
|
75
|
+
const calcualtedSignature = crypto
|
|
76
|
+
.createHmac(apiSecretAlgorithm, apiSecret)
|
|
77
|
+
.update(signatureContent)
|
|
78
|
+
.digest('hex');
|
|
79
|
+
if (calcualtedSignature !== signature) {
|
|
80
|
+
logger.error(`Invalid signature provided. Expected: ${calcualtedSignature}. Provided: ${signature}`);
|
|
81
|
+
return { valid: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { valid: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// TODO: decouple from users
|
|
88
|
+
async authorizeBearer<UserTokenEnityFields = unknown>(
|
|
89
|
+
data: { authToken?: string; refreshToken?: string },
|
|
90
|
+
options?: { identifierDataField?: string }
|
|
91
|
+
): Promise<{ newAuthToken?: string; tokenContent?: DecodedTokenContent<UserTokenEnityFields>; valid: boolean }> {
|
|
92
|
+
const { logger, tokenManager } = this;
|
|
93
|
+
const { authToken, refreshToken } = data;
|
|
94
|
+
const { identifierDataField } = options || {};
|
|
95
|
+
if (!tokenManager) {
|
|
96
|
+
logger.error('Token manager not configured.');
|
|
97
|
+
return { valid: false };
|
|
98
|
+
}
|
|
99
|
+
if (!authToken) {
|
|
100
|
+
logger.error('Missing auth token.');
|
|
101
|
+
return { valid: false };
|
|
102
|
+
}
|
|
103
|
+
let newAuthToken: string | undefined;
|
|
104
|
+
let tokenContent: DecodedTokenContent<UserTokenEnityFields> | undefined;
|
|
105
|
+
try {
|
|
106
|
+
const tokenRes = await tokenManager.verifyAccessToken(authToken, {
|
|
107
|
+
deleteFromStoreIfExpired: true,
|
|
108
|
+
identifierDataField,
|
|
109
|
+
persistNewToken: true,
|
|
110
|
+
purgeStoreOnRenew: true,
|
|
111
|
+
refreshToken,
|
|
112
|
+
refreshTokenAccessTokenIdentifierDataField: 'accessToken'
|
|
113
|
+
});
|
|
114
|
+
tokenContent = tokenRes.content as unknown as DecodedTokenContent<UserTokenEnityFields>;
|
|
115
|
+
if (tokenRes.newToken) {
|
|
116
|
+
newAuthToken = tokenRes.newToken;
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
logger.error('Failed to parse the access or refresh token:', e);
|
|
120
|
+
return { valid: false };
|
|
121
|
+
}
|
|
122
|
+
return { newAuthToken, tokenContent, valid: true };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async checkAccessWithStorage(): Promise<void> {
|
|
126
|
+
throw new ApplicationError('[IAMAuthorizationService.checkAccessWithStorage]: Method not implemented.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static checkAccess<InputData = GenericObject>(
|
|
130
|
+
inputData: InputData,
|
|
131
|
+
user: AuthorizationUser<unknown>,
|
|
132
|
+
options: AuthorizationStaticCheckAccessOptions
|
|
133
|
+
): AuthorizationStaticCheckAccessResult {
|
|
134
|
+
const { moduleName, resourceContext, resource } = options;
|
|
135
|
+
let hasResource = false;
|
|
136
|
+
if (resource) {
|
|
137
|
+
if (!resourceContext) {
|
|
138
|
+
throw new ApplicationError(
|
|
139
|
+
'[IAMAuthorizationService.checkAccess]: A resourceContext is required when providing a resource value.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
hasResource = true;
|
|
143
|
+
}
|
|
144
|
+
// check the access to the found authorization points
|
|
145
|
+
const mutatedInputData = ld.cloneDeep(inputData);
|
|
146
|
+
const usedAuthorizationPoints: GenericObject<BaseAuthorizationPoint<unknown>> = {};
|
|
147
|
+
const { currentAuthorizationPoints } = user;
|
|
148
|
+
let authorizationPointsCount = 0;
|
|
149
|
+
let authorizationPointsForDifferentModules = 0;
|
|
150
|
+
let authorizationPointsForDifferentContexts = 0;
|
|
151
|
+
let hasAccess = false;
|
|
152
|
+
let inputDataToBeMutated: GenericObject = {};
|
|
153
|
+
let noMatchForResource = false;
|
|
154
|
+
for (const apId in currentAuthorizationPoints) {
|
|
155
|
+
const apData = currentAuthorizationPoints[apId];
|
|
156
|
+
authorizationPointsCount++;
|
|
157
|
+
// RBAC - check whether the user has general access to the module.
|
|
158
|
+
if (moduleName !== apData.moduleName) {
|
|
159
|
+
authorizationPointsForDifferentModules++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// RBAC - check whether the user has general access to the resource.
|
|
163
|
+
if (
|
|
164
|
+
hasResource &&
|
|
165
|
+
(!apData.resourceContext ||
|
|
166
|
+
apData.resourceContext !== resourceContext ||
|
|
167
|
+
!apData.resources?.includes(resource!))
|
|
168
|
+
) {
|
|
169
|
+
authorizationPointsForDifferentContexts++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// FGA - check whether the user has access based on specific input and user fields.
|
|
173
|
+
const { allowedInputData, forbiddenInputData, inputDataFieldName, requiredStaticData, userFieldName } = apData;
|
|
174
|
+
const hasStaticData = requiredStaticData && Object.keys(requiredStaticData).length;
|
|
175
|
+
const innerMutatedInputData = ld.cloneDeep(mutatedInputData) as GenericObject;
|
|
176
|
+
const innerInputDataToBeMutated: GenericObject = {};
|
|
177
|
+
hasAccess = true;
|
|
178
|
+
if (!noMatchForResource) {
|
|
179
|
+
noMatchForResource = true;
|
|
180
|
+
}
|
|
181
|
+
// 1. Required static data
|
|
182
|
+
if (hasStaticData) {
|
|
183
|
+
for (const fieldName in requiredStaticData) {
|
|
184
|
+
if (
|
|
185
|
+
!IAMAuthorizationService.testValue(
|
|
186
|
+
getNested({ inputData: innerMutatedInputData, user }, fieldName, { removeNestedFieldEscapeSign: true })
|
|
187
|
+
.unifiedValue,
|
|
188
|
+
requiredStaticData[fieldName]
|
|
189
|
+
)
|
|
190
|
+
) {
|
|
191
|
+
hasAccess = false;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!hasAccess) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 2. User field data vs input field data.
|
|
200
|
+
if (userFieldName && inputDataFieldName) {
|
|
201
|
+
const { paths: inputFieldPaths, unifiedValue: inputFieldValue } = getNested(
|
|
202
|
+
innerMutatedInputData,
|
|
203
|
+
inputDataFieldName,
|
|
204
|
+
{
|
|
205
|
+
removeNestedFieldEscapeSign: true
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
const { unifiedValue: userFieldValue } = getNested(user, userFieldName, { removeNestedFieldEscapeSign: true });
|
|
209
|
+
if (typeof userFieldValue === 'undefined') {
|
|
210
|
+
hasAccess = false;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (typeof inputFieldValue === 'undefined') {
|
|
214
|
+
innerInputDataToBeMutated[inputDataFieldName] = userFieldValue;
|
|
215
|
+
setNested(innerMutatedInputData, inputDataFieldName, userFieldValue, {
|
|
216
|
+
removeNestedFieldEscapeSign: true,
|
|
217
|
+
setNestedArraysPerIndex: inputFieldPaths.length > 1
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
const allowedValues = IAMAuthorizationService.matchInputValues(innerMutatedInputData, {
|
|
221
|
+
[inputDataFieldName]: userFieldValue
|
|
222
|
+
})[inputDataFieldName] as unknown[];
|
|
223
|
+
const inputValueIsArray = inputFieldValue instanceof Array;
|
|
224
|
+
if (!allowedValues?.length) {
|
|
225
|
+
hasAccess = false;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (inputValueIsArray) {
|
|
229
|
+
innerInputDataToBeMutated[inputDataFieldName] = allowedValues;
|
|
230
|
+
setNested(innerMutatedInputData, inputDataFieldName, allowedValues, { removeNestedFieldEscapeSign: true });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// 3. Input data whitelist
|
|
235
|
+
// WARNING: In an expressjs v5+ environment, this will only work properly if the query is mutable
|
|
236
|
+
if (allowedInputData && Object.keys(allowedInputData).length) {
|
|
237
|
+
const values = IAMAuthorizationService.matchInputValues(innerMutatedInputData, allowedInputData);
|
|
238
|
+
for (const key in values) {
|
|
239
|
+
innerInputDataToBeMutated[key] = values[key];
|
|
240
|
+
setNested(innerMutatedInputData, key, values[key], { removeNestedFieldEscapeSign: true });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// 4. Input data blacklist
|
|
244
|
+
if (forbiddenInputData && Object.keys(forbiddenInputData).length) {
|
|
245
|
+
const values = IAMAuthorizationService.matchInputValues(innerMutatedInputData, forbiddenInputData);
|
|
246
|
+
for (const key in values) {
|
|
247
|
+
innerInputDataToBeMutated[key] = undefined;
|
|
248
|
+
setNested(innerMutatedInputData, key, undefined, { removeNestedFieldEscapeSign: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
inputDataToBeMutated = ld.merge(inputDataToBeMutated, innerInputDataToBeMutated);
|
|
252
|
+
usedAuthorizationPoints[apId] = apData;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
const returnData: AuthorizationStaticCheckAccessResult = {
|
|
256
|
+
authorizationPoints: usedAuthorizationPoints,
|
|
257
|
+
hasAccess,
|
|
258
|
+
inputDataToBeMutated,
|
|
259
|
+
noMatchForResource
|
|
260
|
+
};
|
|
261
|
+
if (!hasAccess) {
|
|
262
|
+
if (authorizationPointsForDifferentModules === authorizationPointsCount) {
|
|
263
|
+
returnData.errorCode = AuthorizationCheckErrorCode.RBACNoAccessToModule;
|
|
264
|
+
} else if (authorizationPointsForDifferentContexts === authorizationPointsCount) {
|
|
265
|
+
returnData.errorCode = AuthorizationCheckErrorCode.RBACNoAccessToResource;
|
|
266
|
+
} else {
|
|
267
|
+
returnData.errorCode = AuthorizationCheckErrorCode.FGANoAccessToModule;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return returnData;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static getValuesForTesting(valueToTest: unknown): unknown[] {
|
|
274
|
+
const values = [
|
|
275
|
+
valueToTest, // the value as-is
|
|
276
|
+
parseInt(valueToTest as string, 10), // the int equivalent of the value
|
|
277
|
+
parseFloat(valueToTest as string) // the float equivalent of the value
|
|
278
|
+
];
|
|
279
|
+
// the boolean equivalent of the values
|
|
280
|
+
if (valueToTest === 'true') {
|
|
281
|
+
values.push(true);
|
|
282
|
+
} else if (valueToTest === 'false') {
|
|
283
|
+
values.push(false);
|
|
284
|
+
}
|
|
285
|
+
return values;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
static matchInputValues(input: GenericObject, values: GenericObject): GenericObject {
|
|
289
|
+
const matchedValues: GenericObject = {};
|
|
290
|
+
for (const fieldName in values) {
|
|
291
|
+
const { paths: valuePaths, values: foundValues } = getNested(input, fieldName, {
|
|
292
|
+
removeNestedFieldEscapeSign: true
|
|
293
|
+
});
|
|
294
|
+
const allowedValue = values[fieldName];
|
|
295
|
+
const allowedValues = allowedValue instanceof Array ? allowedValue : [allowedValue];
|
|
296
|
+
const valuesToSet: unknown[] = [];
|
|
297
|
+
valuePaths.forEach((valuePath, valuePathIndex) => {
|
|
298
|
+
const valueAtIndex = foundValues[valuePathIndex];
|
|
299
|
+
let valueIsArray = false;
|
|
300
|
+
let valuesToCheck: unknown[] = [];
|
|
301
|
+
if (valueAtIndex instanceof Array) {
|
|
302
|
+
valuesToCheck = valueAtIndex;
|
|
303
|
+
valueIsArray = true;
|
|
304
|
+
} else {
|
|
305
|
+
valuesToCheck.push(valueAtIndex);
|
|
306
|
+
}
|
|
307
|
+
valuesToCheck.forEach(valueToCheck => {
|
|
308
|
+
for (const j in allowedValues) {
|
|
309
|
+
if (IAMAuthorizationService.testValue(valueToCheck, allowedValues[j])) {
|
|
310
|
+
valuesToSet.push(valueToCheck);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
if (!valuesToSet.length) {
|
|
316
|
+
matchedValues[valuePath] = undefined;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
matchedValues[valuePath] = valueIsArray ? valuesToSet : valuesToSet[0];
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return matchedValues;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
static processOutputData(
|
|
326
|
+
authorizationPoints: { [id: number]: BaseAuthorizationPoint<unknown> },
|
|
327
|
+
outputData: GenericObject
|
|
328
|
+
): {
|
|
329
|
+
outputDataToBeMutated: GenericObject;
|
|
330
|
+
} {
|
|
331
|
+
const mutatedOutputData = ld.cloneDeep(outputData);
|
|
332
|
+
let outputDataToBeMutated: GenericObject = {};
|
|
333
|
+
for (const apId in authorizationPoints) {
|
|
334
|
+
const apData = authorizationPoints[apId];
|
|
335
|
+
const { allowedOutputData, forbiddenOutputData } = apData;
|
|
336
|
+
const innerMutatedOutputData = ld.cloneDeep(mutatedOutputData);
|
|
337
|
+
const innerOutputDataToBeMutated: GenericObject = {};
|
|
338
|
+
if (allowedOutputData && Object.keys(allowedOutputData).length) {
|
|
339
|
+
const values = IAMAuthorizationService.matchInputValues(innerMutatedOutputData, allowedOutputData);
|
|
340
|
+
for (const key in values) {
|
|
341
|
+
innerOutputDataToBeMutated[key] = values[key];
|
|
342
|
+
setNested(innerMutatedOutputData, key, values[key], { removeNestedFieldEscapeSign: true });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (forbiddenOutputData && Object.keys(forbiddenOutputData).length) {
|
|
346
|
+
const values = IAMAuthorizationService.matchInputValues(innerMutatedOutputData, forbiddenOutputData);
|
|
347
|
+
for (const key in values) {
|
|
348
|
+
innerOutputDataToBeMutated[key] = undefined;
|
|
349
|
+
setNested(innerMutatedOutputData, key, undefined, { removeNestedFieldEscapeSign: true });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
outputDataToBeMutated = ld.merge(outputDataToBeMutated, innerOutputDataToBeMutated);
|
|
353
|
+
}
|
|
354
|
+
return { outputDataToBeMutated };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
static testValue(valueToTest: unknown, valueToTestAgainst: unknown): boolean {
|
|
358
|
+
if (
|
|
359
|
+
typeof valueToTestAgainst === 'string' &&
|
|
360
|
+
valueToTestAgainst.charAt(0) === '/' &&
|
|
361
|
+
valueToTestAgainst.charAt(valueToTestAgainst.length - 1) === '/'
|
|
362
|
+
) {
|
|
363
|
+
const regex = new RegExp(valueToTestAgainst.substring(1, valueToTestAgainst.length - 1));
|
|
364
|
+
if (typeof valueToTest === 'undefined') {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
return regex.test(typeof valueToTest === 'string' ? valueToTest : JSON.stringify(valueToTest));
|
|
368
|
+
}
|
|
369
|
+
if (
|
|
370
|
+
typeof valueToTest === 'object' &&
|
|
371
|
+
valueToTest !== null &&
|
|
372
|
+
typeof valueToTestAgainst === 'object' &&
|
|
373
|
+
valueToTestAgainst !== null
|
|
374
|
+
) {
|
|
375
|
+
return JSON.stringify(valueToTest) === JSON.stringify(valueToTestAgainst);
|
|
376
|
+
}
|
|
377
|
+
const possibleValidValues = IAMAuthorizationService.getValuesForTesting(valueToTest);
|
|
378
|
+
let hasMatch = false;
|
|
379
|
+
for (const i in possibleValidValues) {
|
|
380
|
+
if (possibleValidValues[i] === valueToTestAgainst) {
|
|
381
|
+
hasMatch = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return hasMatch;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface IAMMFACompleteData {
|
|
2
|
+
type?: IAMMFAType;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface IAMMFACompleteOptions<Context> {
|
|
6
|
+
context: Context;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum IAMMFAType {
|
|
10
|
+
// eslint-disable-next-line no-unused-vars
|
|
11
|
+
Local = 'local'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IAMMFACompleteResult {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IAMMFAInitiateData {
|
|
19
|
+
type?: IAMMFAType;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IAMMFAInitiateOptions<Context> {
|
|
23
|
+
context: Context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IAMMFAInitiateResult {
|
|
27
|
+
valid: boolean;
|
|
28
|
+
}
|