@boxyhq/saml-jackson 0.2.3-beta.210 → 0.2.3-beta.222

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 (46) hide show
  1. package/.eslintrc.js +13 -0
  2. package/package.json +2 -2
  3. package/prettier.config.js +4 -0
  4. package/src/controller/api.ts +225 -0
  5. package/src/controller/error.ts +13 -0
  6. package/src/controller/oauth/allowed.ts +22 -0
  7. package/src/controller/oauth/code-verifier.ts +11 -0
  8. package/src/controller/oauth/redirect.ts +12 -0
  9. package/src/controller/oauth.ts +337 -0
  10. package/src/controller/utils.ts +17 -0
  11. package/src/db/db.ts +100 -0
  12. package/src/db/encrypter.ts +38 -0
  13. package/src/db/mem.ts +128 -0
  14. package/src/db/mongo.ts +110 -0
  15. package/src/db/redis.ts +103 -0
  16. package/src/db/sql/entity/JacksonIndex.ts +44 -0
  17. package/src/db/sql/entity/JacksonStore.ts +43 -0
  18. package/src/db/sql/entity/JacksonTTL.ts +17 -0
  19. package/src/db/sql/model/JacksonIndex.ts +3 -0
  20. package/src/db/sql/model/JacksonStore.ts +8 -0
  21. package/src/db/sql/sql.ts +184 -0
  22. package/src/db/store.ts +49 -0
  23. package/src/db/utils.ts +26 -0
  24. package/src/env.ts +42 -0
  25. package/src/index.ts +79 -0
  26. package/src/jackson.ts +171 -0
  27. package/src/read-config.ts +29 -0
  28. package/src/saml/claims.ts +41 -0
  29. package/src/saml/saml.ts +234 -0
  30. package/src/saml/x509.ts +51 -0
  31. package/src/test/api.test.ts +271 -0
  32. package/src/test/data/metadata/boxyhq.js +6 -0
  33. package/src/test/data/metadata/boxyhq.xml +30 -0
  34. package/src/test/data/saml_response +1 -0
  35. package/src/test/db.test.ts +313 -0
  36. package/src/test/oauth.test.ts +353 -0
  37. package/src/typings.ts +167 -0
  38. package/tsconfig.build.json +6 -0
  39. package/tsconfig.json +26 -0
  40. package/.nyc_output/36a3e9e1-42eb-468d-a9ec-8d206fedcd3e.json +0 -1
  41. package/.nyc_output/8c0af85a-b807-45bb-8331-20c3aabe15df.json +0 -1
  42. package/.nyc_output/ad148b90-7401-4df2-959f-3fdcf81a06ec.json +0 -1
  43. package/.nyc_output/processinfo/36a3e9e1-42eb-468d-a9ec-8d206fedcd3e.json +0 -1
  44. package/.nyc_output/processinfo/8c0af85a-b807-45bb-8331-20c3aabe15df.json +0 -1
  45. package/.nyc_output/processinfo/ad148b90-7401-4df2-959f-3fdcf81a06ec.json +0 -1
  46. package/.nyc_output/processinfo/index.json +0 -1
package/.eslintrc.js ADDED
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ "env": {
3
+ "es2021": true,
4
+ "node": true
5
+ },
6
+ "extends": "eslint:recommended",
7
+ "parserOptions": {
8
+ "ecmaVersion": 13,
9
+ "sourceType": "module"
10
+ },
11
+ "rules": {
12
+ }
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.2.3-beta.210",
3
+ "version": "0.2.3-beta.222",
4
4
  "license": "Apache 2.0",
5
5
  "description": "SAML 2.0 service",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "SAML 2.0"
17
17
  ],
