@axinom/mosaic-id-guard 0.16.3-rc.8 → 0.17.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -6
- package/src/common/deprecated/error-code.ts +23 -0
- package/src/common/deprecated/index.ts +1 -0
- package/src/common/get-authenticated-subject.spec.ts +261 -0
- package/src/common/get-authenticated-subject.ts +222 -0
- package/src/common/guard-utils.spec.ts +185 -0
- package/src/common/guard-utils.ts +207 -0
- package/src/common/handle-end-user-authorization.ts +79 -0
- package/src/common/handle-management-user-authorization.ts +91 -0
- package/src/common/helpers/index.ts +2 -0
- package/src/common/helpers/object-helpers.spec.ts +101 -0
- package/src/common/helpers/object-helpers.ts +9 -0
- package/src/common/helpers/parse-jwt-token-error-handler.ts +143 -0
- package/src/common/id-guard-error.ts +12 -0
- package/src/common/id-guard-errors.ts +82 -0
- package/src/common/index.ts +11 -0
- package/src/common/jwt-verify-options.ts +19 -0
- package/src/common/parse-jwt-token.spec.ts +784 -0
- package/src/common/parse-jwt-token.ts +137 -0
- package/src/common/subject-type.ts +10 -0
- package/src/common/type-assertions.spec.ts +251 -0
- package/src/common/type-assertions.ts +142 -0
- package/src/common/types/authenticated-subject-models.ts +117 -0
- package/src/common/types/authentication-config.ts +15 -0
- package/src/common/types/authentication-request-context.ts +42 -0
- package/src/common/types/index.ts +13 -0
- package/src/graphql/enforce-strict-permissions.plugin.spec.ts +142 -0
- package/src/graphql/enforce-strict-permissions.plugin.ts +163 -0
- package/src/graphql/guard-context.spec.ts +86 -0
- package/src/graphql/guard-context.ts +89 -0
- package/src/graphql/guard-middleware.spec.ts +118 -0
- package/src/graphql/guard-middleware.ts +209 -0
- package/src/graphql/guard-plugin.spec.ts +990 -0
- package/src/graphql/guard-plugin.ts +102 -0
- package/src/graphql/index.ts +12 -0
- package/src/graphql/subscription-authorization-hook-factory.spec.ts +749 -0
- package/src/graphql/subscription-authorization-hook-factory.ts +286 -0
- package/src/index.ts +9 -0
- package/src/message-bus/guard-message-handler.ts +99 -0
- package/src/message-bus/index.ts +3 -0
- package/src/message-bus/message-handler-authentication.spec.ts +123 -0
- package/src/message-bus/message-handler-authentication.ts +84 -0
- package/src/message-bus/message-handler-managed-authentication.spec.ts +111 -0
- package/src/message-bus/message-handler-permissions.spec.ts +108 -0
- package/src/message-bus/message-handler-permissions.ts +51 -0
- package/src/tests/test-utils/index.ts +2 -0
- package/src/tests/test-utils/test-message.ts +46 -0
- package/src/tests/test-utils/test-user.ts +84 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-id-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-rc.0",
|
|
4
4
|
"description": "Authentication and authorization helpers for Axinom Mosaic services",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"axinom mosaic"
|
|
11
11
|
],
|
|
12
12
|
"files": [
|
|
13
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"src"
|
|
14
15
|
],
|
|
15
16
|
"main": "dist/index.js",
|
|
16
17
|
"types": "dist/index.d.ts",
|
|
@@ -27,9 +28,9 @@
|
|
|
27
28
|
"lint": "eslint . --ext .ts,.tsx,.js --color --cache"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@axinom/mosaic-id-utils": "^0.
|
|
31
|
-
"@axinom/mosaic-message-bus": "^0.
|
|
32
|
-
"@axinom/mosaic-service-common": "^0.27.0-rc.
|
|
31
|
+
"@axinom/mosaic-id-utils": "^0.11.0-rc.0",
|
|
32
|
+
"@axinom/mosaic-message-bus": "^0.12.0-rc.0",
|
|
33
|
+
"@axinom/mosaic-service-common": "^0.27.0-rc.11",
|
|
33
34
|
"amqplib": "^0.6.0",
|
|
34
35
|
"express": "^4.17.1",
|
|
35
36
|
"express-bearer-token": "^2.4.0",
|
|
@@ -60,5 +61,5 @@
|
|
|
60
61
|
"publishConfig": {
|
|
61
62
|
"access": "public"
|
|
62
63
|
},
|
|
63
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "140f827280ea9616400d5f6d2f770a0911ebf0e2"
|
|
64
65
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Please use `IdGuardErrors` instead, e.g.
|
|
3
|
+
* ```ts
|
|
4
|
+
* import { IdGuardErrors } from '@axinom/mosaic-id-guard';
|
|
5
|
+
*
|
|
6
|
+
* console.log(IdGuardErrors.AccessTokenRequired.code);
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* This is only guaranteed to stay until the end of October 2022.
|
|
10
|
+
*/
|
|
11
|
+
export enum ErrorCode {
|
|
12
|
+
AccessTokenInvalid = 'ACCESS_TOKEN_INVALID',
|
|
13
|
+
UserNotAuthorized = 'USER_NOT_AUTHORIZED',
|
|
14
|
+
MalformedToken = 'MALFORMED_TOKEN',
|
|
15
|
+
AccessTokenExpired = 'ACCESS_TOKEN_EXPIRED',
|
|
16
|
+
SigningKeyNotFound = 'SIGNING_KEY_NOT_FOUND',
|
|
17
|
+
JwksError = 'JWKS_ERROR',
|
|
18
|
+
AccessTokenVerificationFailed = 'ACCESS_TOKEN_VERIFICATION_FAILED',
|
|
19
|
+
AuthConfigInvalid = 'AUTH_CONFIG_INVALID',
|
|
20
|
+
AccessTokenRequired = 'ACCESS_TOKEN_REQUIRED',
|
|
21
|
+
IdentityServiceNotAccessible = 'IDENTITY_SERVICE_NOT_ACCESSIBLE',
|
|
22
|
+
UserServiceNotAccessible = 'USER_SERVICE_NOT_ACCESSIBLE',
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './error-code';
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { TokenExpiredError } from 'jsonwebtoken';
|
|
2
|
+
import createJWKSMock from 'mock-jwks';
|
|
3
|
+
import {
|
|
4
|
+
createTestEndUserApplication,
|
|
5
|
+
createTestUser,
|
|
6
|
+
} from '../tests/test-utils';
|
|
7
|
+
import {
|
|
8
|
+
EMBEDDED_END_USER_TOKEN_KEY,
|
|
9
|
+
getAuthenticatedEndUser,
|
|
10
|
+
getAuthenticatedManagementSubject,
|
|
11
|
+
} from './get-authenticated-subject';
|
|
12
|
+
import { TOKEN_ISSUER_USER_SERVICE } from './jwt-verify-options';
|
|
13
|
+
import { SubjectType } from './subject-type';
|
|
14
|
+
import { AuthenticationConfig } from './types';
|
|
15
|
+
|
|
16
|
+
const authEndpoint = 'https://AUTH_ENDPOINT_URL';
|
|
17
|
+
|
|
18
|
+
describe('getAuthenticatedManagementSubject method --> managed service accounts', () => {
|
|
19
|
+
const jwks = createJWKSMock(authEndpoint);
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jwks.start();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await jwks.stop();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should verify managed service account tokens', async () => {
|
|
30
|
+
const user = createTestUser({
|
|
31
|
+
subjectType: SubjectType.ManagedServiceAccount,
|
|
32
|
+
});
|
|
33
|
+
const token = jwks.token(user);
|
|
34
|
+
|
|
35
|
+
const result = await getAuthenticatedManagementSubject(token, authEndpoint);
|
|
36
|
+
expect(result).toEqual(user);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('getAuthenticatedManagementSubject method', () => {
|
|
41
|
+
const tenantId = '27aa1b27-3441-4115-84aa-1ed5f3072248';
|
|
42
|
+
const environmentId = '83bfce33-61b1-4ec5-93d5-ad40e4ed33c9';
|
|
43
|
+
const jwks = createJWKSMock(
|
|
44
|
+
authEndpoint,
|
|
45
|
+
`/${tenantId}/${environmentId}/.well-known/jwks.json`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jwks.start();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
await jwks.stop();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should verify tokens', async () => {
|
|
57
|
+
const user = createTestUser();
|
|
58
|
+
const token = jwks.token(user);
|
|
59
|
+
const config: AuthenticationConfig = {
|
|
60
|
+
authEndpoint,
|
|
61
|
+
tenantId,
|
|
62
|
+
environmentId,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = await getAuthenticatedManagementSubject(token, config);
|
|
66
|
+
expect(result).toEqual(user);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle token expiry', async () => {
|
|
70
|
+
const user = createTestUser({
|
|
71
|
+
exp: 0,
|
|
72
|
+
});
|
|
73
|
+
const token = jwks.token(user);
|
|
74
|
+
const config: AuthenticationConfig = {
|
|
75
|
+
authEndpoint,
|
|
76
|
+
tenantId,
|
|
77
|
+
environmentId,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await expect(
|
|
81
|
+
getAuthenticatedManagementSubject(token, config),
|
|
82
|
+
).rejects.toThrow(TokenExpiredError);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getEndUserApplication method', () => {
|
|
87
|
+
const tenantId = '27aa1b27-3441-4115-84aa-1ed5f3072248';
|
|
88
|
+
const environmentId = '83bfce33-61b1-4ec5-93d5-ad40e4ed33c9';
|
|
89
|
+
const applicationId = 'd2f708a9-8c71-4db7-9c09-a84147d2e1c5';
|
|
90
|
+
|
|
91
|
+
const jwks = createJWKSMock(
|
|
92
|
+
authEndpoint,
|
|
93
|
+
`/${tenantId}/${environmentId}/${applicationId}/.well-known/jwks.json`,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
jwks.start();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterEach(async () => {
|
|
101
|
+
await jwks.stop();
|
|
102
|
+
});
|
|
103
|
+
it('should verify an end-user application token', async () => {
|
|
104
|
+
const endUserApplication = createTestEndUserApplication();
|
|
105
|
+
const token = jwks.token(endUserApplication);
|
|
106
|
+
const config: AuthenticationConfig = {
|
|
107
|
+
authEndpoint,
|
|
108
|
+
tenantId,
|
|
109
|
+
environmentId,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = await getAuthenticatedEndUser(token, config);
|
|
113
|
+
expect(result).toEqual(endUserApplication);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('getAuthenticatedEndUser', () => {
|
|
118
|
+
const tenantId = '27aa1b27-3441-4115-84aa-1ed5f3072248';
|
|
119
|
+
const environmentId = '83bfce33-61b1-4ec5-93d5-ad40e4ed33c9';
|
|
120
|
+
const applicationId = '0a0857af-9d3a-4d39-96c9-f0f15fbaecdf';
|
|
121
|
+
|
|
122
|
+
const jwks = createJWKSMock(
|
|
123
|
+
authEndpoint,
|
|
124
|
+
`/${tenantId}/${environmentId}/${applicationId}/.well-known/jwks.json`,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
jwks.start();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
afterEach(async () => {
|
|
132
|
+
await jwks.stop();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should verify tokens', async () => {
|
|
136
|
+
const endUser = createTestUser({
|
|
137
|
+
applicationId,
|
|
138
|
+
subjectType: SubjectType.EndUserAccount,
|
|
139
|
+
iss: TOKEN_ISSUER_USER_SERVICE,
|
|
140
|
+
});
|
|
141
|
+
const token = jwks.token(endUser);
|
|
142
|
+
|
|
143
|
+
const config: AuthenticationConfig = {
|
|
144
|
+
authEndpoint,
|
|
145
|
+
tenantId,
|
|
146
|
+
environmentId,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const result = await getAuthenticatedEndUser(token, config);
|
|
150
|
+
|
|
151
|
+
expect(result).toEqual(endUser);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle token expiry', async () => {
|
|
155
|
+
const endUser = createTestUser({
|
|
156
|
+
exp: 0,
|
|
157
|
+
applicationId,
|
|
158
|
+
subjectType: SubjectType.EndUserAccount,
|
|
159
|
+
iss: TOKEN_ISSUER_USER_SERVICE,
|
|
160
|
+
});
|
|
161
|
+
const token = jwks.token(endUser);
|
|
162
|
+
const config: AuthenticationConfig = {
|
|
163
|
+
authEndpoint,
|
|
164
|
+
tenantId,
|
|
165
|
+
environmentId,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
await expect(getAuthenticatedEndUser(token, config)).rejects.toThrow(
|
|
169
|
+
TokenExpiredError,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('When a JWT with the property mosaic.end-user.accessToken is passed with the value having a valid end-user access token, it should be successfully verified', async () => {
|
|
174
|
+
// Arrange
|
|
175
|
+
const config: AuthenticationConfig = {
|
|
176
|
+
authEndpoint,
|
|
177
|
+
tenantId,
|
|
178
|
+
environmentId,
|
|
179
|
+
};
|
|
180
|
+
const endUser = createTestUser({
|
|
181
|
+
applicationId,
|
|
182
|
+
subjectType: SubjectType.EndUserAccount,
|
|
183
|
+
iss: TOKEN_ISSUER_USER_SERVICE,
|
|
184
|
+
});
|
|
185
|
+
const endUserJwt = jwks.token(endUser);
|
|
186
|
+
const tokenPayloadWithEmbeddedJwt = {
|
|
187
|
+
sub: '1234567890',
|
|
188
|
+
name: 'Jonas Khanwald',
|
|
189
|
+
iat: 1646631810,
|
|
190
|
+
exp: 2046635410,
|
|
191
|
+
[EMBEDDED_END_USER_TOKEN_KEY]: endUserJwt,
|
|
192
|
+
};
|
|
193
|
+
const embeddedJwt = jwks.token(tokenPayloadWithEmbeddedJwt);
|
|
194
|
+
|
|
195
|
+
// Act
|
|
196
|
+
const authenticatedEndUser = await getAuthenticatedEndUser(
|
|
197
|
+
embeddedJwt,
|
|
198
|
+
config,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Assert
|
|
202
|
+
expect(authenticatedEndUser).toEqual(endUser);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it.each([
|
|
206
|
+
jwks.token({
|
|
207
|
+
sub: '1234567890',
|
|
208
|
+
name: 'John Doe',
|
|
209
|
+
iat: 1646631810,
|
|
210
|
+
exp: 2046635410,
|
|
211
|
+
}),
|
|
212
|
+
'inavlid-token-that-is-not-even-a-jwt',
|
|
213
|
+
])(
|
|
214
|
+
'When a JWT with the property mosaic.end-user.accessToken is passed, but the JWT is not a valid token Mosaic End-User Token, an error is raised',
|
|
215
|
+
async (invalidEmbeddedJwt) => {
|
|
216
|
+
// Arrange
|
|
217
|
+
const config: AuthenticationConfig = {
|
|
218
|
+
authEndpoint,
|
|
219
|
+
tenantId,
|
|
220
|
+
environmentId,
|
|
221
|
+
};
|
|
222
|
+
const tokenPayloadWithEmbeddedJwt = {
|
|
223
|
+
sub: '1234567890',
|
|
224
|
+
name: 'Jonas Khanwald',
|
|
225
|
+
iat: 1646631810,
|
|
226
|
+
exp: 2046635410,
|
|
227
|
+
[EMBEDDED_END_USER_TOKEN_KEY]: invalidEmbeddedJwt,
|
|
228
|
+
};
|
|
229
|
+
const embeddedJwt = jwks.token(tokenPayloadWithEmbeddedJwt);
|
|
230
|
+
|
|
231
|
+
// Act and Assert
|
|
232
|
+
await expect(
|
|
233
|
+
getAuthenticatedEndUser(embeddedJwt, config),
|
|
234
|
+
).rejects.toThrow(
|
|
235
|
+
'Passed JWT is not a Mosaic End-User Token. Cannot be verified.',
|
|
236
|
+
);
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
it('When a JWT that is not an End-User Access Token is passed, an error is raised', async () => {
|
|
241
|
+
// Arrange
|
|
242
|
+
const config: AuthenticationConfig = {
|
|
243
|
+
authEndpoint,
|
|
244
|
+
tenantId,
|
|
245
|
+
environmentId,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const tokenPayloadWithEmbeddedJwt = {
|
|
249
|
+
sub: '1234567890',
|
|
250
|
+
name: 'Jonas Khanwald',
|
|
251
|
+
iat: 1646631810,
|
|
252
|
+
exp: 2046635410,
|
|
253
|
+
};
|
|
254
|
+
const embeddedJwt = jwks.token(tokenPayloadWithEmbeddedJwt);
|
|
255
|
+
|
|
256
|
+
// Act and Assert
|
|
257
|
+
await expect(getAuthenticatedEndUser(embeddedJwt, config)).rejects.toThrow(
|
|
258
|
+
'Passed JWT is not a Mosaic End-User Token. Cannot be verified.',
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { isNullOrWhitespace } from '@axinom/mosaic-service-common';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import jwks from 'jwks-rsa';
|
|
4
|
+
import { IdGuardError } from './id-guard-error';
|
|
5
|
+
import { IdGuardErrors } from './id-guard-errors';
|
|
6
|
+
import {
|
|
7
|
+
getJwtVerifyOptions,
|
|
8
|
+
TOKEN_ISSUER_ID_SERVICE,
|
|
9
|
+
TOKEN_ISSUER_USER_SERVICE,
|
|
10
|
+
} from './jwt-verify-options';
|
|
11
|
+
import { SubjectType } from './subject-type';
|
|
12
|
+
import {
|
|
13
|
+
AuthenticatedEndUser,
|
|
14
|
+
AuthenticatedEndUserApplication,
|
|
15
|
+
AuthenticatedManagementSubject,
|
|
16
|
+
AuthenticationConfig,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export const EMBEDDED_END_USER_TOKEN_KEY = 'mosaic.end-user.accessToken';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses a JWT token to produce an `AuthenticatedManagementSubject`.
|
|
23
|
+
* This function is intended to be used by any service which works with the Mosaic Management System.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} token JWT token without `Bearer ` prefix
|
|
26
|
+
* @param {string | AuthenticationConfig} authParams This could be either a string containing the Auth Endpoint for the ID Service or an instance of `AuthenticationConfig`.
|
|
27
|
+
* @returns {Promise<AuthenticatedManagementSubject>} AuthenticatedManagementSubject
|
|
28
|
+
*/
|
|
29
|
+
export const getAuthenticatedManagementSubject = async (
|
|
30
|
+
token: string,
|
|
31
|
+
authParams: string | AuthenticationConfig,
|
|
32
|
+
): Promise<AuthenticatedManagementSubject> => {
|
|
33
|
+
let tenantId = '';
|
|
34
|
+
let environmentId = '';
|
|
35
|
+
let isManagedServiceAccount = false;
|
|
36
|
+
let jwksUri = '';
|
|
37
|
+
|
|
38
|
+
const decoded = jwt.decode(token) as AuthenticatedManagementSubject;
|
|
39
|
+
const issuer = decoded.iss;
|
|
40
|
+
|
|
41
|
+
// Tenant ID and Environment ID should be decoded from the token
|
|
42
|
+
tenantId = decoded.tenantId;
|
|
43
|
+
environmentId = decoded.environmentId;
|
|
44
|
+
isManagedServiceAccount =
|
|
45
|
+
decoded.subjectType === SubjectType.ManagedServiceAccount;
|
|
46
|
+
|
|
47
|
+
// If token is not issued by the ID Service, the jwks URI will not be set, and the verification will fail.
|
|
48
|
+
if (issuer === TOKEN_ISSUER_ID_SERVICE) {
|
|
49
|
+
if (typeof authParams === 'string') {
|
|
50
|
+
if (isManagedServiceAccount) {
|
|
51
|
+
jwksUri = new URL(`/.well-known/jwks.json`, authParams).href;
|
|
52
|
+
} else {
|
|
53
|
+
jwksUri = new URL(
|
|
54
|
+
`/${tenantId}/${environmentId}/.well-known/jwks.json`,
|
|
55
|
+
authParams,
|
|
56
|
+
).href;
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
// TODO: Refactor this to support AuthenticationConfig with mandatory values.
|
|
60
|
+
if (
|
|
61
|
+
!isNullOrWhitespace(authParams.tenantId) &&
|
|
62
|
+
!isNullOrWhitespace(authParams.environmentId)
|
|
63
|
+
) {
|
|
64
|
+
tenantId = authParams.tenantId;
|
|
65
|
+
environmentId = authParams.environmentId;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isManagedServiceAccount) {
|
|
69
|
+
jwksUri = new URL(`/.well-known/jwks.json`, authParams.authEndpoint)
|
|
70
|
+
.href;
|
|
71
|
+
} else {
|
|
72
|
+
jwksUri = new URL(
|
|
73
|
+
`/${tenantId}/${environmentId}/.well-known/jwks.json`,
|
|
74
|
+
authParams.authEndpoint,
|
|
75
|
+
).href;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Verify access token using JWKS
|
|
81
|
+
return (await verifyTokenAndGetAuthenticatedSubject(
|
|
82
|
+
token,
|
|
83
|
+
jwksUri,
|
|
84
|
+
'MANAGEMENT',
|
|
85
|
+
)) as AuthenticatedManagementSubject;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parses a JWT token to produce an `AuthenticatedEndUser` or an `AuthenticatedEndUserApplication`.
|
|
90
|
+
* This function is intended to be used by any service which works with End-User Applications.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} token JWT token without `Bearer ` prefix.
|
|
93
|
+
* @param {string | AuthenticationConfig} authParams This could be either a string containing the Auth Endpoint for the User Service or an instance of `AuthenticationConfig`.
|
|
94
|
+
* @returns {Promise<AuthenticatedEndUser | AuthenticatedEndUserApplication>} AuthenticatedEndUser/AuthenticatedEndUserApplication
|
|
95
|
+
*/
|
|
96
|
+
export const getAuthenticatedEndUser = async (
|
|
97
|
+
token: string,
|
|
98
|
+
authParams: string | AuthenticationConfig,
|
|
99
|
+
): Promise<AuthenticatedEndUser | AuthenticatedEndUserApplication> => {
|
|
100
|
+
let tenantId = '';
|
|
101
|
+
let environmentId = '';
|
|
102
|
+
let jwksUri = '';
|
|
103
|
+
let endUserToken = '';
|
|
104
|
+
|
|
105
|
+
const decodedToken = jwt.decode(token) as Record<string, unknown>;
|
|
106
|
+
|
|
107
|
+
// Handle embedded end-user token.
|
|
108
|
+
if (decodedToken[EMBEDDED_END_USER_TOKEN_KEY] !== undefined) {
|
|
109
|
+
endUserToken = String(decodedToken[EMBEDDED_END_USER_TOKEN_KEY]);
|
|
110
|
+
} else {
|
|
111
|
+
endUserToken = token;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const decodedEndUserToken = jwt.decode(
|
|
115
|
+
endUserToken,
|
|
116
|
+
) as AuthenticatedEndUserApplication;
|
|
117
|
+
|
|
118
|
+
// There can be a chance the token passed through `mosaic.end-user.accessToken` is not a valid JWT. (i.e. a simple string)
|
|
119
|
+
if (isNullOrWhitespace(decodedEndUserToken)) {
|
|
120
|
+
throw new IdGuardError(IdGuardErrors.JwtIsNotMosaicEndUserToken);
|
|
121
|
+
}
|
|
122
|
+
const issuer = decodedEndUserToken.iss;
|
|
123
|
+
tenantId = decodedEndUserToken.tenantId;
|
|
124
|
+
environmentId = decodedEndUserToken.environmentId;
|
|
125
|
+
const applicationId = decodedEndUserToken.applicationId;
|
|
126
|
+
|
|
127
|
+
// If token is not issued by the User Service, the jwks URI will not be set, and the verification will fail.
|
|
128
|
+
if (issuer === TOKEN_ISSUER_USER_SERVICE) {
|
|
129
|
+
if (typeof authParams === 'string') {
|
|
130
|
+
jwksUri = new URL(
|
|
131
|
+
`/${tenantId}/${environmentId}/${applicationId}/.well-known/jwks.json`,
|
|
132
|
+
authParams,
|
|
133
|
+
).href;
|
|
134
|
+
} else {
|
|
135
|
+
// TODO: Refactor this to support AuthenticationConfig with mandatory values.
|
|
136
|
+
if (
|
|
137
|
+
!isNullOrWhitespace(authParams.tenantId) &&
|
|
138
|
+
!isNullOrWhitespace(authParams.environmentId)
|
|
139
|
+
) {
|
|
140
|
+
tenantId = authParams.tenantId;
|
|
141
|
+
environmentId = authParams.environmentId;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
jwksUri = new URL(
|
|
145
|
+
`/${tenantId}/${environmentId}/${applicationId}/.well-known/jwks.json`,
|
|
146
|
+
authParams.authEndpoint,
|
|
147
|
+
).href;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (decodedEndUserToken.subjectType === SubjectType.EndUserApplication) {
|
|
152
|
+
// Verify access token using JWKS
|
|
153
|
+
return (await verifyTokenAndGetAuthenticatedSubject(
|
|
154
|
+
endUserToken,
|
|
155
|
+
jwksUri,
|
|
156
|
+
'END_USER_APPLICATION',
|
|
157
|
+
)) as AuthenticatedEndUserApplication;
|
|
158
|
+
} else if (decodedEndUserToken.subjectType === SubjectType.EndUserAccount) {
|
|
159
|
+
// Verify access token using JWKS
|
|
160
|
+
return (await verifyTokenAndGetAuthenticatedSubject(
|
|
161
|
+
endUserToken,
|
|
162
|
+
jwksUri,
|
|
163
|
+
'END_USER',
|
|
164
|
+
)) as AuthenticatedEndUser;
|
|
165
|
+
} else {
|
|
166
|
+
throw new IdGuardError(IdGuardErrors.JwtIsNotMosaicEndUserToken);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const verifyTokenAndGetAuthenticatedSubject = async (
|
|
171
|
+
token: string,
|
|
172
|
+
jwksUri: string,
|
|
173
|
+
authType: 'MANAGEMENT' | 'END_USER' | 'END_USER_APPLICATION',
|
|
174
|
+
): Promise<
|
|
175
|
+
| AuthenticatedManagementSubject
|
|
176
|
+
| AuthenticatedEndUser
|
|
177
|
+
| AuthenticatedEndUserApplication
|
|
178
|
+
> => {
|
|
179
|
+
return new Promise<
|
|
180
|
+
| AuthenticatedManagementSubject
|
|
181
|
+
| AuthenticatedEndUser
|
|
182
|
+
| AuthenticatedEndUserApplication
|
|
183
|
+
>((resolve, reject) => {
|
|
184
|
+
const jwksClient = jwks({
|
|
185
|
+
jwksUri,
|
|
186
|
+
cache: true,
|
|
187
|
+
cacheMaxAge: 1000 * 60 * 10, // 10 Minutes (same as access token lifetime)
|
|
188
|
+
cacheMaxEntries: 100,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const getPublicKey: jwt.GetPublicKeyOrSecret = (
|
|
192
|
+
header: jwt.JwtHeader,
|
|
193
|
+
callback: jwt.SigningKeyCallback,
|
|
194
|
+
): void => {
|
|
195
|
+
jwksClient.getSigningKey(
|
|
196
|
+
header.kid ?? 'MISSING_KEY_ID_IN_JWT_HEADER',
|
|
197
|
+
(error: Error | null, key: jwks.SigningKey) => {
|
|
198
|
+
if (error) {
|
|
199
|
+
reject(error);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
callback(null, key.getPublicKey());
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
jwt.verify(token, getPublicKey, getJwtVerifyOptions(), (error, decoded) => {
|
|
208
|
+
if (error) {
|
|
209
|
+
reject(error);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (authType === 'MANAGEMENT') {
|
|
214
|
+
resolve(decoded as AuthenticatedManagementSubject);
|
|
215
|
+
} else if (authType === 'END_USER') {
|
|
216
|
+
resolve(decoded as AuthenticatedEndUser);
|
|
217
|
+
} else {
|
|
218
|
+
resolve(decoded as AuthenticatedEndUserApplication);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
};
|