@adobe/spacecat-shared-http-utils 1.3.4 → 1.4.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [@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)
2
+
3
+
4
+ ### Features
5
+
6
+ * move auth wrappers from api-service ([#307](https://github.com/adobe/spacecat-shared/issues/307)) ([3094a99](https://github.com/adobe/spacecat-shared/commit/3094a999f330dbe007b64fbfc3f282bd56807b37))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update external fixes ([#304](https://github.com/adobe/spacecat-shared/issues/304)) ([c6c56a7](https://github.com/adobe/spacecat-shared/commit/c6c56a72897acb60fb042215b708816ec16a5870))
14
+
1
15
  # [@adobe/spacecat-shared-http-utils-v1.3.4](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-http-utils-v1.3.3...@adobe/spacecat-shared-http-utils-v1.3.4) (2024-06-07)
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.4",
3
+ "version": "1.4.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,14 @@
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
+ "jose": "5.6.3"
33
35
  },
34
36
  "devDependencies": {
35
- "chai": "4.4.1"
37
+ "@adobe/helix-shared-wrap": "2.0.2",
38
+ "chai": "4.5.0",
39
+ "chai-as-promised": "8.0.0",
40
+ "sinon": "18.0.0"
36
41
  }
37
42
  }
@@ -0,0 +1,53 @@
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
+ });
22
+ }
23
+
24
+ /**
25
+ * Set the authenticated flag.
26
+ * @param {boolean} value - The value of the authenticated flag
27
+ * @returns {AuthInfo} The auth info object
28
+ */
29
+ withAuthenticated(value) {
30
+ this.authenticated = value;
31
+ return this;
32
+ }
33
+
34
+ /**
35
+ * Set the profile. A profile is an object that contains information about the user.
36
+ * @param {Object} profile - The user profile
37
+ * @return {AuthInfo} The auth info object
38
+ */
39
+ withProfile(profile) {
40
+ this.profile = profile;
41
+ return this;
42
+ }
43
+
44
+ /**
45
+ * Set the type of the authentication that was performed.
46
+ * @param {string} value - The type of the authentication
47
+ * @return {AuthInfo} The auth info object
48
+ */
49
+ withType(value) {
50
+ this.type = value;
51
+ return this;
52
+ }
53
+ }
@@ -0,0 +1,53 @@
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 AuthenticationManager from './authentication-manager.js';
16
+
17
+ const ANONYMOUS_ENDPOINTS = [
18
+ 'GET /slack/events',
19
+ 'POST /slack/events',
20
+ ];
21
+
22
+ export function authWrapper(fn, opts = {}) {
23
+ let authenticationManager;
24
+
25
+ return async (request, context) => {
26
+ const { log, pathInfo: { method, suffix } } = context;
27
+
28
+ const route = `${method.toUpperCase()} ${suffix}`;
29
+
30
+ if (ANONYMOUS_ENDPOINTS.includes(route)
31
+ || route.startsWith('POST /hooks/site-detection/')
32
+ || method.toUpperCase() === 'OPTIONS') {
33
+ return fn(request, context);
34
+ }
35
+
36
+ if (!authenticationManager) {
37
+ if (!Array.isArray(opts.authHandlers)) {
38
+ log.error('Invalid auth handlers');
39
+ return new Response('Server error', { status: 500 });
40
+ }
41
+
42
+ authenticationManager = AuthenticationManager.create(opts.authHandlers, log);
43
+ }
44
+
45
+ try {
46
+ await authenticationManager.authenticate(request, context);
47
+ } catch (error) {
48
+ return new Response('Unauthorized', { status: 401 });
49
+ }
50
+
51
+ return fn(request, context);
52
+ };
53
+ }
@@ -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,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,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,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.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,8 @@ 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
+
93
+ export { AdobeImsHandler, LegacyApiKeyHandler };