18
18
  "scripts": {
19
- "build": "tsc",
19
+ "build": "tsc -p tsconfig.build.json",
20
20
  "start": "cross-env IDP_ENABLED=true node src/jackson.js",
21
21
  "dev": "cross-env IDP_ENABLED=true nodemon src/jackson.js",
22
22
  "mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson DB_ENCRYPTION_KEY=RiVoTxDoLUUoIUOp224abMxK6PGGfFuF nodemon --config nodemon.json src/jackson.ts",
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ singleQuote: true,
3
+ trailingComma: 'es5',
4
+ };
@@ -0,0 +1,225 @@
1
+ import crypto from 'crypto';
2
+ import { IdPConfig, ISAMLConfig, OAuth, Storable } from 'saml-jackson';
3
+ import * as dbutils from '../db/utils';
4
+ import saml from '../saml/saml';
5
+ import { JacksonError } from './error';
6
+ import { IndexNames } from './utils';
7
+ import x509 from '../saml/x509';
8
+
9
+ export class SAMLConfig implements ISAMLConfig {
10
+ private configStore: Storable;
11
+
12
+ constructor({ configStore }) {
13
+ this.configStore = configStore;
14
+ }
15
+
16
+ private _validateIdPConfig(body: IdPConfig): void {
17
+ const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } =
18
+ body;
19
+
20
+ if (!rawMetadata) {
21
+ throw new JacksonError('Please provide rawMetadata', 400);
22
+ }
23
+
24
+ if (!defaultRedirectUrl) {
25
+ throw new JacksonError('Please provide a defaultRedirectUrl', 400);
26
+ }
27
+
28
+ if (!redirectUrl) {
29
+ throw new JacksonError('Please provide redirectUrl', 400);
30
+ }
31
+
32
+ if (!tenant) {
33
+ throw new JacksonError('Please provide tenant', 400);
34
+ }
35
+
36
+ if (!product) {
37
+ throw new JacksonError('Please provide product', 400);
38
+ }
39
+ }
40
+
41
+ public async create(body: IdPConfig): Promise<OAuth> {
42
+ const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } =
43
+ body;
44
+
45
+ this._validateIdPConfig(body);
46
+
47
+ const idpMetadata = await saml.parseMetadataAsync(rawMetadata);
48
+
49
+ // extract provider
50
+ let providerName = extractHostName(idpMetadata.entityID);
51
+ if (!providerName) {
52
+ providerName = extractHostName(
53
+ idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl
54
+ );
55
+ }
56
+
57
+ idpMetadata.provider = providerName ? providerName : 'Unknown';
58
+
59
+ let clientID = dbutils.keyDigest(
60
+ dbutils.keyFromParts(tenant, product, idpMetadata.entityID)
61
+ );
62
+
63
+ let clientSecret;
64
+
65
+ let exists = await this.configStore.get(clientID);
66
+
67
+ if (exists) {
68
+ clientSecret = exists.clientSecret;
69
+ } else {
70
+ clientSecret = crypto.randomBytes(24).toString('hex');
71
+ }
72
+
73
+ const certs = await x509.generate();
74
+
75
+ if (!certs) {
76
+ throw new Error('Error generating x59 certs');
77
+ }
78
+
79
+ await this.configStore.put(
80
+ clientID,
81
+ {
82
+ idpMetadata,
83
+ defaultRedirectUrl,
84
+ redirectUrl: JSON.parse(redirectUrl), // redirectUrl is a stringified array
85
+ tenant,
86
+ product,
87
+ clientID,
88
+ clientSecret,
89
+ certs,
90
+ },
91
+ {
92
+ // secondary index on entityID
93
+ name: IndexNames.EntityID,
94
+ value: idpMetadata.entityID,
95
+ },
96
+ {
97
+ // secondary index on tenant + product
98
+ name: IndexNames.TenantProduct,
99
+ value: dbutils.keyFromParts(tenant, product),
100
+ }
101
+ );
102
+
103
+ return {
104
+ client_id: clientID,
105
+ client_secret: clientSecret,
106
+ provider: idpMetadata.provider,
107
+ };
108
+ }
109
+
110
+ public async get(body: {
111
+ clientID: string;
112
+ tenant: string;
113
+ product: string;
114
+ }): Promise<Partial<OAuth>> {
115
+ const { clientID, tenant, product } = body;
116
+
117
+ if (clientID) {
118
+ const samlConfig = await this.configStore.get(clientID);
119
+
120
+ return samlConfig ? { provider: samlConfig.idpMetadata.provider } : {};
121
+ }
122
+
123
+ if (tenant && product) {
124
+ const samlConfigs = await this.configStore.getByIndex({
125
+ name: IndexNames.TenantProduct,
126
+ value: dbutils.keyFromParts(tenant, product),
127
+ });
128
+
129
+ if (!samlConfigs || !samlConfigs.length) {
130
+ return {};
131
+ }
132
+
133
+ return { provider: samlConfigs[0].idpMetadata.provider };
134
+ }
135
+
136
+ throw new JacksonError(
137
+ 'Please provide `clientID` or `tenant` and `product`.',
138
+ 400
139
+ );
140
+ }
141
+
142
+ public async delete(body: {
143
+ clientID: string;
144
+ clientSecret: string;
145
+ tenant: string;
146
+ product: string;
147
+ }): Promise<void> {
148
+ const { clientID, clientSecret, tenant, product } = body;
149
+
150
+ if (clientID && clientSecret) {
151
+ const samlConfig = await this.configStore.get(clientID);
152
+
153
+ if (!samlConfig) {
154
+ return;
155
+ }
156
+
157
+ if (samlConfig.clientSecret === clientSecret) {
158
+ await this.configStore.delete(clientID);
159
+ } else {
160
+ throw new JacksonError('clientSecret mismatch.', 400);
161
+ }
162
+
163
+ return;
164
+ }
165
+
166
+ if (tenant && product) {
167
+ const samlConfigs = await this.configStore.getByIndex({
168
+ name: IndexNames.TenantProduct,
169
+ value: dbutils.keyFromParts(tenant, product),
170
+ });
171
+
172
+ if (!samlConfigs || !samlConfigs.length) {
173
+ return;
174
+ }
175
+
176
+ for (const conf of samlConfigs) {
177
+ await this.configStore.delete(conf.clientID);
178
+ }
179
+
180
+ return;
181
+ }
182
+
183
+ throw new JacksonError(
184
+ 'Please provide `clientID` and `clientSecret` or `tenant` and `product`.',
185
+ 400
186
+ );
187
+ }
188
+
189
+ // Ensure backward compatibility
190
+
191
+ async config(body: IdPConfig): Promise<OAuth> {
192
+ return this.create(body);
193
+ }
194
+
195
+ async getConfig(body: {
196
+ clientID: string;
197
+ tenant: string;
198
+ product: string;
199
+ }): Promise<Partial<OAuth>> {
200
+ return this.get(body);
201
+ }
202
+
203
+ async deleteConfig(body: {
204
+ clientID: string;
205
+ clientSecret: string;
206
+ tenant: string;
207
+ product: string;
208
+ }): Promise<void> {
209
+ return this.delete(body);
210
+ }
211
+ }
212
+
213
+ const extractHostName = (url: string): string | null => {
214
+ try {
215
+ const pUrl = new URL(url);
216
+
217
+ if (pUrl.hostname.startsWith('www.')) {
218
+ return pUrl.hostname.substring(4);
219
+ }
220
+
221
+ return pUrl.hostname;
222
+ } catch (err) {
223
+ return null;
224
+ }
225
+ };
@@ -0,0 +1,13 @@
1
+ export class JacksonError extends Error {
2
+ public name: string;
3
+ public statusCode: number;
4
+
5
+ constructor(message: string, statusCode: number = 500) {
6
+ super(message);
7
+
8
+ this.name = this.constructor.name;
9
+ this.statusCode = statusCode;
10
+
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
@@ -0,0 +1,22 @@
1
+ export const redirect = (
2
+ redirectUrl: string,
3
+ redirectUrls: string[]
4
+ ): boolean => {
5
+ const url: URL = new URL(redirectUrl);
6
+
7
+ for (const idx in redirectUrls) {
8
+ const rUrl: URL = new URL(redirectUrls[idx]);
9
+
10
+ // TODO: Check pathname, for now pathname is ignored
11
+
12
+ if (
13
+ rUrl.protocol === url.protocol &&
14
+ rUrl.hostname === url.hostname &&
15
+ rUrl.port === url.port
16
+ ) {
17
+ return true;
18
+ }
19
+ }
20
+
21
+ return false;
22
+ };
@@ -0,0 +1,11 @@
1
+ import crypto from 'crypto';
2
+
3
+ export const transformBase64 = (input: string): string => {
4
+ return input.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
5
+ };
6
+
7
+ export const encode = (code_challenge: string): string => {
8
+ return transformBase64(
9
+ crypto.createHash('sha256').update(code_challenge).digest('base64')
10
+ );
11
+ };
@@ -0,0 +1,12 @@
1
+ export const success = (
2
+ redirectUrl: string,
3
+ params: Record<string, string>
4
+ ): string => {
5
+ const url: URL = new URL(redirectUrl);
6
+
7
+ for (const [key, value] of Object.entries(params)) {
8
+ url.searchParams.set(key, value);
9
+ }
10
+
11
+ return url.href;
12
+ };
@@ -0,0 +1,337 @@
1
+ import crypto from 'crypto';
2
+ import {
3
+ IOAuthController,
4
+ JacksonOption,
5
+ OAuthReqBody,
6
+ OAuthTokenReq,
7
+ OAuthTokenRes,
8
+ Profile,
9
+ SAMLResponsePayload,
10
+ Storable,
11
+ } from 'saml-jackson';
12
+ import * as dbutils from '../db/utils';
13
+ import saml from '../saml/saml';
14
+ import { JacksonError } from './error';
15
+ import * as allowed from './oauth/allowed';
16
+ import * as codeVerifier from './oauth/code-verifier';
17
+ import * as redirect from './oauth/redirect';
18
+ import { IndexNames } from './utils';
19
+
20
+ const relayStatePrefix = 'boxyhq_jackson_';
21
+
22
+ function getEncodedClientId(
23
+ client_id: string
24
+ ): { tenant: string | null; product: string | null } | null {
25
+ try {
26
+ const sp = new URLSearchParams(client_id);
27
+ const tenant = sp.get('tenant');
28
+ const product = sp.get('product');
29
+ if (tenant && product) {
30
+ return {
31
+ tenant: sp.get('tenant'),
32
+ product: sp.get('product'),
33
+ };
34
+ }
35
+
36
+ return null;
37
+ } catch (err) {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export class OAuthController implements IOAuthController {
43
+ private configStore: Storable;
44
+ private sessionStore: Storable;
45
+ private codeStore: Storable;
46
+ private tokenStore: Storable;
47
+ private opts: JacksonOption;
48
+
49
+ constructor({ configStore, sessionStore, codeStore, tokenStore, opts }) {
50
+ this.configStore = configStore;
51
+ this.sessionStore = sessionStore;
52
+ this.codeStore = codeStore;
53
+ this.tokenStore = tokenStore;
54
+ this.opts = opts;
55
+ }
56
+
57
+ public async authorize(
58
+ body: OAuthReqBody
59
+ ): Promise<{ redirect_url: string }> {
60
+ const {
61
+ response_type = 'code',
62
+ client_id,
63
+ redirect_uri,
64
+ state,
65
+ tenant,
66
+ product,
67
+ code_challenge,
68
+ code_challenge_method = '',
69
+ // eslint-disable-next-line no-unused-vars
70
+ provider = 'saml',
71
+ } = body;
72
+
73
+ if (!redirect_uri) {
74
+ throw new JacksonError('Please specify a redirect URL.', 400);
75
+ }
76
+
77
+ if (!state) {
78
+ throw new JacksonError(
79
+ 'Please specify a state to safeguard against XSRF attacks.',
80
+ 400
81
+ );
82
+ }
83
+
84
+ let samlConfig;
85
+
86
+ if (tenant && product) {
87
+ const samlConfigs = await this.configStore.getByIndex({
88
+ name: IndexNames.TenantProduct,
89
+ value: dbutils.keyFromParts(tenant, product),
90
+ });
91
+
92
+ if (!samlConfigs || samlConfigs.length === 0) {
93
+ throw new JacksonError('SAML configuration not found.', 403);
94
+ }
95
+
96
+ // TODO: Support multiple matches
97
+ samlConfig = samlConfigs[0];
98
+ } else if (
99
+ client_id &&
100
+ client_id !== '' &&
101
+ client_id !== 'undefined' &&
102
+ client_id !== 'null'
103
+ ) {
104
+ // if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
105
+ const sp = getEncodedClientId(client_id);
106
+ if (sp?.tenant) {
107
+ const samlConfigs = await this.configStore.getByIndex({
108
+ name: IndexNames.TenantProduct,
109
+ value: dbutils.keyFromParts(sp.tenant, sp.product || ''),
110
+ });
111
+
112
+ if (!samlConfigs || samlConfigs.length === 0) {
113
+ throw new JacksonError('SAML configuration not found.', 403);
114
+ }
115
+
116
+ // TODO: Support multiple matches
117
+ samlConfig = samlConfigs[0];
118
+ } else {
119
+ samlConfig = await this.configStore.get(client_id);
120
+ }
121
+ } else {
122
+ throw new JacksonError(
123
+ 'You need to specify client_id or tenant & product',
124
+ 403
125
+ );
126
+ }
127
+
128
+ if (!samlConfig) {
129
+ throw new JacksonError('SAML configuration not found.', 403);
130
+ }
131
+
132
+ if (!allowed.redirect(redirect_uri, samlConfig.redirectUrl)) {
133
+ throw new JacksonError('Redirect URL is not allowed.', 403);
134
+ }
135
+
136
+ const samlReq = saml.request({
137
+ // @ts-ignore
138
+ entityID: this.opts.samlAudience,
139
+ callbackUrl: this.opts.externalUrl + this.opts.samlPath,
140
+ signingKey: samlConfig.certs.privateKey,
141
+ });
142
+
143
+ const sessionId = crypto.randomBytes(16).toString('hex');
144
+
145
+ await this.sessionStore.put(sessionId, {
146
+ id: samlReq.id,
147
+ redirect_uri,
148
+ response_type,
149
+ state,
150
+ code_challenge,
151
+ code_challenge_method,
152
+ });
153
+
154
+ const redirectUrl = redirect.success(
155
+ samlConfig.idpMetadata.sso.redirectUrl,
156
+ {
157
+ RelayState: relayStatePrefix + sessionId,
158
+ SAMLRequest: Buffer.from(samlReq.request).toString('base64'),
159
+ }
160
+ );
161
+
162
+ return { redirect_url: redirectUrl };
163
+ }
164
+
165
+ public async samlResponse(
166
+ body: SAMLResponsePayload
167
+ ): Promise<{ redirect_url: string }> {
168
+ const { SAMLResponse } = body; // RelayState will contain the sessionId from earlier quasi-oauth flow
169
+
170
+ let RelayState = body.RelayState || '';
171
+
172
+ if (!this.opts.idpEnabled && !RelayState.startsWith(relayStatePrefix)) {
173
+ // IDP is disabled so block the request
174
+
175
+ throw new JacksonError(
176
+ 'IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.',
177
+ 403
178
+ );
179
+ }
180
+
181
+ if (!RelayState.startsWith(relayStatePrefix)) {
182
+ RelayState = '';
183
+ }
184
+
185
+ RelayState = RelayState.replace(relayStatePrefix, '');
186
+
187
+ const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
188
+
189
+ const parsedResp = await saml.parseAsync(rawResponse);
190
+
191
+ const samlConfigs = await this.configStore.getByIndex({
192
+ name: IndexNames.EntityID,
193
+
194
+ // @ts-ignore
195
+ value: parsedResp?.issuer,
196
+ });
197
+
198
+ if (!samlConfigs || samlConfigs.length === 0) {
199
+ throw new JacksonError('SAML configuration not found.', 403);
200
+ }
201
+
202
+ // TODO: Support multiple matches
203
+ const samlConfig = samlConfigs[0];
204
+
205
+ let session;
206
+
207
+ if (RelayState !== '') {
208
+ session = await this.sessionStore.get(RelayState);
209
+ if (!session) {
210
+ throw new JacksonError(
211
+ 'Unable to validate state from the origin request.',
212
+ 403
213
+ );
214
+ }
215
+ }
216
+
217
+ let validateOpts: any = {
218
+ thumbprint: samlConfig.idpMetadata.thumbprint,
219
+ audience: this.opts.samlAudience,
220
+ };
221
+
222
+ if (session && session.id) {
223
+ validateOpts.inResponseTo = session.id;
224
+ }
225
+
226
+ const profile = await saml.validateAsync(rawResponse, validateOpts);
227
+
228
+ // store details against a code
229
+ const code = crypto.randomBytes(20).toString('hex');
230
+
231
+ let codeVal: any = {
232
+ profile,
233
+ clientID: samlConfig.clientID,
234
+ clientSecret: samlConfig.clientSecret,
235
+ };
236
+
237
+ if (session) {
238
+ codeVal.session = session;
239
+ }
240
+
241
+ await this.codeStore.put(code, codeVal);
242
+
243
+ if (
244
+ session &&
245
+ session.redirect_uri &&
246
+ !allowed.redirect(session.redirect_uri, samlConfig.redirectUrl)
247
+ ) {
248
+ throw new JacksonError('Redirect URL is not allowed.', 403);
249
+ }
250
+
251
+ let params: any = {
252
+ code,
253
+ };
254
+
255
+ if (session && session.state) {
256
+ params.state = session.state;
257
+ }
258
+
259
+ const redirectUrl = redirect.success(
260
+ (session && session.redirect_uri) || samlConfig.defaultRedirectUrl,
261
+ params
262
+ );
263
+
264
+ return { redirect_url: redirectUrl };
265
+ }
266
+
267
+ public async token(body: OAuthTokenReq): Promise<OAuthTokenRes> {
268
+ const {
269
+ client_id,
270
+ client_secret,
271
+ code_verifier,
272
+ code,
273
+ grant_type = 'authorization_code',
274
+ } = body;
275
+
276
+ if (grant_type !== 'authorization_code') {
277
+ throw new JacksonError('Unsupported grant_type', 400);
278
+ }
279
+
280
+ if (!code) {
281
+ throw new JacksonError('Please specify code', 400);
282
+ }
283
+
284
+ const codeVal = await this.codeStore.get(code);
285
+ if (!codeVal || !codeVal.profile) {
286
+ throw new JacksonError('Invalid code', 403);
287
+ }
288
+
289
+ if (client_id && client_secret) {
290
+ // check if we have an encoded client_id
291
+ if (client_id !== 'dummy' && client_secret !== 'dummy') {
292
+ const sp = getEncodedClientId(client_id);
293
+ if (!sp) {
294
+ // OAuth flow
295
+ if (
296
+ client_id !== codeVal.clientID ||
297
+ client_secret !== codeVal.clientSecret
298
+ ) {
299
+ throw new JacksonError('Invalid client_id or client_secret', 401);
300
+ }
301
+ }
302
+ }
303
+ } else if (code_verifier) {
304
+ // PKCE flow
305
+ let cv = code_verifier;
306
+ if (codeVal.session.code_challenge_method.toLowerCase() === 's256') {
307
+ cv = codeVerifier.encode(code_verifier);
308
+ }
309
+
310
+ if (codeVal.session.code_challenge !== cv) {
311
+ throw new JacksonError('Invalid code_verifier', 401);
312
+ }
313
+ } else if (codeVal && codeVal.session) {
314
+ throw new JacksonError(
315
+ 'Please specify client_secret or code_verifier',
316
+ 401
317
+ );
318
+ }
319
+
320
+ // store details against a token
321
+ const token = crypto.randomBytes(20).toString('hex');
322
+
323
+ await this.tokenStore.put(token, codeVal.profile);
324
+
325
+ return {
326
+ access_token: token,
327
+ token_type: 'bearer',
328
+ expires_in: this.opts.db.ttl,
329
+ };
330
+ }
331
+
332
+ public async userInfo(token: string): Promise<Profile> {
333
+ const { claims } = await this.tokenStore.get(token);
334
+
335
+ return claims;
336
+ }
337
+ }
@@ -0,0 +1,17 @@
1
+ import { Request } from 'express';
2
+
3
+ export const extractAuthToken = (req: Request): string | null => {
4
+ const authHeader = req.get('authorization');
5
+ const parts = (authHeader || '').split(' ');
6
+
7
+ if (parts.length > 1) {
8
+ return parts[1];
9
+ }
10
+
11
+ return null;
12
+ };
13
+
14
+ export enum IndexNames {
15
+ EntityID = 'entityID',
16
+ TenantProduct = 'tenantProduct',
17
+ }