@pikku/openapi-parser 0.12.13 → 0.12.14

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,5 +1,11 @@
1
1
  # @pikku/openapi-parser
2
2
 
3
+ ## 0.12.14
4
+
5
+ ### Patch Changes
6
+
7
+ - a10e88d: auth-config: new `extraHeaders` field — static headers baked into every generated request (the delegated login call and all proxied service calls), for upstreams that route on a header such as multi-tenant APIs resolving the tenant from `Origin`.
8
+
3
9
  ## 0.12.13
4
10
 
5
11
  ### Patch Changes
@@ -39,6 +39,7 @@ export declare const AuthConfigSchema: z.ZodObject<{
39
39
  raw: "raw";
40
40
  bearer: "bearer";
41
41
  }>>;
42
+ extraHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
42
43
  delegated: z.ZodOptional<z.ZodObject<{
43
44
  loginPath: z.ZodString;
44
45
  loginMethod: z.ZodDefault<z.ZodString>;
@@ -38,6 +38,13 @@ export const AuthConfigSchema = z.object({
38
38
  headerName: z.string().optional(),
39
39
  /** 'raw' sends the bare token; 'bearer' prefixes it. Default: bearer for Authorization, raw for custom headers. */
40
40
  headerFormat: z.enum(['raw', 'bearer']).optional(),
41
+ /**
42
+ * Static headers sent on EVERY request — the delegated login call and all
43
+ * proxied API calls. Needed for upstreams that route on a header, e.g. a
44
+ * multi-tenant API that resolves the tenant from an `Origin` header and
45
+ * rejects requests without it.
46
+ */
47
+ extraHeaders: z.record(z.string(), z.string()).optional(),
41
48
  /** Present when end-users sign in with their existing upstream credentials (delegated login). */
42
49
  delegated: DelegatedLoginSchema.optional(),
43
50
  });
package/dist/codegen.js CHANGED
@@ -839,7 +839,12 @@ function generateUpstreamAuthFile(vars, authConfig) {
839
839
  lines.push(` credentials: ${pascalName}UpstreamCredentials,`);
840
840
  lines.push(' baseUrl: string');
841
841
  lines.push(`): Promise<${pascalName}UpstreamIdentity | null> => {`);
842
- lines.push(" const headers: Record<string, string> = { 'Content-Type': 'application/json' }");
842
+ lines.push(' const headers: Record<string, string> = {');
843
+ lines.push(" 'Content-Type': 'application/json',");
844
+ for (const [header, value] of Object.entries(authConfig.extraHeaders ?? {})) {
845
+ lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`);
846
+ }
847
+ lines.push(' }');
843
848
  lines.push(' const body: Record<string, string> = {}');
844
849
  if (delegated.credentials.includes('email')) {
845
850
  lines.push(' if (credentials.email) body.email = credentials.email');
@@ -1041,6 +1046,9 @@ function generateServiceFile(spec, opPairs, vars, flags) {
1041
1046
  lines.push(' const query: Record<string, string> = {}');
1042
1047
  lines.push(' const headers: Record<string, string> = {');
1043
1048
  lines.push(" 'Content-Type': 'application/json',");
1049
+ for (const [header, value] of Object.entries(flags.authConfig?.extraHeaders ?? {})) {
1050
+ lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`);
1051
+ }
1044
1052
  lines.push(' }');
1045
1053
  lines.push('');
1046
1054
  // When camelCase flag is on, use rawData (snake_case converted) for route matching
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/openapi-parser",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "OpenAPI spec parser and code generator for Pikku",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -41,6 +41,13 @@ export const AuthConfigSchema = z.object({
41
41
  headerName: z.string().optional(),
42
42
  /** 'raw' sends the bare token; 'bearer' prefixes it. Default: bearer for Authorization, raw for custom headers. */
43
43
  headerFormat: z.enum(['raw', 'bearer']).optional(),
44
+ /**
45
+ * Static headers sent on EVERY request — the delegated login call and all
46
+ * proxied API calls. Needed for upstreams that route on a header, e.g. a
47
+ * multi-tenant API that resolves the tenant from an `Origin` header and
48
+ * rejects requests without it.
49
+ */
50
+ extraHeaders: z.record(z.string(), z.string()).optional(),
44
51
  /** Present when end-users sign in with their existing upstream credentials (delegated login). */
45
52
  delegated: DelegatedLoginSchema.optional(),
46
53
  })
@@ -932,6 +932,29 @@ describe('auth config', () => {
932
932
  )
933
933
  })
934
934
 
935
+ test('extraHeaders are baked into the service and the upstream-auth login', () => {
936
+ const spec = makeSpec({ operations: [makeOp()] })
937
+ const files = generateAddonFromOpenAPI(spec, makeVars(), {
938
+ oauth: false,
939
+ secret: false,
940
+ credential: 'bearer',
941
+ authConfig: {
942
+ ...delegatedConfig,
943
+ extraHeaders: { Origin: 'https://tenant.example.com' },
944
+ },
945
+ })
946
+ const service = files['src/test-api-api.service.ts']
947
+ assert.ok(
948
+ service.includes('"Origin": "https://tenant.example.com",'),
949
+ 'service headers init should carry the static header'
950
+ )
951
+ const authFile = files['src/test-api-upstream-auth.ts']
952
+ assert.ok(
953
+ authFile.includes('"Origin": "https://tenant.example.com",'),
954
+ 'login fetch should carry the static header'
955
+ )
956
+ })
957
+
935
958
  test('no delegated config → no upstream-auth file', () => {
936
959
  const spec = makeSpec({ operations: [makeOp()] })
937
960
  const files = generateAddonFromOpenAPI(spec, makeVars(), {
package/src/codegen.ts CHANGED
@@ -1132,9 +1132,12 @@ function generateUpstreamAuthFile(
1132
1132
  lines.push(` credentials: ${pascalName}UpstreamCredentials,`)
1133
1133
  lines.push(' baseUrl: string')
1134
1134
  lines.push(`): Promise<${pascalName}UpstreamIdentity | null> => {`)
1135
- lines.push(
1136
- " const headers: Record<string, string> = { 'Content-Type': 'application/json' }"
1137
- )
1135
+ lines.push(' const headers: Record<string, string> = {')
1136
+ lines.push(" 'Content-Type': 'application/json',")
1137
+ for (const [header, value] of Object.entries(authConfig.extraHeaders ?? {})) {
1138
+ lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`)
1139
+ }
1140
+ lines.push(' }')
1138
1141
  lines.push(' const body: Record<string, string> = {}')
1139
1142
  if (delegated.credentials.includes('email')) {
1140
1143
  lines.push(' if (credentials.email) body.email = credentials.email')
@@ -1405,6 +1408,11 @@ function generateServiceFile(
1405
1408
  lines.push(' const query: Record<string, string> = {}')
1406
1409
  lines.push(' const headers: Record<string, string> = {')
1407
1410
  lines.push(" 'Content-Type': 'application/json',")
1411
+ for (const [header, value] of Object.entries(
1412
+ flags.authConfig?.extraHeaders ?? {}
1413
+ )) {
1414
+ lines.push(` ${JSON.stringify(header)}: ${JSON.stringify(value)},`)
1415
+ }
1408
1416
  lines.push(' }')
1409
1417
  lines.push('')
1410
1418
  // When camelCase flag is on, use rawData (snake_case converted) for route matching