Skip to content

Commit

Permalink
Accept common cookie attributes when deleting a cookie (#10671)
Browse files Browse the repository at this point in the history
* Accept common cookie attributes when deleting a cookie

* Fix AstroCookieSetOptions IDE annotations

* Use AstroCookieSetOptions to construct AstroCookieDeleteOptions

* Update .changeset/shaggy-cats-film.md

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>

---------

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
  • Loading branch information
fshafiee and florian-lefebvre committed Apr 10, 2024
1 parent cdd0c7c commit 9e14a78
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-cats-film.md
@@ -0,0 +1,5 @@
---
"astro": minor
---

Adds the `httpOnly`, `sameSite`, and `secure` options when deleting a cookie
35 changes: 17 additions & 18 deletions packages/astro/src/core/cookies/cookies.ts
Expand Up @@ -2,22 +2,16 @@ import type { CookieSerializeOptions } from 'cookie';
import { parse, serialize } from 'cookie';
import { AstroError, AstroErrorData } from '../errors/index.js';

export interface AstroCookieSetOptions {
domain?: string;
expires?: Date;
httpOnly?: boolean;
maxAge?: number;
path?: string;
sameSite?: boolean | 'lax' | 'none' | 'strict';
secure?: boolean;
encode?: (value: string) => string;
}
export type AstroCookieSetOptions = Pick<
CookieSerializeOptions,
'domain' | 'path' | 'expires' | 'maxAge' | 'httpOnly' | 'sameSite' | 'secure' | 'encode'
>;

export interface AstroCookieGetOptions {
decode?: (value: string) => string;
}

type AstroCookieDeleteOptions = Pick<AstroCookieSetOptions, 'domain' | 'path'>;
type AstroCookieDeleteOptions = Omit<AstroCookieSetOptions, 'expires' | 'maxAge' | 'encode'>;

interface AstroCookieInterface {
value: string;
Expand Down Expand Up @@ -78,17 +72,22 @@ class AstroCookies implements AstroCookiesInterface {
* @param options Options related to this deletion, such as the path of the cookie.
*/
delete(key: string, options?: AstroCookieDeleteOptions): void {
/**
* The `@ts-expect-error` is necessary because `maxAge` and `expires` properties
* must not appear in the AstroCookieDeleteOptions type.
*/
const {
// @ts-expect-error
maxAge: _ignoredMaxAge,
// @ts-expect-error
expires: _ignoredExpires,
...sanitizedOptions
} = options || {};
const serializeOptions: CookieSerializeOptions = {
expires: DELETED_EXPIRATION,
...sanitizedOptions,
};

if (options?.domain) {
serializeOptions.domain = options.domain;
}
if (options?.path) {
serializeOptions.path = options.path;
}

// Set-Cookie: token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
this.#ensureOutgoingMap().set(key, [
DELETED_VALUE,
Expand Down
39 changes: 35 additions & 4 deletions packages/astro/test/units/cookies/delete.test.js
Expand Up @@ -47,26 +47,57 @@ describe('astro/src/core/cookies', () => {
assert.equal(cookies.has('foo'), false);
});

it('can provide a path', () => {
it('deletes a cookie with attributes', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);

cookies.delete('foo', {
domain: 'example.com',
path: '/subpath/',
priority: 'high',
secure: true,
httpOnly: true,
sameSite: 'strict',
});

let headers = Array.from(cookies.headers());
assert.equal(headers.length, 1);
assert.equal(/foo=deleted/.test(headers[0]), true);
assert.equal(/Expires=Thu, 01 Jan 1970 00:00:00 GMT/.test(headers[0]), true);
assert.equal(/Domain=example.com/.test(headers[0]), true);
assert.equal(/Path=\/subpath\//.test(headers[0]), true);
assert.equal(/Priority=High/.test(headers[0]), true);
assert.equal(/Secure/.test(headers[0]), true);
assert.equal(/HttpOnly/.test(headers[0]), true);
assert.equal(/SameSite=Strict/.test(headers[0]), true);
});

it('ignores expires option', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);

cookies.delete('foo', {
expires: new Date(),
});

let headers = Array.from(cookies.headers());
assert.equal(headers.length, 1);
assert.equal(/foo=deleted/.test(headers[0]), true);
assert.equal(/Expires=Thu, 01 Jan 1970 00:00:00 GMT/.test(headers[0]), true);
});

it('can provide a domain', () => {
it('ignores maxAge option', () => {
let req = new Request('http://example.com/');
let cookies = new AstroCookies(req);

cookies.delete('foo', {
domain: '.example.com',
maxAge: 60,
});

let headers = Array.from(cookies.headers());
assert.equal(headers.length, 1);
assert.equal(/Domain=\.example\.com/.test(headers[0]), true);
assert.equal(/foo=deleted/.test(headers[0]), true);
assert.equal(/Expires=Thu, 01 Jan 1970 00:00:00 GMT/.test(headers[0]), true);
});
});
});

0 comments on commit 9e14a78

Please sign in to comment.