Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
s2sAuthWrapper,
} from '@adobe/spacecat-shared-http-utils';
import AuthInfo from '@adobe/spacecat-shared-http-utils/src/auth/auth-info.js';
import AbstractHandler from '@adobe/spacecat-shared-http-utils/src/auth/handlers/abstract.js';
import { imsClientWrapper } from '@adobe/spacecat-shared-ims-client';
import {
elevatedSlackClientWrapper,
Expand Down Expand Up @@ -136,36 +137,50 @@ function localCORSWrapper(fn) {
}
/* c8 ignore stop */

/* c8 ignore start */
/**
* This is the main function
* @param {Request} request the request object (see fetch api)
* @param {UniversalContext} context the context of the universal serverless function
* @returns {Response} a response
* Auth handler that bypasses authentication when SKIP_AUTH=true.
* For local development only — injects a mock admin identity.
*/
async function run(request, context) {
const { log, pathInfo, env } = context;
const { route, suffix, method } = pathInfo;
class SkipAuthHandler extends AbstractHandler {
constructor(log) {
super('skipAuth', log);
}

// Add mock authInfo when authentication is skipped
/* c8 ignore start */
if (env.SKIP_AUTH === 'true' && !context.attributes?.authInfo) {
if (!context.attributes) {
context.attributes = {};
// eslint-disable-next-line no-unused-vars,class-methods-use-this
async checkAuth(request, context) {
if (context.env?.SKIP_AUTH !== 'true') {
return null;
}
// Create a mock admin authInfo
context.attributes.authInfo = new AuthInfo()
// Defense-in-depth: refuse to skip auth in a deployed Lambda environment
if (context.func?.name || process.env.AWS_LAMBDA_FUNCTION_NAME) {
this.log('SKIP_AUTH is true but running in Lambda - ignoring', 'warn');
return null;
}
this.log('SKIP_AUTH is true - injecting mock admin identity', 'info');
return new AuthInfo()
.withAuthenticated(true)
.withProfile({
user_id: 'local-dev-admin',
email: 'admin@localhost',
is_admin: true,
// Empty tenants means hasOrganization will return false, but is_admin bypasses that
tenants: [],
})
.withType('api_key')
.withScopes([{ name: 'admin' }]);
}
/* c8 ignore stop */
}
/* c8 ignore stop */

/**
* This is the main function
* @param {Request} request the request object (see fetch api)
* @param {UniversalContext} context the context of the universal serverless function
* @returns {Response} a response
*/
async function run(request, context) {
const { log, pathInfo } = context;
const { route, suffix, method } = pathInfo;

if (!hasText(route)) {
log.info(`Unable to extract path info. Wrong format: ${suffix}`);
Expand Down Expand Up @@ -329,7 +344,9 @@ const { WORKSPACE_EXTERNAL } = SLACK_TARGETS;
// 2. authWrapper — handles JWT, IMS, scoped API key, legacy API key
const wrappedMain = wrap(run)
.with(authWrapper, {
authHandlers: [JwtHandler, AdobeImsHandler, ScopedApiKeyHandler, LegacyApiKeyHandler],
authHandlers: [
SkipAuthHandler, JwtHandler, AdobeImsHandler, ScopedApiKeyHandler, LegacyApiKeyHandler,
],
})
.with(s2sAuthWrapper, { routeCapabilities: routeRequiredCapabilities });

Expand Down
Loading