0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-06 22:10:10 -05:00

Allow setting all cookie package serialize/parse options (#9063)

* #9062: allow setting all cookie package serialize/parse options

* 9062: fix scripts to original arrangement

* feat: only add specific properties

* Update tiny-days-dance.md

* Add examples to the changeset

* Update .changeset/tiny-days-dance.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/tiny-days-dance.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/tiny-days-dance.md

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Update .changeset/tiny-days-dance.md

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

---------

Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>
Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
Co-authored-by: Matthew Phillips <matthew@skypack.dev>
Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
Alex Sherwin 2024-01-03 08:19:41 -05:00 committed by GitHub
parent 08402ad584
commit f33fe3190b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 10 deletions

View file

@ -0,0 +1,28 @@
---
'astro': minor
---
Cookie encoding / decoding can now be customized
Adds new `encode` and `decode` functions to allow customizing how cookies are encoded and decoded. For example, you can bypass the default encoding via `encodeURIComponent` when adding a URL as part of a cookie:
```astro
---
import { encodeCookieValue } from "./cookies";
Astro.cookies.set('url', Astro.url.toString(), {
// Override the default encoding so that URI components are not encoded
encode: value => encodeCookieValue(value)
});
---
```
Later, you can decode the URL in the same way:
```astro
---
import { decodeCookieValue } from "./cookies";
const url = Astro.cookies.get('url', {
decode: value => decodeCookieValue(value)
});
---
```

View file

@ -64,7 +64,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;

View file

@ -2,7 +2,7 @@ import type { CookieSerializeOptions } from 'cookie';
import { parse, serialize } from 'cookie';
import { AstroError, AstroErrorData } from '../errors/index.js';
interface AstroCookieSetOptions {
export interface AstroCookieSetOptions {
domain?: string;
expires?: Date;
httpOnly?: boolean;
@ -10,8 +10,13 @@ interface AstroCookieSetOptions {
path?: string;
sameSite?: boolean | 'lax' | 'none' | 'strict';
secure?: boolean;
encode?: (value: string) => string;
}
export interface AstroCookieGetOptions {
decode?: (value: string) => string;
};
type AstroCookieDeleteOptions = Pick<AstroCookieSetOptions, 'domain' | 'path'>;
interface AstroCookieInterface {
@ -97,7 +102,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)!;
@ -108,7 +113,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);
@ -121,12 +126,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];
}
@ -185,9 +190,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 = {};
@ -202,13 +207,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);
}
}

View file

@ -4,3 +4,4 @@ export {
getSetCookiesFromResponse,
responseHasCookies,
} from './response.js';
export type { AstroCookieSetOptions, AstroCookieGetOptions } from "./cookies.js";

View file

@ -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);

View file

@ -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);