From db1ec7ea0324dde07a3092b457e1c8832cf525cc Mon Sep 17 00:00:00 2001 From: Max Edell Date: Tue, 10 Dec 2024 10:00:11 +0100 Subject: [PATCH 1/4] feat: config auth --- src/types.d.ts | 2 ++ src/utils/auth.js | 35 +++++++++++++++++++++++++++++++++++ wrangler.toml | 9 ++++++--- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/utils/auth.js diff --git a/src/types.d.ts b/src/types.d.ts index 364c7e8..677f782 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -58,6 +58,7 @@ declare global { // KV namespaces CONFIGS: KVNamespace; + KEYS: KVNamespace; [key: string]: string | KVNamespace | R2Bucket; } @@ -74,6 +75,7 @@ declare global { attributes: { htmlTemplate?: HTMLTemplate; jsonTemplate?: JSONTemplate; + key?: string; [key: string]: any; } } diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..25ceb95 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { errorWithResponse } from './http.js'; + +/** + * @param {Context} ctx + */ +export async function assertAuthorization(ctx) { + let actual; + if (typeof ctx.attributes.key === 'undefined') { + ctx.attributes.key = ctx.info.headers.authorization?.slice('Bearer '.length); + actual = ctx.attributes.key; + } + if (!actual) { + throw errorWithResponse(403, 'invalid key'); + } + + const expected = await ctx.env.KEYS.get(ctx.config.siteKey); + if (!expected) { + throw errorWithResponse(403, 'no key found for site'); + } + if (actual !== expected) { + throw errorWithResponse(403, 'access denied'); + } +} diff --git a/wrangler.toml b/wrangler.toml index 32c49c2..0ea1b0d 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -8,7 +8,8 @@ send_metrics = false build = { command = "npm install && node build.js" } kv_namespaces = [ - { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" } + { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" }, + { binding = "KEYS", id = "0ca5d0ba0150453bb8aafcdf4304dc7a", preview_id = "0ca5d0ba0150453bb8aafcdf4304dc7a" } ] [[r2_buckets]] @@ -27,7 +28,8 @@ ENVIRONMENT = "dev" name = "adobe-commerce-api-ci" kv_namespaces = [ - { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" } + { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" }, + { binding = "KEYS", id = "0ca5d0ba0150453bb8aafcdf4304dc7a", preview_id = "0ca5d0ba0150453bb8aafcdf4304dc7a" } ] [[env.ci.r2_buckets]] @@ -46,7 +48,8 @@ ENVIRONMENT = "ci" name = "adobe-commerce-api" kv_namespaces = [ - { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" } + { binding = "CONFIGS", id = "bb91e12a65a8462282396f32e63406f1", preview_id = "bb91e12a65a8462282396f32e63406f1" }, + { binding = "KEYS", id = "0ca5d0ba0150453bb8aafcdf4304dc7a", preview_id = "0ca5d0ba0150453bb8aafcdf4304dc7a" } ] [[env.production.r2_buckets]] From 389c2cfe4cc4a0d946aba445981ca1496d2483f3 Mon Sep 17 00:00:00 2001 From: Max Edell Date: Tue, 10 Dec 2024 10:39:09 +0100 Subject: [PATCH 2/4] add config route --- src/config/handler.js | 38 ++++++++++++++++++++++++++++++++++++++ src/index.js | 6 ++++-- src/types.d.ts | 1 + src/{ => utils}/config.js | 21 ++++++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/config/handler.js rename src/{ => utils}/config.js (88%) diff --git a/src/config/handler.js b/src/config/handler.js new file mode 100644 index 0000000..3262a0c --- /dev/null +++ b/src/config/handler.js @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { errorResponse } from '../utils/http.js'; +import { assertAuthorization } from '../utils/auth.js'; + +/** + * @param {Context} ctx + * @returns {Promise} + */ +export default async function configHandler(ctx) { + const { method } = ctx.info; + if (!['GET', 'POST'].includes(method)) { + return errorResponse(405, 'method not allowed'); + } + + await assertAuthorization(ctx); + + if (method === 'GET') { + return new Response(JSON.stringify(ctx.config.confMapStr), { + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + // TODO: validate config body, set config in kv + return errorResponse(501, 'not implemented'); +} diff --git a/src/index.js b/src/index.js index 269da7a..019ef60 100644 --- a/src/index.js +++ b/src/index.js @@ -11,9 +11,10 @@ */ import { errorResponse } from './utils/http.js'; -import { resolveConfig } from './config.js'; +import { resolveConfig } from './utils/config.js'; import content from './content/handler.js'; import catalog from './catalog/handler.js'; +import configHandler from './config/handler.js'; /** * @type {Record Promise>} @@ -21,6 +22,7 @@ import catalog from './catalog/handler.js'; const handlers = { content, catalog, + config: configHandler, // eslint-disable-next-line no-unused-vars graphql: async (ctx) => errorResponse(501, 'not implemented'), }; @@ -41,7 +43,7 @@ export function makeContext(pctx, req, env) { ctx.url = new URL(req.url); ctx.log = console; ctx.info = { - method: req.method, + method: req.method.toUpperCase(), headers: Object.fromEntries( [...req.headers.entries()] .map(([k, v]) => [k.toLowerCase(), v]), diff --git a/src/types.d.ts b/src/types.d.ts index 677f782..afa2b07 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -50,6 +50,7 @@ declare global { imageParams?: Record; confMap: ConfigMap; + confMapStr: string; } export interface Env { diff --git a/src/config.js b/src/utils/config.js similarity index 88% rename from src/config.js rename to src/utils/config.js index 527b920..160a499 100644 --- a/src/config.js +++ b/src/utils/config.js @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import { errorWithResponse } from './utils/http.js'; +import { errorWithResponse } from './http.js'; /** * This function finds ordered matches between a list of patterns and a given path. @@ -65,6 +65,7 @@ export async function resolveConfig(ctx, overrides = {}) { * @type {ConfigMap} */ const confMap = await ctx.env.CONFIGS.get(siteKey, 'json'); + const confMapStr = JSON.stringify(confMap); if (!confMap) { return null; } @@ -73,6 +74,23 @@ export async function resolveConfig(ctx, overrides = {}) { return null; } + // if route is `config` don't resolve further + if (route === 'config') { + return { + ...confMap.base, + headers: confMap.base?.headers ?? {}, + params: {}, + confMap, + confMapStr, + org, + site, + route, + siteKey, + matchedPatterns: [], + ...overrides, + }; + } + // order paths by preference const suffix = `/${ctx.url.pathname.split('/').slice(3).join('/')}`; const paths = findOrderedMatches( @@ -101,6 +119,7 @@ export async function resolveConfig(ctx, overrides = {}) { params: {}, }), confMap, + confMapStr, org, site, route, From 7006d4e722d771988ab1e37ec6963e8fcd1cde1c Mon Sep 17 00:00:00 2001 From: Max Edell Date: Tue, 10 Dec 2024 10:49:44 +0100 Subject: [PATCH 3/4] fix tests --- test/{ => utils}/config.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename test/{ => utils}/config.test.js (89%) diff --git a/test/config.test.js b/test/utils/config.test.js similarity index 89% rename from test/config.test.js rename to test/utils/config.test.js index d8b3193..4892365 100644 --- a/test/config.test.js +++ b/test/utils/config.test.js @@ -13,9 +13,9 @@ // @ts-nocheck import assert from 'node:assert'; -import { resolveConfig } from '../src/config.js'; -import { TEST_CONTEXT } from './fixtures/context.js'; -import { defaultTenantConfigs } from './fixtures/kv.js'; +import { resolveConfig } from '../../src/utils/config.js'; +import { TEST_CONTEXT } from '../fixtures/context.js'; +import { defaultTenantConfigs } from '../fixtures/kv.js'; describe('config tests', () => { it('should extract path params', async () => { @@ -52,6 +52,7 @@ describe('config tests', () => { apiKey: 'bad', }, }, + confMapStr: '{"base":{"apiKey":"bad"},"/us/p/{{urlkey}}/{{sku}}":{"pageType":"product","apiKey":"good"}}', }); }); @@ -105,6 +106,7 @@ describe('config tests', () => { }, }, }, + confMapStr: '{"base":{"apiKey":"bad","headers":{"foo":"1","baz":"1"}},"/us/p/{{urlkey}}/{{sku}}":{"pageType":"product","apiKey":"good","headers":{"foo":"2","bar":"2"}}}', }); }); @@ -142,6 +144,7 @@ describe('config tests', () => { apiKey: 'bad', }, }, + confMapStr: '{"base":{"apiKey":"bad"},"/us/p/*/{{sku}}":{"pageType":"product","apiKey":"good"}}', }); }); @@ -182,6 +185,7 @@ describe('config tests', () => { apiKey: 'bad1', }, }, + confMapStr: '{"base":{"apiKey":"bad1"},"/us/p/{{sku}}":{"pageType":"product","apiKey":"bad2"}}', }); }); From 1188880fb9f40d4e82e03d37260cb9d30c06132d Mon Sep 17 00:00:00 2001 From: Max Edell Date: Tue, 10 Dec 2024 11:07:04 +0100 Subject: [PATCH 4/4] fix: dont double stringify --- src/config/handler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/handler.js b/src/config/handler.js index 3262a0c..07fcd55 100644 --- a/src/config/handler.js +++ b/src/config/handler.js @@ -26,7 +26,7 @@ export default async function configHandler(ctx) { await assertAuthorization(ctx); if (method === 'GET') { - return new Response(JSON.stringify(ctx.config.confMapStr), { + return new Response(ctx.config.confMapStr, { headers: { 'Content-Type': 'application/json', },