Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,10 +601,12 @@ export const composeHandler = ({
const cookieMeta: {
secrets?: string | string[]
sign: string[] | true
encode?: CookieOptions['encode']
properties: { [x: string]: Object }
} = validator.cookie?.config
? mergeCookie(validator?.cookie?.config, app.config.cookie as any)
: app.config.cookie
const hasCookieEncode = typeof cookieMeta?.encode === 'function'

let _encodeCookie = ''
const encodeCookie = () => {
Expand Down Expand Up @@ -736,6 +738,7 @@ export const composeHandler = ({
get('priority') +
get('sameSite') +
get('secure') +
(hasCookieEncode ? 'encode:cookieEncode,' : '') +
'}'
: 'undefined'

Expand Down Expand Up @@ -2151,6 +2154,7 @@ export const composeHandler = ({
allocateIf(`parseCookie,`, hasCookie) +
allocateIf(`signCookie,`, hasCookie) +
allocateIf(`decodeURIComponent,`, hasQuery) +
allocateIf(`cookieEncode,`, hasCookieEncode) +
`ElysiaCustomStatusResponse,` +
allocateIf(`ELYSIA_TRACE,`, hasTrace) +
allocateIf(`ELYSIA_REQUEST_ID,`, hasTrace) +
Expand Down Expand Up @@ -2205,6 +2209,7 @@ export const composeHandler = ({
ERROR_CODE,
parseCookie: hasCookie ? parseCookie : undefined,
signCookie: hasCookie ? signCookie : undefined,
cookieEncode: hasCookieEncode ? cookieMeta.encode : undefined,
Cookie: hasCookie ? Cookie : undefined,
decodeURIComponent: hasQuery ? decode : undefined,
ElysiaCustomStatusResponse,
Expand Down
16 changes: 16 additions & 0 deletions src/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ export interface CookieOptions {
*/
secure?: boolean | undefined

/**
* Specifies a function that will be used to encode a cookie value before
* serializing it into the `Set-Cookie` header.
*
* @default encodeURIComponent
*/
encode?: ((value: string) => string) | undefined

/**
* Secret key for signing cookie
*
Expand Down Expand Up @@ -297,6 +305,14 @@ export class Cookie<T> implements ElysiaCookie {
this.setCookie.partitioned = partitioned
}

get encode() {
return this.cookie.encode
}

set encode(encode: ElysiaCookie['encode']) {
this.setCookie.encode = encode
}

get secrets() {
return this.cookie.secrets
}
Expand Down
63 changes: 27 additions & 36 deletions src/dynamic-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,47 +447,38 @@ export const createDynamicHandler = (app: AnyElysia) => {
for (const [key, value] of request.headers.entries())
context.headers[key] = value

const routeCookieConfig = (
(hooks as {
cookie?: {
config?: CookieOptions & {
sign?: true | string | string[]
}
}
}).cookie?.config ??
(validator?.cookie?.config as
| (CookieOptions & {
sign?: true | string | string[]
})
| undefined)
)

const cookieMeta = {
domain:
app.config.cookie?.domain ??
// @ts-expect-error
validator?.cookie?.config.domain,
expires:
app.config.cookie?.expires ??
// @ts-expect-error
validator?.cookie?.config.expires,
domain: app.config.cookie?.domain ?? routeCookieConfig?.domain,
expires: app.config.cookie?.expires ?? routeCookieConfig?.expires,
httpOnly:
app.config.cookie?.httpOnly ??
// @ts-expect-error
validator?.cookie?.config.httpOnly,
maxAge:
app.config.cookie?.maxAge ??
// @ts-expect-error
validator?.cookie?.config.maxAge,
// @ts-expect-error
path: app.config.cookie?.path ?? validator?.cookie?.config.path,
app.config.cookie?.httpOnly ?? routeCookieConfig?.httpOnly,
maxAge: app.config.cookie?.maxAge ?? routeCookieConfig?.maxAge,
path: app.config.cookie?.path ?? routeCookieConfig?.path,
priority:
app.config.cookie?.priority ??
// @ts-expect-error
validator?.cookie?.config.priority,
app.config.cookie?.priority ?? routeCookieConfig?.priority,
partitioned:
app.config.cookie?.partitioned ??
// @ts-expect-error
validator?.cookie?.config.partitioned,
sameSite:
app.config.cookie?.sameSite ??
// @ts-expect-error
validator?.cookie?.config.sameSite,
secure:
app.config.cookie?.secure ??
// @ts-expect-error
validator?.cookie?.config.secure,
secrets:
app.config.cookie?.secrets ??
// @ts-expect-error
validator?.cookie?.config.secrets,
// @ts-expect-error
sign: app.config.cookie?.sign ?? validator?.cookie?.config.sign
routeCookieConfig?.partitioned,
sameSite: app.config.cookie?.sameSite ?? routeCookieConfig?.sameSite,
secure: app.config.cookie?.secure ?? routeCookieConfig?.secure,
encode: app.config.cookie?.encode ?? routeCookieConfig?.encode,
secrets: app.config.cookie?.secrets ?? routeCookieConfig?.secrets,
sign: app.config.cookie?.sign ?? routeCookieConfig?.sign
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} as CookieOptions & {
sign?: true | string | string[]
}
Expand Down
2 changes: 2 additions & 0 deletions src/type-system/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ export const ElysiaType = {
priority,
sameSite,
secure,
encode,
secrets,
sign,
...options
Expand All @@ -537,6 +538,7 @@ export const ElysiaType = {
priority,
sameSite,
secure,
encode,
secrets,
sign
}
Expand Down
96 changes: 96 additions & 0 deletions test/cookie/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,102 @@ describe('Cookie Response', () => {
])
})

it('uses cookie encode from constructor', async () => {
const app = new Elysia({
cookie: {
encode: (value) => value.replaceAll(' ', '+')
}
}).get('/', ({ cookie: { name } }) => {
name.value = 'seminar Himari'
})

const response = await app.handle(req('/'))

expect(response.headers.getAll('Set-Cookie')).toEqual([
'name=seminar+Himari; Path=/'
])
})

it('uses cookie encode from constructor in dynamic mode', async () => {
const app = new Elysia({
aot: false,
cookie: {
encode: (value) => value.replaceAll(' ', '+')
}
}).get('/', ({ cookie: { name } }) => {
name.value = 'seminar Himari'
})

const response = await app.handle(req('/'))

expect(response.headers.getAll('Set-Cookie')).toEqual([
'name=seminar+Himari; Path=/'
])
})

it('uses cookie encode from route cookie config', async () => {
const app = new Elysia().get(
'/',
({ cookie: { name } }) => {
name.value = 'seminar Himari'
},
{
cookie: t.Cookie(
{
name: t.Optional(t.String())
},
{
encode: (value) => value.replaceAll(' ', '+')
}
)
}
)

const response = await app.handle(req('/'))

expect(response.headers.getAll('Set-Cookie')).toEqual([
'name=seminar+Himari; Path=/'
])
})

it('uses cookie encode from route cookie config in dynamic mode', async () => {
const app = new Elysia({ aot: false }).get(
'/',
({ cookie: { name } }) => {
name.value = 'seminar Himari'
},
{
cookie: t.Cookie(
{
name: t.Optional(t.String())
},
{
encode: (value) => value.replaceAll(' ', '+')
}
)
}
)

const response = await app.handle(req('/'))

expect(response.headers.getAll('Set-Cookie')).toEqual([
'name=seminar+Himari; Path=/'
])
})

it('uses cookie encode set on a cookie instance', async () => {
const app = new Elysia().get('/', ({ cookie: { name } }) => {
name.encode = (value) => value.replaceAll(' ', '+')
name.value = 'seminar Himari'
})

const response = await app.handle(req('/'))

expect(response.headers.getAll('Set-Cookie')).toEqual([
'name=seminar+Himari; Path=/'
])
})

it('retain cookie value when using set if not provided', async () => {
const response = await app.handle(req('/set'))

Expand Down