@adobe/spacecat-shared-http-utils 1.3.5 → 1.5.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/CHANGELOG.md +14 -0
- package/package.json +10 -4
- package/src/auth/auth-info.js +82 -0
- package/src/auth/auth-wrapper.js +63 -0
- package/src/auth/authentication-manager.js +90 -0
- package/src/auth/check-scopes.js +41 -0
- package/src/auth/errors/not-authenticated.js +17 -0
- package/src/auth/generate-hash.js +16 -0
- package/src/auth/handlers/abstract.js +44 -0
- package/src/auth/handlers/config/ims-stg.js +24 -0
- package/src/auth/handlers/config/ims.js +23 -0
- package/src/auth/handlers/ims.js +138 -0
- package/src/auth/handlers/legacy-api-key.js +64 -0
- package/src/auth/handlers/scoped-api-key.js +69 -0
- package/src/auth/readme.md +82 -0
- package/src/enrich-path-info-wrapper.js +26 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-http-utils-v1.5.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-http-utils-v1.4.0...@adobe/spacecat-shared-http-utils-v1.5.0) (2024-08-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Introduce scoped API keys ([#312](https://github.com/adobe/spacecat-shared/issues/312)) ([449d273](https://github.com/adobe/spacecat-shared/commit/449d2736154d7e92fb4a3d1f9f290e15e665aa5e))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-http-utils-v1.4.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-http-utils-v1.3.5...@adobe/spacecat-shared-http-utils-v1.4.0) (2024-07-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* move auth wrappers from api-service ([#307](https://github.com/adobe/spacecat-shared/issues/307)) ([3094a99](https://github.com/adobe/spacecat-shared/commit/3094a999f330dbe007b64fbfc3f282bd56807b37))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-http-utils-v1.3.5](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-http-utils-v1.3.4...@adobe/spacecat-shared-http-utils-v1.3.5) (2024-07-27)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-http-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - HTTP Utils",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"types": "src/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "c8 mocha",
|
|
9
|
+
"test": "c8 mocha --spec=test/**/*.test.js",
|
|
10
10
|
"lint": "eslint .",
|
|
11
11
|
"clean": "rm -rf package-lock.json node_modules"
|
|
12
12
|
},
|
|
@@ -29,9 +29,15 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@adobe/fetch": "4.1.8"
|
|
32
|
+
"@adobe/fetch": "4.1.8",
|
|
33
|
+
"@adobe/spacecat-shared-utils": "1.19.1",
|
|
34
|
+
"@adobe/spacecat-shared-data-access": "1.39.0",
|
|
35
|
+
"jose": "5.6.3"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
|
-
"
|
|
38
|
+
"@adobe/helix-shared-wrap": "2.0.2",
|
|
39
|
+
"chai": "4.5.0",
|
|
40
|
+
"chai-as-promised": "8.0.0",
|
|
41
|
+
"sinon": "18.0.0"
|
|
36
42
|
}
|
|
37
43
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The auth info class represents information about the current authentication state.
|
|
15
|
+
*/
|
|
16
|
+
export default class AuthInfo {
|
|
17
|
+
constructor() {
|
|
18
|
+
Object.assign(this, {
|
|
19
|
+
authenticated: false,
|
|
20
|
+
profile: null,
|
|
21
|
+
scopes: [],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set the authenticated flag.
|
|
27
|
+
* @param {boolean} value - The value of the authenticated flag
|
|
28
|
+
* @returns {AuthInfo} The auth info object
|
|
29
|
+
*/
|
|
30
|
+
withAuthenticated(value) {
|
|
31
|
+
this.authenticated = value;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set the profile. A profile is an object that contains information about the user.
|
|
37
|
+
* @param {Object} profile - The user profile
|
|
38
|
+
* @return {AuthInfo} The auth info object
|
|
39
|
+
*/
|
|
40
|
+
withProfile(profile) {
|
|
41
|
+
this.profile = profile;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set the type of the authentication that was performed.
|
|
47
|
+
* @param {string} value - The type of the authentication
|
|
48
|
+
* @return {AuthInfo} The auth info object
|
|
49
|
+
*/
|
|
50
|
+
withType(value) {
|
|
51
|
+
this.type = value;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Set the reason that authentication has failed.
|
|
57
|
+
* @param {string} reason - The reason for auth failure
|
|
58
|
+
* @return {AuthInfo} The auth info object
|
|
59
|
+
*/
|
|
60
|
+
withReason(reason) {
|
|
61
|
+
this.reason = reason;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set the scopes that this auth info instance has access to.
|
|
67
|
+
* @param {Array<{name: string, domains?: Array<string>}>} scopes - The array of scope objects
|
|
68
|
+
* @return {AuthInfo} The auth info object
|
|
69
|
+
*/
|
|
70
|
+
withScopes(scopes) {
|
|
71
|
+
this.scopes = scopes;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getScopes() { return this.scopes; }
|
|
76
|
+
|
|
77
|
+
getProfile() { return this.profile; }
|
|
78
|
+
|
|
79
|
+
getReason() { return this.reason; }
|
|
80
|
+
|
|
81
|
+
isAuthenticated() { return this.authenticated; }
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Response } from '@adobe/fetch';
|
|
14
|
+
|
|
15
|
+
import { isObject } from '@adobe/spacecat-shared-utils';
|
|
16
|
+
import AuthenticationManager from './authentication-manager.js';
|
|
17
|
+
import { checkScopes } from './check-scopes.js';
|
|
18
|
+
|
|
19
|
+
const ANONYMOUS_ENDPOINTS = [
|
|
20
|
+
'GET /slack/events',
|
|
21
|
+
'POST /slack/events',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function authWrapper(fn, opts = {}) {
|
|
25
|
+
let authenticationManager;
|
|
26
|
+
|
|
27
|
+
return async (request, context) => {
|
|
28
|
+
const { log, pathInfo: { method, suffix } } = context;
|
|
29
|
+
|
|
30
|
+
const route = `${method.toUpperCase()} ${suffix}`;
|
|
31
|
+
|
|
32
|
+
if (ANONYMOUS_ENDPOINTS.includes(route)
|
|
33
|
+
|| route.startsWith('POST /hooks/site-detection/')
|
|
34
|
+
|| method.toUpperCase() === 'OPTIONS') {
|
|
35
|
+
return fn(request, context);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!authenticationManager) {
|
|
39
|
+
if (!Array.isArray(opts.authHandlers)) {
|
|
40
|
+
log.error('Invalid auth handlers');
|
|
41
|
+
return new Response('Server error', { status: 500 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
authenticationManager = AuthenticationManager.create(opts.authHandlers, log);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const authInfo = await authenticationManager.authenticate(request, context);
|
|
49
|
+
|
|
50
|
+
// Add a helper function to the context for checking scoped API keys.
|
|
51
|
+
// authInfo is available at context.attributes.authInfo.
|
|
52
|
+
if (!isObject(context.auth)) {
|
|
53
|
+
context.auth = {
|
|
54
|
+
checkScopes: (scopes) => checkScopes(scopes, authInfo, log),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return new Response('Unauthorized', { status: 401 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return fn(request, context);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import { isObject } from '@adobe/spacecat-shared-utils';
|
|
13
|
+
|
|
14
|
+
import NotAuthenticatedError from './errors/not-authenticated.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Authentication manager. It will try to authenticate the request with all the provided handlers.
|
|
18
|
+
* If none of the handlers are able to authenticate the request, it will throw
|
|
19
|
+
* a NotAuthenticatedError.
|
|
20
|
+
* @class
|
|
21
|
+
*/
|
|
22
|
+
export default class AuthenticationManager {
|
|
23
|
+
constructor(log) {
|
|
24
|
+
this.log = log;
|
|
25
|
+
this.handlers = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register a handler. This method is private and should not be called directly.
|
|
30
|
+
* The handlers are used in the order they are registered.
|
|
31
|
+
* @param {AbstractHandler} Handler - The handler to be registered
|
|
32
|
+
*/
|
|
33
|
+
#registerHandler(Handler) {
|
|
34
|
+
this.handlers.push(new Handler(this.log));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Authenticate the request with all the handlers.
|
|
39
|
+
* @param {Object} request - The request object
|
|
40
|
+
* @param {UniversalContext} context - The context object
|
|
41
|
+
* @return {Promise<AuthInfo>} The authentication info
|
|
42
|
+
* @throws {NotAuthenticatedError} If no handler was able to authenticate the request
|
|
43
|
+
*/
|
|
44
|
+
async authenticate(request, context) {
|
|
45
|
+
for (const handler of this.handlers) {
|
|
46
|
+
this.log.debug(`Trying to authenticate with ${handler.name}`);
|
|
47
|
+
|
|
48
|
+
// eslint-disable-next-line no-await-in-loop
|
|
49
|
+
const authInfo = await handler.checkAuth(request, context);
|
|
50
|
+
|
|
51
|
+
if (isObject(authInfo)) {
|
|
52
|
+
this.log.info(`Authenticated with ${handler.name}`);
|
|
53
|
+
|
|
54
|
+
context.attributes = context.attributes || {};
|
|
55
|
+
context.attributes.authInfo = authInfo;
|
|
56
|
+
|
|
57
|
+
return authInfo;
|
|
58
|
+
} else {
|
|
59
|
+
this.log.debug(`Failed to authenticate with ${handler.name}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.log.info('No authentication handler was able to authenticate the request');
|
|
64
|
+
throw new NotAuthenticatedError();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create an instance of AuthenticationManager.
|
|
69
|
+
* @param {Array<AbstractHandler>} handlers - The handlers to be used for authentication
|
|
70
|
+
* @param {Object} log - The logger object
|
|
71
|
+
* @return {AuthenticationManager} The authentication manager
|
|
72
|
+
*/
|
|
73
|
+
static create(handlers, log) {
|
|
74
|
+
const manager = new AuthenticationManager(log);
|
|
75
|
+
|
|
76
|
+
if (!Array.isArray(handlers)) {
|
|
77
|
+
throw new Error('Invalid handlers');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!handlers.length) {
|
|
81
|
+
throw new Error('No handlers provided');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
handlers.forEach((handler) => {
|
|
85
|
+
manager.#registerHandler(handler);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return manager;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isObject } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if the given AuthInfo has the requested scopes. Throws an error if any scopes are missing.
|
|
17
|
+
* @param {Array<string>} scopes - The scopes required for the request
|
|
18
|
+
* @param {AuthInfo} authInfo - Authentication state for the current request
|
|
19
|
+
* @param {Logger} log - Logger
|
|
20
|
+
*/
|
|
21
|
+
export function checkScopes(scopes, authInfo, log) {
|
|
22
|
+
if (!isObject(authInfo)) {
|
|
23
|
+
throw new Error('Auth info is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check that each required scope is present in authInfo
|
|
27
|
+
const missingScopes = [];
|
|
28
|
+
const authInfoScopeNames = authInfo.getScopes().map((scopeObject) => scopeObject.name);
|
|
29
|
+
scopes.forEach((scope) => {
|
|
30
|
+
if (!authInfoScopeNames.includes(scope)) {
|
|
31
|
+
missingScopes.push(scope);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (missingScopes.length > 0) {
|
|
36
|
+
log.error(`API key with ID: ${authInfo.getProfile()?.api_key_id} does not have required scopes. It's missing: ${missingScopes.join(',')}`);
|
|
37
|
+
throw new Error(`API key is missing the [${missingScopes.join(',')}] scope(s) required for this resource`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Otherwise: all good
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export default class NotAuthenticatedError extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('Not authenticated');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
export function hashWithSHA256(input) {
|
|
15
|
+
return crypto.createHash('sha256').update(input).digest('hex');
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export default class AbstractHandler {
|
|
14
|
+
constructor(name, log) {
|
|
15
|
+
if (new.target === AbstractHandler) {
|
|
16
|
+
throw new TypeError('Cannot construct AbstractHandler instances directly');
|
|
17
|
+
}
|
|
18
|
+
this.name = name;
|
|
19
|
+
this.logger = log;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Log a message with a specific log level. Log messages are prefixed with the handler name.
|
|
24
|
+
* @param {string} message - The log message
|
|
25
|
+
* @param {string} level - The log level
|
|
26
|
+
*/
|
|
27
|
+
log(message, level) {
|
|
28
|
+
this.logger[level](`[${this.name}] ${message}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check the authentication of a request. This method must be implemented by the concrete handler.
|
|
33
|
+
* It should return an object of type AuthInfo if the request is authenticated,
|
|
34
|
+
* otherwise it should return null.
|
|
35
|
+
* @param {Object} request - The request object
|
|
36
|
+
* @param {UniversalContext} context - The context object
|
|
37
|
+
* @return {Promise<AuthInfo|null>} The authentication info
|
|
38
|
+
* or null if the request is not authenticated
|
|
39
|
+
*/
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars,class-methods-use-this
|
|
41
|
+
async checkAuth(request, context) {
|
|
42
|
+
throw new Error('checkAuth method must be implemented');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
export default {
|
|
13
|
+
name: 'ims-na1-stg1',
|
|
14
|
+
discoveryUrl: 'https://ims-na1-stg1.adobelogin.com/ims/.well-known/openid-configuration',
|
|
15
|
+
// todo: fetch from discovery document
|
|
16
|
+
discovery: {
|
|
17
|
+
issuer: 'https://ims-na1-stg1.adobelogin.com',
|
|
18
|
+
authorization_endpoint: 'https://ims-na1-stg1.adobelogin.com/ims/authorize/v2',
|
|
19
|
+
token_endpoint: 'https://ims-na1-stg1.adobelogin.com/ims/token/v3',
|
|
20
|
+
userinfo_endpoint: 'https://ims-na1-stg1.adobelogin.com/ims/userinfo/v2',
|
|
21
|
+
revocation_endpoint: 'https://ims-na1-stg1.adobelogin.com/ims/revoke',
|
|
22
|
+
jwks_uri: 'https://ims-na1-stg1.adobelogin.com/ims/keys',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
export default {
|
|
13
|
+
name: 'ims-na1',
|
|
14
|
+
discoveryUrl: 'https://ims-na1.adobelogin.com/ims/.well-known/openid-configuration',
|
|
15
|
+
discovery: {
|
|
16
|
+
issuer: 'https://ims-na1.adobelogin.com',
|
|
17
|
+
authorization_endpoint: 'https://ims-na1.adobelogin.com/ims/authorize/v2',
|
|
18
|
+
token_endpoint: 'https://ims-na1.adobelogin.com/ims/token/v3',
|
|
19
|
+
userinfo_endpoint: 'https://ims-na1.adobelogin.com/ims/userinfo/v2',
|
|
20
|
+
revocation_endpoint: 'https://ims-na1.adobelogin.com/ims/revoke',
|
|
21
|
+
jwks_uri: 'https://ims-na1.adobelogin.com/ims/keys',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { hasText } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import {
|
|
15
|
+
createLocalJWKSet,
|
|
16
|
+
createRemoteJWKSet,
|
|
17
|
+
decodeJwt,
|
|
18
|
+
jwtVerify,
|
|
19
|
+
} from 'jose';
|
|
20
|
+
|
|
21
|
+
import configProd from './config/ims.js';
|
|
22
|
+
import configDev from './config/ims-stg.js';
|
|
23
|
+
|
|
24
|
+
import AbstractHandler from './abstract.js';
|
|
25
|
+
import AuthInfo from '../auth-info.js';
|
|
26
|
+
|
|
27
|
+
const IGNORED_PROFILE_PROPS = [
|
|
28
|
+
'id',
|
|
29
|
+
'type',
|
|
30
|
+
'as_id',
|
|
31
|
+
'ctp',
|
|
32
|
+
'pac',
|
|
33
|
+
'rtid',
|
|
34
|
+
'moi',
|
|
35
|
+
'rtea',
|
|
36
|
+
'user_id',
|
|
37
|
+
'fg',
|
|
38
|
+
'aa_id',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const loadConfig = (context) => {
|
|
42
|
+
const funcVersion = context.func?.version;
|
|
43
|
+
const isDev = /^ci\d*$/i.test(funcVersion);
|
|
44
|
+
context.log.debug(`Function version: ${funcVersion} (isDev: ${isDev})`);
|
|
45
|
+
/* c8 ignore next */
|
|
46
|
+
return isDev ? configDev : configProd;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getBearerToken = (context) => {
|
|
50
|
+
const authorizationHeader = context.pathInfo?.headers?.authorization || '';
|
|
51
|
+
|
|
52
|
+
if (!authorizationHeader.startsWith('Bearer ')) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return authorizationHeader.replace('Bearer ', '');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const transformProfile = (payload) => {
|
|
60
|
+
const profile = { ...payload };
|
|
61
|
+
|
|
62
|
+
profile.email = payload.user_id;
|
|
63
|
+
IGNORED_PROFILE_PROPS.forEach((prop) => delete profile[prop]);
|
|
64
|
+
|
|
65
|
+
return profile;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default class AdobeImsHandler extends AbstractHandler {
|
|
69
|
+
constructor(log) {
|
|
70
|
+
super('ims', log);
|
|
71
|
+
this.jwksCache = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async #getJwksUri(config) {
|
|
75
|
+
if (!this.jwksCache) {
|
|
76
|
+
/* c8 ignore next 3 */
|
|
77
|
+
this.jwksCache = config.discovery.jwks
|
|
78
|
+
? createLocalJWKSet(config.discovery.jwks)
|
|
79
|
+
: createRemoteJWKSet(new URL(config.discovery.jwks_uri));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return this.jwksCache;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async #validateToken(token, config) {
|
|
86
|
+
const decoded = await decodeJwt(token);
|
|
87
|
+
if (config.name !== decoded.as) {
|
|
88
|
+
throw new Error(`Token not issued by expected idp: ${config.name} != ${decoded.as}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const jwks = await this.#getJwksUri(config);
|
|
92
|
+
const { payload } = await jwtVerify(token, jwks);
|
|
93
|
+
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const expiresIn = Number.parseInt(payload.expires_in, 10);
|
|
96
|
+
const createdAt = Number.parseInt(payload.created_at, 10);
|
|
97
|
+
|
|
98
|
+
if (Number.isNaN(expiresIn) || Number.isNaN(createdAt)) {
|
|
99
|
+
throw new Error('expires_in and created_at claims must be numbers');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (createdAt >= now) {
|
|
103
|
+
throw new Error('created_at should be in the past');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const ttl = Math.floor((createdAt + expiresIn - now) / 1000);
|
|
107
|
+
if (ttl <= 0) {
|
|
108
|
+
throw new Error('token expired');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
payload.ttl = ttl;
|
|
112
|
+
|
|
113
|
+
return payload;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async checkAuth(request, context) {
|
|
117
|
+
const token = getBearerToken(context);
|
|
118
|
+
if (!hasText(token)) {
|
|
119
|
+
this.log('No bearer token provided', 'debug');
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const config = loadConfig(context);
|
|
125
|
+
const payload = await this.#validateToken(token, config);
|
|
126
|
+
const profile = transformProfile(payload);
|
|
127
|
+
|
|
128
|
+
return new AuthInfo()
|
|
129
|
+
.withType(this.name)
|
|
130
|
+
.withAuthenticated(true)
|
|
131
|
+
.withProfile(profile);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
this.log(`Failed to validate token: ${e.message}`, 'error');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { hasText } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import AuthInfo from '../auth-info.js';
|
|
16
|
+
import AbstractHandler from './abstract.js';
|
|
17
|
+
|
|
18
|
+
const ADMIN_ENDPOINTS = [
|
|
19
|
+
'GET /trigger',
|
|
20
|
+
'POST /sites',
|
|
21
|
+
'POST /event/fulfillment',
|
|
22
|
+
'POST /slack/channels/invite-by-user-id',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handler for legacy API key authentication. This handler is used to authenticate requests
|
|
27
|
+
* that contain a legacy API key in the `x-api-key` header.
|
|
28
|
+
*/
|
|
29
|
+
export default class LegacyApiKeyHandler extends AbstractHandler {
|
|
30
|
+
constructor(log) {
|
|
31
|
+
super('legacyApiKey', log);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async checkAuth(request, context) {
|
|
35
|
+
const expectedUserApiKey = context.env?.USER_API_KEY;
|
|
36
|
+
const expectedAdminApiKey = context.env?.ADMIN_API_KEY;
|
|
37
|
+
|
|
38
|
+
if (!hasText(expectedUserApiKey) || !hasText(expectedAdminApiKey)) {
|
|
39
|
+
this.log('API keys were not configured', 'error');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const apiKeyFromHeader = context.pathInfo?.headers['x-api-key'];
|
|
44
|
+
|
|
45
|
+
if (!hasText(apiKeyFromHeader)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isRouteAdminOnly = ADMIN_ENDPOINTS.includes(context.pathInfo.route);
|
|
50
|
+
const isApiKeyValid = isRouteAdminOnly
|
|
51
|
+
? apiKeyFromHeader === expectedAdminApiKey
|
|
52
|
+
: apiKeyFromHeader === expectedUserApiKey || apiKeyFromHeader === expectedAdminApiKey;
|
|
53
|
+
|
|
54
|
+
if (isApiKeyValid) {
|
|
55
|
+
const profile = isRouteAdminOnly ? { user_id: 'admin' } : { user_id: 'legacy-user' };
|
|
56
|
+
return new AuthInfo()
|
|
57
|
+
.withAuthenticated(true)
|
|
58
|
+
.withProfile(profile)
|
|
59
|
+
.withType(this.name);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { hasText, isIsoDate } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import AbstractHandler from './abstract.js';
|
|
15
|
+
import { hashWithSHA256 } from '../generate-hash.js';
|
|
16
|
+
import AuthInfo from '../auth-info.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handler to support API keys which include scope details. These API keys are stored in the data
|
|
20
|
+
* layer and require context.dataAccess in order to authenticate each request.
|
|
21
|
+
*/
|
|
22
|
+
export default class ScopedApiKeyHandler extends AbstractHandler {
|
|
23
|
+
constructor(log) {
|
|
24
|
+
super('scopedApiKey', log);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async checkAuth(request, context) {
|
|
28
|
+
const { dataAccess, pathInfo: { headers = {} } } = context;
|
|
29
|
+
if (!dataAccess) {
|
|
30
|
+
throw new Error('Data access is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const apiKeyFromHeader = headers['x-api-key'];
|
|
34
|
+
if (!hasText(apiKeyFromHeader)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Keys are stored by their hash, so we need to hash the key to look it up
|
|
39
|
+
const hashedKey = hashWithSHA256(apiKeyFromHeader);
|
|
40
|
+
const apiKeyEntity = await dataAccess.getApiKeyByHashedKey(hashedKey);
|
|
41
|
+
|
|
42
|
+
if (!apiKeyEntity) {
|
|
43
|
+
this.log(`No API key entity found in the data layer for the provided API key: ${apiKeyFromHeader}`, 'error');
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// We have an API key entity, and need to check if it's still valid
|
|
48
|
+
const authInfo = new AuthInfo()
|
|
49
|
+
.withProfile(apiKeyEntity) // Include the API key entity as the profile
|
|
50
|
+
.withType(this.name);
|
|
51
|
+
|
|
52
|
+
// Verify that the api key has not expired or been revoked
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
if (isIsoDate(apiKeyEntity.getExpiresAt()) && apiKeyEntity.getExpiresAt() < now) {
|
|
55
|
+
this.log(`API key has expired. Name: ${apiKeyEntity.getName()}, id: ${apiKeyEntity.getId()}`, 'error');
|
|
56
|
+
return authInfo.withReason('API key has expired');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isIsoDate(apiKeyEntity.getRevokedAt()) && apiKeyEntity.getRevokedAt() < now) {
|
|
60
|
+
this.log(`API key has been revoked. Name: ${apiKeyEntity.getName()} id: ${apiKeyEntity.getId()}`, 'error');
|
|
61
|
+
return authInfo.withReason('API key has been revoked');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// API key is valid: return auth info with scope details from the API key entity
|
|
65
|
+
return authInfo
|
|
66
|
+
.withAuthenticated(true)
|
|
67
|
+
.withScopes(apiKeyEntity.getScopes());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Sure, here's a README that provides an overview of the architecture, how to use the authentication wrapper, and how to implement an example authentication handler:
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Authentication System
|
|
6
|
+
|
|
7
|
+
The authentication system is designed to secure AWS Lambda functions by integrating various authentication methods.
|
|
8
|
+
It includes an authentication wrapper, a manager, and handler classes that implement specific authentication logic.
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
The architecture of the authentication system consists of the following components:
|
|
13
|
+
|
|
14
|
+
### Authentication Wrapper
|
|
15
|
+
|
|
16
|
+
The `authWrapper` function wraps your Lambda function, enforcing authentication based on defined handlers.
|
|
17
|
+
It determines which endpoints require authentication and delegates the actual authentication process to the `AuthenticationManager`.
|
|
18
|
+
|
|
19
|
+
### Authentication Manager
|
|
20
|
+
|
|
21
|
+
The `AuthenticationManager` class manages multiple authentication handlers.
|
|
22
|
+
It attempts to authenticate each request by delegating to the registered handlers in sequence.
|
|
23
|
+
|
|
24
|
+
### AuthInfo Class
|
|
25
|
+
|
|
26
|
+
The `AuthInfo` class represents information about the current authentication state,
|
|
27
|
+
including whether the user is authenticated, the user's profile, and the type of authentication performed.
|
|
28
|
+
|
|
29
|
+
### Handlers
|
|
30
|
+
|
|
31
|
+
Handlers are responsible for implementing specific authentication mechanisms.
|
|
32
|
+
Each handler extends the `AbstractHandler` class and implements the `checkAuth` method.
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
### Using the Authentication Wrapper
|
|
37
|
+
|
|
38
|
+
To secure your Lambda function, wrap it with the `authWrapper` function. You must provide an array of authentication handlers to the wrapper.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
import { Response } from '@adobe/fetch';
|
|
42
|
+
import auth from './auth-wrapper.js';
|
|
43
|
+
|
|
44
|
+
const run = async (request, context) => {
|
|
45
|
+
const authInfo = context.attributes.authInfo;
|
|
46
|
+
return new Response(`Hello, ${authInfo.profile.user_id}!`, { status: 200 });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const main = wrap(run)
|
|
50
|
+
.with(auth, { authHandlers: [LegacyApiKeyHandler, AdobeImsHandler] });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Implementing an Authentication Handler
|
|
54
|
+
|
|
55
|
+
To implement a new authentication handler, extend the `AbstractHandler` class and implement the `checkAuth` method.
|
|
56
|
+
This method should return an `AuthInfo` object if authentication is successful or `null` if it fails.
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import AbstractHandler from './abstract.js';
|
|
60
|
+
import AuthInfo from '../auth-info.js';
|
|
61
|
+
|
|
62
|
+
export default class ExampleHandler extends AbstractHandler {
|
|
63
|
+
constructor(log) {
|
|
64
|
+
super('exampleHandler', log);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async checkAuth(request, context) {
|
|
68
|
+
// Implement your authentication logic here
|
|
69
|
+
|
|
70
|
+
const isAuthenticated = true; // Replace with actual authentication logic
|
|
71
|
+
if (isAuthenticated) {
|
|
72
|
+
const profile = { user_id: 'example-user' };
|
|
73
|
+
return new AuthInfo()
|
|
74
|
+
.withAuthenticated(true)
|
|
75
|
+
.withProfile(profile)
|
|
76
|
+
.withType(this.name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function enrichPathInfo(fn) { // export for testing
|
|
14
|
+
return async (request, context) => {
|
|
15
|
+
const [, route] = context?.pathInfo?.suffix?.split(/\/+/) || [];
|
|
16
|
+
context.pathInfo = {
|
|
17
|
+
...context.pathInfo,
|
|
18
|
+
...{
|
|
19
|
+
method: request.method.toUpperCase(),
|
|
20
|
+
headers: request.headers.plain(),
|
|
21
|
+
route,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
return fn(request, context);
|
|
25
|
+
};
|
|
26
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -24,3 +24,8 @@ export declare function notFound(message?: string, headers?: object): Response;
|
|
|
24
24
|
export declare function internalServerError(message?: string, headers?: object): Response;
|
|
25
25
|
|
|
26
26
|
export declare function found(location: string): Response;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Utility functions
|
|
30
|
+
*/
|
|
31
|
+
export function hashWithSHA256(input: string): string;
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
import { Response } from '@adobe/fetch';
|
|
14
14
|
|
|
15
|
+
import LegacyApiKeyHandler from './auth/handlers/legacy-api-key.js';
|
|
16
|
+
import AdobeImsHandler from './auth/handlers/ims.js';
|
|
17
|
+
|
|
15
18
|
const HEADER_CONTENT_TYPE = 'content-type';
|
|
16
19
|
const HEADER_ERROR = 'x-error';
|
|
17
20
|
|
|
@@ -83,3 +86,9 @@ export function internalServerError(message = 'internal server error', headers =
|
|
|
83
86
|
...headers,
|
|
84
87
|
});
|
|
85
88
|
}
|
|
89
|
+
|
|
90
|
+
export { authWrapper } from './auth/auth-wrapper.js';
|
|
91
|
+
export { enrichPathInfo } from './enrich-path-info-wrapper.js';
|
|
92
|
+
export { hashWithSHA256 } from './auth/generate-hash.js';
|
|
93
|
+
|
|
94
|
+
export { AdobeImsHandler, LegacyApiKeyHandler };
|