-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
109 lines (96 loc) · 2.65 KB
/
index.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
interface Payload {
[key: string]: any;
}
function base64UrlEncode(data: string): string {
return Buffer.from(data)
.toString("base64")
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
function base64UrlDecode(data: string): string {
data = data.replace(/-/g, "+").replace(/_/g, "/");
while (data.length % 4) {
data += "=";
}
return Buffer.from(data, "base64").toString("utf8");
}
async function createHmac(secret: string, message: string) {
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
const signature = await crypto.subtle.sign(
"HMAC",
key,
encoder.encode(message)
);
return Buffer.from(signature).toString("base64");
}
export async function encode_jwt(
secret: string,
id: string | number,
payload: Payload,
ttl?: number,
aud?: string,
iss?: string
): Promise<string> {
const header = { alg: "HS256", typ: "JWT" };
const iat = Math.floor(Date.now() / 1000);
const exp = ttl ? iat + ttl : undefined;
const body = { ...payload, sub: id, iat, exp, aud, iss };
const base64UrlHeader = base64UrlEncode(JSON.stringify(header));
const base64UrlBody = base64UrlEncode(JSON.stringify(body));
const signature = await createHmac(
secret,
`${base64UrlHeader}.${base64UrlBody}`
);
const base64UrlSignature = base64UrlEncode(signature);
return `${base64UrlHeader}.${base64UrlBody}.${base64UrlSignature}`;
}
export async function decode_jwt(
secret: string,
token: string
): Promise<{
id: string;
payload: Payload;
expires_at?: Date;
issued_at?: Date;
audience?: string;
issuer?: string;
}> {
const [header, payload, signature] = token.split(".");
const base64UrlPayload = base64UrlDecode(payload);
const expectedSignature = await createHmac(secret, `${header}.${payload}`);
if (base64UrlEncode(expectedSignature) !== signature) {
throw new Error("Invalid token signature");
}
const decodedPayload = JSON.parse(base64UrlPayload);
return {
id: decodedPayload.sub,
payload: decodedPayload,
expires_at: decodedPayload.exp
? new Date(decodedPayload.exp * 1000)
: undefined,
issued_at: decodedPayload.iat
? new Date(decodedPayload.iat * 1000)
: undefined,
audience: decodedPayload.aud,
issuer: decodedPayload.iss,
};
}
export async function validate_jwt(
secret: string,
token: string
): Promise<boolean> {
try {
const decoded = await decode_jwt(secret, token);
return !decoded.expires_at || decoded.expires_at > new Date();
} catch {
return false;
}
}