@node-c/domain-iam 1.0.0-alpha8 → 1.0.0-beta0

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