Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting all cookie package serialize/parse options #9063

Merged
merged 12 commits into from
Jan 3, 2024
5 changes: 5 additions & 0 deletions .changeset/tiny-days-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

#9062 - allow specifying all cookie package get/set options
2 changes: 1 addition & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export type {
} from '../assets/types.js';
export type { RemotePattern } from '../assets/utils/remotePattern.js';
export type { SSRManifest } from '../core/app/types.js';
export type { AstroCookies } from '../core/cookies/index.js';
export type { AstroCookies, AstroCookieSetOptions, AstroCookieGetOptions } from '../core/cookies/index.js';

export interface AstroBuiltinProps {
'client:load'?: boolean;
Expand Down
30 changes: 12 additions & 18 deletions packages/astro/src/core/cookies/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import type { CookieSerializeOptions } from 'cookie';
import type { CookieSerializeOptions, CookieParseOptions } from 'cookie';
import { parse, serialize } from 'cookie';
import { AstroError, AstroErrorData } from '../errors/index.js';

interface AstroCookieSetOptions {
domain?: string;
expires?: Date;
httpOnly?: boolean;
maxAge?: number;
path?: string;
sameSite?: boolean | 'lax' | 'none' | 'strict';
secure?: boolean;
}
export type AstroCookieSetOptions = CookieSerializeOptions

export type AstroCookieGetOptions = CookieParseOptions;

type AstroCookieDeleteOptions = Pick<AstroCookieSetOptions, 'domain' | 'path'>;

Expand Down Expand Up @@ -97,7 +91,7 @@ class AstroCookies implements AstroCookiesInterface {
* @param key The cookie to get.
* @returns An object containing the cookie value as well as convenience methods for converting its value.
*/
get(key: string): AstroCookie | undefined {
get(key: string, options: AstroCookieGetOptions | undefined = undefined): AstroCookie | undefined {
// Check for outgoing Set-Cookie values first
if (this.#outgoing?.has(key)) {
let [serializedValue, , isSetValue] = this.#outgoing.get(key)!;
Expand All @@ -108,7 +102,7 @@ class AstroCookies implements AstroCookiesInterface {
}
}

const values = this.#ensureParsed();
const values = this.#ensureParsed(options);
if (key in values) {
const value = values[key];
return new AstroCookie(value);
Expand All @@ -121,12 +115,12 @@ class AstroCookies implements AstroCookiesInterface {
* @param key The cookie to check for.
* @returns
*/
has(key: string): boolean {
has(key: string, options: AstroCookieGetOptions | undefined = undefined): boolean {
if (this.#outgoing?.has(key)) {
let [, , isSetValue] = this.#outgoing.get(key)!;
return isSetValue;
}
const values = this.#ensureParsed();
const values = this.#ensureParsed(options);
return !!values[key];
}

Expand Down Expand Up @@ -185,9 +179,9 @@ class AstroCookies implements AstroCookiesInterface {
}
}

#ensureParsed(): Record<string, string> {
#ensureParsed(options: AstroCookieGetOptions | undefined = undefined): Record<string, string> {
if (!this.#requestValues) {
this.#parse();
this.#parse(options);
}
if (!this.#requestValues) {
this.#requestValues = {};
Expand All @@ -202,13 +196,13 @@ class AstroCookies implements AstroCookiesInterface {
return this.#outgoing;
}

#parse() {
#parse(options: AstroCookieGetOptions | undefined = undefined) {
const raw = this.#request.headers.get('cookie');
if (!raw) {
return;
}

this.#requestValues = parse(raw);
this.#requestValues = parse(raw, options);
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/cookies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export {
getSetCookiesFromResponse,
responseHasCookies,
} from './response.js';
export type { AstroCookieSetOptions, AstroCookieGetOptions } from "./cookies.js";
24 changes: 24 additions & 0 deletions packages/astro/test/units/cookies/get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,30 @@ describe('astro/src/core/cookies', () => {
expect(cookies.get('foo').value).to.equal('bar');
});

it('gets the cookie value with default decode', () => {
const url = 'http://localhost';
const req = new Request('http://example.com/', {
headers: {
cookie: `url=${encodeURIComponent(url)}`,
},
});
const cookies = new AstroCookies(req);
// by default decodeURIComponent is used on the value
expect(cookies.get('url').value).to.equal(url);
});

it('gets the cookie value with custom decode', () => {
const url = 'http://localhost';
const req = new Request('http://example.com/', {
headers: {
cookie: `url=${encodeURIComponent(url)}`,
},
});
const cookies = new AstroCookies(req);
// set decode to the identity function to prevent decodeURIComponent on the value
expect(cookies.get('url', { decode: o => o }).value).to.equal(encodeURIComponent(url));
});

it("Returns undefined is the value doesn't exist", () => {
const req = new Request('http://example.com/');
let cookies = new AstroCookies(req);
Expand Down
23 changes: 23 additions & 0 deletions packages/astro/test/units/cookies/set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@ describe('astro/src/core/cookies', () => {
expect(headers[0]).to.equal('foo=bar');
});

it('Sets a cookie value that can be serialized w/ defaultencodeURIComponent behavior', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);
const url = 'http://localhost/path';
cookies.set('url', url);
let headers = Array.from(cookies.headers());
expect(headers).to.have.a.lengthOf(1);
// by default cookie value is URI encoded
expect(headers[0]).to.equal(`url=${encodeURIComponent(url)}`);
});

it('Sets a cookie value that can be serialized w/ custom encode behavior', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);
const url = 'http://localhost/path';
// set encode option to the identity function
cookies.set('url', url, { encode: o => o });
let headers = Array.from(cookies.headers());
expect(headers).to.have.a.lengthOf(1);
// no longer URI encoded
expect(headers[0]).to.equal(`url=${url}`);
});

it('Can set cookie options', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);
Expand Down