Skip to content

Commit 4cda4b8

Browse files
committed
feat: add support for API key security schemes in cookies
1 parent 2797d6c commit 4cda4b8

File tree

24 files changed

+199
-22
lines changed

24 files changed

+199
-22
lines changed

packages/client-axios/src/__tests__/utils.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@ describe('setAuthParams', () => {
9090
expect(query).toEqual({});
9191
});
9292

93+
it('sets an API key in a cookie', async () => {
94+
const auth = vi.fn().mockReturnValue('foo');
95+
const headers: Record<any, unknown> = {};
96+
const query: Record<any, unknown> = {};
97+
await setAuthParams({
98+
auth,
99+
headers,
100+
query,
101+
security: [
102+
{
103+
in: 'cookie',
104+
name: 'baz',
105+
type: 'apiKey',
106+
},
107+
],
108+
});
109+
expect(auth).toHaveBeenCalled();
110+
expect(headers.Cookie).toBe('baz=foo');
111+
expect(query).toEqual({});
112+
});
113+
93114
it('sets first scheme only', async () => {
94115
const auth = vi.fn().mockReturnValue('foo');
95116
const headers: Record<any, unknown> = {};

packages/client-axios/src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ export const setAuthParams = async ({
171171
}
172172
options.query[name] = token;
173173
break;
174+
case 'cookie': {
175+
const value = `${name}=${token}`;
176+
if ('Cookie' in options.headers && options.headers['Cookie']) {
177+
options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`;
178+
} else {
179+
options.headers['Cookie'] = value;
180+
}
181+
break;
182+
}
174183
case 'header':
175184
default:
176185
options.headers[name] = token;

packages/client-core/src/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface Auth {
66
*
77
* @default 'header'
88
*/
9-
in?: 'header' | 'query';
9+
in?: 'header' | 'query' | 'cookie';
1010
/**
1111
* Header or query parameter name.
1212
*

packages/client-fetch/src/__tests__/utils.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,25 @@ describe('setAuthParams', () => {
186186
expect(headers.get('baz')).toBeNull();
187187
expect(query.baz).toBe('Bearer foo');
188188
});
189+
190+
it('sets an API key in a cookie', async () => {
191+
const auth = vi.fn().mockReturnValue('foo');
192+
const headers = new Headers();
193+
const query: Record<any, unknown> = {};
194+
await setAuthParams({
195+
auth,
196+
headers,
197+
query,
198+
security: [
199+
{
200+
in: 'cookie',
201+
name: 'baz',
202+
type: 'apiKey',
203+
},
204+
],
205+
});
206+
expect(auth).toHaveBeenCalled();
207+
expect(headers.get('Cookie')).toBe('baz=foo');
208+
expect(query).toEqual({});
209+
});
189210
});

packages/client-fetch/src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ export const setAuthParams = async ({
217217
}
218218
options.query[name] = token;
219219
break;
220+
case 'cookie':
221+
options.headers.append('Cookie', `${name}=${token}`);
222+
break;
220223
case 'header':
221224
default:
222225
options.headers.set(name, token);

packages/client-next/src/__tests__/utils.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,25 @@ describe('setAuthParams', () => {
186186
expect(headers.get('baz')).toBeNull();
187187
expect(query.baz).toBe('Bearer foo');
188188
});
189+
190+
it('sets an API key in a cookie', async () => {
191+
const auth = vi.fn().mockReturnValue('foo');
192+
const headers = new Headers();
193+
const query: Record<any, unknown> = {};
194+
await setAuthParams({
195+
auth,
196+
headers,
197+
query,
198+
security: [
199+
{
200+
in: 'cookie',
201+
name: 'baz',
202+
type: 'apiKey',
203+
},
204+
],
205+
});
206+
expect(auth).toHaveBeenCalled();
207+
expect(headers.get('Cookie')).toBe('baz=foo');
208+
expect(query).toEqual({});
209+
});
189210
});

packages/client-next/src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ export const setAuthParams = async ({
217217
}
218218
options.query[name] = token;
219219
break;
220+
case 'cookie':
221+
options.headers.append('Cookie', `${name}=${token}`);
222+
break;
220223
case 'header':
221224
default:
222225
options.headers.set(name, token);

packages/client-nuxt/src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ export const setAuthParams = async ({
178178
}
179179
toValue(options.query)[name] = token;
180180
break;
181+
case 'cookie':
182+
options.headers.append('Cookie', `${name}=${token}`);
183+
break;
181184
case 'header':
182185
default:
183186
options.headers.set(name, token);

packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export interface Auth {
3737
*
3838
* @default 'header'
3939
*/
40-
in?: 'header' | 'query';
40+
in?: 'header' | 'query' | 'cookie';
4141
/**
4242
* Header or query parameter name.
4343
*
@@ -175,8 +175,10 @@ const securitySchemeObjectToAuthObject = ({
175175
};
176176
}
177177

178-
// TODO: parser - support cookies auth
179-
if (securitySchemeObject.in === 'query') {
178+
if (
179+
securitySchemeObject.in === 'query' ||
180+
securitySchemeObject.in == 'cookie'
181+
) {
180182
return {
181183
in: securitySchemeObject.in,
182184
name: securitySchemeObject.name,

packages/openapi-ts/test/__snapshots__/3.0.x/security-api-key/sdk.gen.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This file is auto-generated by @hey-api/openapi-ts
22

33
import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';
4-
import type { GetFooData } from './types.gen';
4+
import type { GetFooData, GetBarData } from './types.gen';
55
import { client as _heyApiClient } from './client.gen';
66

77
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
@@ -30,4 +30,18 @@ export const getFoo = <ThrowOnError extends boolean = false>(options?: Options<G
3030
url: '/foo',
3131
...options
3232
});
33-
};
33+
};
34+
35+
export const getBar = <ThrowOnError extends boolean = false>(options?: Options<GetBarData, ThrowOnError>) => {
36+
return (options?.client ?? _heyApiClient).get<unknown, unknown, ThrowOnError>({
37+
security: [
38+
{
39+
in: 'cookie',
40+
name: 'bar',
41+
type: 'apiKey'
42+
}
43+
],
44+
url: '/bar',
45+
...options
46+
});
47+
};

packages/openapi-ts/test/__snapshots__/3.0.x/security-api-key/types.gen.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ export type GetFooResponses = {
1414
200: unknown;
1515
};
1616

17+
export type GetBarData = {
18+
body?: never;
19+
path?: never;
20+
query?: never;
21+
url: '/bar';
22+
};
23+
24+
export type GetBarResponses = {
25+
/**
26+
* OK
27+
*/
28+
200: unknown;
29+
};
30+
1731
export type ClientOptions = {
1832
baseUrl: `${string}://${string}` | (string & {});
19-
};
33+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
'use strict';var B=require('axios');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var B__default=/*#__PURE__*/_interopDefault(B);var w=async(t,r)=>{let e=typeof r=="function"?await r(t):r;if(e)return t.scheme==="bearer"?`Bearer ${e}`:t.scheme==="basic"?`Basic ${btoa(e)}`:e},z=(t,r,e)=>{typeof e=="string"||e instanceof Blob?t.append(r,e):t.append(r,JSON.stringify(e));},O=(t,r,e)=>{typeof e=="string"?t.append(r,e):t.append(r,JSON.stringify(e));},j={bodySerializer:t=>{let r=new FormData;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(r,e,a)):z(r,e,i));}),r}},q={bodySerializer:t=>JSON.stringify(t,(r,e)=>typeof e=="bigint"?e.toString():e)},P={bodySerializer:t=>{let r=new URLSearchParams;return Object.entries(t).forEach(([e,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>O(r,e,a)):O(r,e,i));}),r.toString()}},v=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},$=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},k=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},h=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(!r){let n=(t?a:a.map(o=>encodeURIComponent(o))).join($(i));switch(i){case "label":return `.${n}`;case "matrix":return `;${e}=${n}`;case "simple":return n;default:return `${e}=${n}`}}let s=v(i),l=a.map(n=>i==="label"||i==="simple"?t?n:encodeURIComponent(n):f({allowReserved:t,name:e,value:n})).join(s);return i==="label"||i==="matrix"?s+l:l},f=({allowReserved:t,name:r,value:e})=>{if(e==null)return "";if(typeof e=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${r}=${t?e:encodeURIComponent(e)}`},g=({allowReserved:t,explode:r,name:e,style:i,value:a})=>{if(a instanceof Date)return `${e}=${a.toISOString()}`;if(i!=="deepObject"&&!r){let n=[];Object.entries(a).forEach(([u,d])=>{n=[...n,u,t?d:encodeURIComponent(d)];});let o=n.join(",");switch(i){case "form":return `${e}=${o}`;case "label":return `.${o}`;case "matrix":return `;${e}=${o}`;default:return o}}let s=k(i),l=Object.entries(a).map(([n,o])=>f({allowReserved:t,name:i==="deepObject"?`${e}[${n}]`:n,value:o})).join(s);return i==="label"||i==="matrix"?s+l:l};var T=/\{[^{}]+\}/g,E=({path:t,url:r})=>{let e=r,i=r.match(T);if(i)for(let a of i){let s=false,l=a.substring(1,a.length-1),n="simple";l.endsWith("*")&&(s=true,l=l.substring(0,l.length-1)),l.startsWith(".")?(l=l.substring(1),n="label"):l.startsWith(";")&&(l=l.substring(1),n="matrix");let o=t[l];if(o==null)continue;if(Array.isArray(o)){e=e.replace(a,h({explode:s,name:l,style:n,value:o}));continue}if(typeof o=="object"){e=e.replace(a,g({explode:s,name:l,style:n,value:o}));continue}if(n==="matrix"){e=e.replace(a,`;${f({name:l,value:o})}`);continue}let u=encodeURIComponent(n==="label"?`.${o}`:o);e=e.replace(a,u);}return e},U=({allowReserved:t,array:r,object:e}={})=>a=>{let s=[];if(a&&typeof a=="object")for(let l in a){let n=a[l];if(n!=null){if(Array.isArray(n)){s=[...s,h({allowReserved:t,explode:true,name:l,style:"form",value:n,...r})];continue}if(typeof n=="object"){s=[...s,g({allowReserved:t,explode:true,name:l,style:"deepObject",value:n,...e})];continue}s=[...s,f({allowReserved:t,name:l,value:n})];}}return s.join("&")},A=async({security:t,...r})=>{for(let e of t){let i=await w(e,r.auth);if(!i)continue;let a=e.name??"Authorization";switch(e.in){case "query":r.query||(r.query={}),r.query[a]=i;break;case "header":default:r.headers[a]=i;break}return}},b=t=>D({path:t.path,query:t.paramsSerializer?undefined:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:U(t.querySerializer),url:t.url}),D=({path:t,query:r,querySerializer:e,url:i})=>{let s=i.startsWith("/")?i:`/${i}`;t&&(s=E({path:t,url:s}));let l=r?e(r):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(s+=`?${l}`),s},S=(t,r)=>{let e={...t,...r};return e.headers=y(t.headers,r.headers),e},H=["common","delete","get","head","patch","post","put"],y=(...t)=>{let r={};for(let e of t){if(!e||typeof e!="object")continue;let i=Object.entries(e);for(let[a,s]of i)if(H.includes(a)&&typeof s=="object")r[a]={...r[a],...s};else if(s===null)delete r[a];else if(Array.isArray(s))for(let l of s)r[a]=[...r[a]??[],l];else s!==undefined&&(r[a]=typeof s=="object"?JSON.stringify(s):s);}return r},x=(t={})=>({...t});var I=(t={})=>{let r=S(x(),t),{auth:e,...i}=r,a=B__default.default.create(i),s=()=>({...r}),l=o=>(r=S(r,o),a.defaults={...a.defaults,...r,headers:y(a.defaults.headers,r.headers)},s()),n=async o=>{let u={...r,...o,axios:o.axios??r.axios??a,headers:y(r.headers,o.headers)};u.security&&await A({...u,security:u.security}),u.body&&u.bodySerializer&&(u.body=u.bodySerializer(u.body));let d=b(u);try{let m=u.axios,{auth:c,...R}=u,C=await m({...R,baseURL:u.baseURL,data:u.body,headers:u.headers,params:u.paramsSerializer?u.query:void 0,url:d}),{data:p}=C;return u.responseType==="json"&&(u.responseValidator&&await u.responseValidator(p),u.responseTransformer&&(p=await u.responseTransformer(p))),{...C,data:p??{}}}catch(m){let c=m;if(u.throwOnError)throw c;return c.error=c.response?.data??{},c}};return {buildUrl:b,delete:o=>n({...o,method:"DELETE"}),get:o=>n({...o,method:"GET"}),getConfig:s,head:o=>n({...o,method:"HEAD"}),instance:a,options:o=>n({...o,method:"OPTIONS"}),patch:o=>n({...o,method:"PATCH"}),post:o=>n({...o,method:"POST"}),put:o=>n({...o,method:"PUT"}),request:n,setConfig:l}};exports.createClient=I;exports.createConfig=x;exports.formDataBodySerializer=j;exports.jsonBodySerializer=q;exports.urlSearchParamsBodySerializer=P;//# sourceMappingURL=index.cjs.map
2-
//# sourceMappingURL=index.cjs.map
1+
'use strict';var B=require('axios');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var B__default=/*#__PURE__*/_interopDefault(B);var w=async(t,e)=>{let r=typeof e=="function"?await e(t):e;if(r)return t.scheme==="bearer"?`Bearer ${r}`:t.scheme==="basic"?`Basic ${btoa(r)}`:r},z=(t,e,r)=>{typeof r=="string"||r instanceof Blob?t.append(e,r):t.append(e,JSON.stringify(r));},O=(t,e,r)=>{typeof r=="string"?t.append(e,r):t.append(e,JSON.stringify(r));},j={bodySerializer:t=>{let e=new FormData;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>z(e,r,a)):z(e,r,i));}),e}},k={bodySerializer:t=>JSON.stringify(t,(e,r)=>typeof r=="bigint"?r.toString():r)},$={bodySerializer:t=>{let e=new URLSearchParams;return Object.entries(t).forEach(([r,i])=>{i!=null&&(Array.isArray(i)?i.forEach(a=>O(e,r,a)):O(e,r,i));}),e.toString()}},q=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},v=t=>{switch(t){case "form":return ",";case "pipeDelimited":return "|";case "spaceDelimited":return "%20";default:return ","}},P=t=>{switch(t){case "label":return ".";case "matrix":return ";";case "simple":return ",";default:return "&"}},h=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(!e){let n=(t?a:a.map(s=>encodeURIComponent(s))).join(v(i));switch(i){case "label":return `.${n}`;case "matrix":return `;${r}=${n}`;case "simple":return n;default:return `${r}=${n}`}}let o=q(i),l=a.map(n=>i==="label"||i==="simple"?t?n:encodeURIComponent(n):f({allowReserved:t,name:r,value:n})).join(o);return i==="label"||i==="matrix"?o+l:l},f=({allowReserved:t,name:e,value:r})=>{if(r==null)return "";if(typeof r=="object")throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");return `${e}=${t?r:encodeURIComponent(r)}`},g=({allowReserved:t,explode:e,name:r,style:i,value:a})=>{if(a instanceof Date)return `${r}=${a.toISOString()}`;if(i!=="deepObject"&&!e){let n=[];Object.entries(a).forEach(([u,d])=>{n=[...n,u,t?d:encodeURIComponent(d)];});let s=n.join(",");switch(i){case "form":return `${r}=${s}`;case "label":return `.${s}`;case "matrix":return `;${r}=${s}`;default:return s}}let o=P(i),l=Object.entries(a).map(([n,s])=>f({allowReserved:t,name:i==="deepObject"?`${r}[${n}]`:n,value:s})).join(o);return i==="label"||i==="matrix"?o+l:l};var T=/\{[^{}]+\}/g,E=({path:t,url:e})=>{let r=e,i=e.match(T);if(i)for(let a of i){let o=false,l=a.substring(1,a.length-1),n="simple";l.endsWith("*")&&(o=true,l=l.substring(0,l.length-1)),l.startsWith(".")?(l=l.substring(1),n="label"):l.startsWith(";")&&(l=l.substring(1),n="matrix");let s=t[l];if(s==null)continue;if(Array.isArray(s)){r=r.replace(a,h({explode:o,name:l,style:n,value:s}));continue}if(typeof s=="object"){r=r.replace(a,g({explode:o,name:l,style:n,value:s}));continue}if(n==="matrix"){r=r.replace(a,`;${f({name:l,value:s})}`);continue}let u=encodeURIComponent(n==="label"?`.${s}`:s);r=r.replace(a,u);}return r},U=({allowReserved:t,array:e,object:r}={})=>a=>{let o=[];if(a&&typeof a=="object")for(let l in a){let n=a[l];if(n!=null){if(Array.isArray(n)){o=[...o,h({allowReserved:t,explode:true,name:l,style:"form",value:n,...e})];continue}if(typeof n=="object"){o=[...o,g({allowReserved:t,explode:true,name:l,style:"deepObject",value:n,...r})];continue}o=[...o,f({allowReserved:t,name:l,value:n})];}}return o.join("&")},A=async({security:t,...e})=>{for(let r of t){let i=await w(r,e.auth);if(!i)continue;let a=r.name??"Authorization";switch(r.in){case "query":e.query||(e.query={}),e.query[a]=i;break;case "cookie":{let o=`${a}=${i}`;"Cookie"in e.headers&&e.headers.Cookie?e.headers.Cookie=`${e.headers.Cookie}; ${o}`:e.headers.Cookie=o;break}case "header":default:e.headers[a]=i;break}return}},b=t=>D({path:t.path,query:t.paramsSerializer?undefined:t.query,querySerializer:typeof t.querySerializer=="function"?t.querySerializer:U(t.querySerializer),url:t.url}),D=({path:t,query:e,querySerializer:r,url:i})=>{let o=i.startsWith("/")?i:`/${i}`;t&&(o=E({path:t,url:o}));let l=e?r(e):"";return l.startsWith("?")&&(l=l.substring(1)),l&&(o+=`?${l}`),o},C=(t,e)=>{let r={...t,...e};return r.headers=y(t.headers,e.headers),r},H=["common","delete","get","head","patch","post","put"],y=(...t)=>{let e={};for(let r of t){if(!r||typeof r!="object")continue;let i=Object.entries(r);for(let[a,o]of i)if(H.includes(a)&&typeof o=="object")e[a]={...e[a],...o};else if(o===null)delete e[a];else if(Array.isArray(o))for(let l of o)e[a]=[...e[a]??[],l];else o!==undefined&&(e[a]=typeof o=="object"?JSON.stringify(o):o);}return e},S=(t={})=>({...t});var I=(t={})=>{let e=C(S(),t),{auth:r,...i}=e,a=B__default.default.create(i),o=()=>({...e}),l=s=>(e=C(e,s),a.defaults={...a.defaults,...e,headers:y(a.defaults.headers,e.headers)},o()),n=async s=>{let u={...e,...s,axios:s.axios??e.axios??a,headers:y(e.headers,s.headers)};u.security&&await A({...u,security:u.security}),u.body&&u.bodySerializer&&(u.body=u.bodySerializer(u.body));let d=b(u);try{let m=u.axios,{auth:c,...R}=u,x=await m({...R,baseURL:u.baseURL,data:u.body,headers:u.headers,params:u.paramsSerializer?u.query:void 0,url:d}),{data:p}=x;return u.responseType==="json"&&(u.responseValidator&&await u.responseValidator(p),u.responseTransformer&&(p=await u.responseTransformer(p))),{...x,data:p??{}}}catch(m){let c=m;if(u.throwOnError)throw c;return c.error=c.response?.data??{},c}};return {buildUrl:b,delete:s=>n({...s,method:"DELETE"}),get:s=>n({...s,method:"GET"}),getConfig:o,head:s=>n({...s,method:"HEAD"}),instance:a,options:s=>n({...s,method:"OPTIONS"}),patch:s=>n({...s,method:"PATCH"}),post:s=>n({...s,method:"POST"}),put:s=>n({...s,method:"PUT"}),request:n,setConfig:l}};exports.createClient=I;exports.createConfig=S;exports.formDataBodySerializer=j;exports.jsonBodySerializer=k;exports.urlSearchParamsBodySerializer=$;//# sourceMappingURL=index.cjs.map
2+
//# sourceMappingURL=index.cjs.map

packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/index.d.cts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface Auth {
77
*
88
* @default 'header'
99
*/
10-
in?: 'header' | 'query';
10+
in?: 'header' | 'query' | 'cookie';
1111
/**
1212
* Header or query parameter name.
1313
*

packages/openapi-ts/test/__snapshots__/3.1.x/clients/@hey-api/client-axios/bundle/client/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface Auth {
77
*
88
* @default 'header'
99
*/
10-
in?: 'header' | 'query';
10+
in?: 'header' | 'query' | 'cookie';
1111
/**
1212
* Header or query parameter name.
1313
*

0 commit comments

Comments
 (0)