-
Notifications
You must be signed in to change notification settings - Fork 1
/
cookies.ts
180 lines (155 loc) · 5.99 KB
/
cookies.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import { CookieStore, RequestCookieStore } from "https://ghuc.cc/worker-tools/request-cookie-store/index.ts";
import { SignedCookieStore, DeriveOptions } from "https://ghuc.cc/worker-tools/signed-cookie-store/index.ts";
import { EncryptedCookieStore } from "https://ghuc.cc/worker-tools/encrypted-cookie-store/index.ts";
import { ResolvablePromise } from 'https://ghuc.cc/worker-tools/resolvable-promise/index.ts';
import { forbidden } from "https://ghuc.cc/worker-tools/response-creators/index.ts";
import { Awaitable } from "./utils/common-types.ts";
import { MiddlewareCookieStore } from "./utils/middleware-cookie-store.ts";
import { headersSetCookieFix } from './utils/headers-set-cookie-fix.ts'
import { unsettle } from "./utils/unsettle.ts";
import { Context } from "./index.ts";
export async function cookiesFrom(cookieStore: CookieStore): Promise<Cookies> {
return Object.fromEntries((await cookieStore.getAll()).map(({ name, value }) => [name, value]));
}
/**
* An object of the cookies sent with this request.
* It is for reading convenience only.
* To make changes, use the associated cookie store instead (provided by the middleware along with this object)
*/
export type Cookies = { readonly [key: string]: string };
export interface CookiesContext {
cookieStore: CookieStore,
cookies: Cookies,
}
export interface UnsignedCookiesContext extends CookiesContext {
unsignedCookieStore: CookieStore,
unsignedCookies: Cookies
}
export interface SignedCookiesContext extends CookiesContext {
signedCookieStore: CookieStore,
signedCookies: Cookies,
}
export interface EncryptedCookiesContext extends CookiesContext {
encryptedCookieStore: CookieStore,
encryptedCookies: Cookies,
}
export interface CookiesOptions extends DeriveOptions {
keyring?: readonly CryptoKey[];
}
export const plainCookies = () => async <X extends Context>(ax: Awaitable<X>): Promise<X & UnsignedCookiesContext> => {
const x = await ax;
const cookieStore = new RequestCookieStore(x.request);
const requestDuration = new ResolvablePromise<void>();
const unsignedCookieStore = new MiddlewareCookieStore(cookieStore, requestDuration)
const unsignedCookies = await cookiesFrom(unsignedCookieStore);
const nx = Object.assign(x, {
cookieStore: unsignedCookieStore,
cookies: unsignedCookies,
unsignedCookieStore,
unsignedCookies,
})
x.effects.push(response => {
requestDuration.resolve();
const { headers: cookieHeaders } = cookieStore
if (cookieHeaders.length) response.headers.append('VARY', 'Cookie')
const { status, statusText, headers, body } = response
return new Response(body, {
status,
statusText,
headers: [
...headersSetCookieFix(headers),
...cookieHeaders,
],
});
})
return nx;
}
export { plainCookies as unsignedCookies }
export const signedCookies = (opts: CookiesOptions) => {
// TODO: options to provide own cryptokey??
// TODO: What if secret isn't known at initialization (e.g. Cloudflare Workers)
if (!opts.secret) throw TypeError('Secret missing');
const keyPromise = SignedCookieStore.deriveCryptoKey(opts);
return async <X extends Context>(ax: Awaitable<X>): Promise<X & SignedCookiesContext> => {
const x = await ax;
const request = x.request;
const cookieStore = new RequestCookieStore(request);
const requestDuration = new ResolvablePromise<void>();
const signedCookieStore = new MiddlewareCookieStore(new SignedCookieStore(cookieStore, await keyPromise, {
keyring: opts.keyring
}), requestDuration);
let signedCookies: Cookies;
try {
signedCookies = await cookiesFrom(signedCookieStore);
} catch {
throw forbidden();
}
const nx = Object.assign(x, {
cookieStore: signedCookieStore,
cookies: signedCookies,
signedCookieStore,
signedCookies,
})
x.effects.push(async response => {
// Wait for all set cookie promises to settle
requestDuration.resolve();
await unsettle(signedCookieStore.allSettledPromise);
const { headers: cookieHeaders } = cookieStore
if (cookieHeaders.length) response.headers.append('VARY', 'Cookie')
const { status, statusText, headers, body } = response
return new Response(body, {
status,
statusText,
headers: [
...headersSetCookieFix(headers),
...cookieHeaders,
],
})
})
return nx;
};
}
export const encryptedCookies = (opts: CookiesOptions) => {
// TODO: options to provide own cryptokey??
// TODO: What if secret isn't known at initialization (e.g. Cloudflare Workers)
if (!opts.secret) throw TypeError('Secret missing');
const keyPromise = EncryptedCookieStore.deriveCryptoKey(opts);
return async <X extends Context>(ax: Awaitable<X>): Promise<X & EncryptedCookiesContext> => {
const x = await ax;
const request = x.request;
const cookieStore = new RequestCookieStore(request);
const requestDuration = new ResolvablePromise<void>();
const encryptedCookieStore = new MiddlewareCookieStore(new EncryptedCookieStore(cookieStore, await keyPromise, {
keyring: opts.keyring
}), requestDuration);
let encryptedCookies: Cookies;
try {
encryptedCookies = await cookiesFrom(encryptedCookieStore);
} catch {
throw forbidden();
}
const nx = Object.assign(x, {
cookieStore: encryptedCookieStore,
cookies: encryptedCookies,
encryptedCookieStore,
encryptedCookies,
})
x.effects.push(async response => {
// Wait for all set cookie promises to settle
requestDuration.resolve();
await unsettle(encryptedCookieStore.allSettledPromise);
const { headers: cookieHeaders } = cookieStore
if (cookieHeaders.length) response.headers.append('VARY', 'Cookie')
const { status, statusText, headers, body } = response
return new Response(body, {
status,
statusText,
headers: [
...headersSetCookieFix(headers),
...cookieHeaders,
],
})
})
return nx;
};
}