forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunstable_signed_cookie.ts
128 lines (121 loc) · 3.85 KB
/
unstable_signed_cookie.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
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { decodeHex, encodeHex } from "@std/encoding/hex";
const encoder = new TextEncoder();
function splitByLast(value: string, separator: string): [string, string] {
const index = value.lastIndexOf(separator);
return index === -1
? [value, ""]
: [value.slice(0, index), value.slice(index + 1)];
}
/**
* Returns a promise with the signed cookie value from the given cryptographic
* key.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @example Usage
* ```ts ignore no-assert
* import { signCookie } from "@std/http/unstable-signed-cookie";
* import { setCookie } from "@std/http/cookie";
*
* const key = await crypto.subtle.generateKey(
* { name: "HMAC", hash: "SHA-256" },
* true,
* ["sign", "verify"],
* );
* const value = await signCookie("my-cookie-value", key);
*
* const headers = new Headers();
* setCookie(headers, {
* name: "my-cookie-name",
* value,
* });
*
* const cookieHeader = headers.get("set-cookie");
* ```
*
* @param value The cookie value to sign.
* @param key The cryptographic key to sign the cookie with.
* @returns The signed cookie.
*/
export async function signCookie(
value: string,
key: CryptoKey,
): Promise<string> {
const data = encoder.encode(value);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureHex = encodeHex(signature);
return `${value}.${signatureHex}`;
}
/**
* Returns a promise of a boolean indicating whether the signed cookie is valid.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @example Usage
* ```ts ignore no-assert
* import { verifySignedCookie } from "@std/http/unstable-signed-cookie";
* import { getCookies } from "@std/http/cookie";
*
* const key = await crypto.subtle.generateKey(
* { name: "HMAC", hash: "SHA-256" },
* true,
* ["sign", "verify"],
* );
*
* const headers = new Headers({
* Cookie: "location=tokyo.37f7481039762eef5cd46669f93c0a3214dfecba7d0cdc0b0dc40036063fb22e",
* });
* const signedCookie = getCookies(headers)["location"];
* if (signedCookie === undefined) throw new Error("Cookie not found");
* await verifySignedCookie(signedCookie, key);
* ```
*
* @param signedCookie The signed cookie to verify.
* @param key The cryptographic key to verify the cookie with.
* @returns Whether or not the cookie is valid.
*/
export async function verifySignedCookie(
signedCookie: string,
key: CryptoKey,
): Promise<boolean> {
const [value, signatureHex] = splitByLast(signedCookie, ".");
if (!value || !signatureHex) return false;
const data = encoder.encode(value);
const signature = decodeHex(signatureHex);
return await crypto.subtle.verify("HMAC", key, signature, data);
}
/**
* Parses a signed cookie to get its value.
*
* Important: always verify the cookie using {@linkcode verifySignedCookie} first.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @example Usage
* ```ts ignore no-assert
* import { verifySignedCookie, parseSignedCookie } from "@std/http/unstable-signed-cookie";
* import { getCookies } from "@std/http/cookie";
*
* const key = await crypto.subtle.generateKey(
* { name: "HMAC", hash: "SHA-256" },
* true,
* ["sign", "verify"],
* );
*
* const headers = new Headers({
* Cookie: "location=tokyo.37f7481039762eef5cd46669f93c0a3214dfecba7d0cdc0b0dc40036063fb22e",
* });
* const signedCookie = getCookies(headers)["location"];
* if (signedCookie === undefined) throw new Error("Cookie not found");
* await verifySignedCookie(signedCookie, key);
* const cookie = parseSignedCookie(signedCookie);
* ```
*
* @param signedCookie The signed cookie to parse the value from.
* @returns The parsed cookie.
*/
export function parseSignedCookie(signedCookie: string): string {
return splitByLast(signedCookie, ".")[0];
}