-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwt.ts
147 lines (131 loc) · 3.3 KB
/
jwt.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
import * as crypto from 'crypto'
import {
utf8ToUint8Array,
encodeBase64URL,
arrayBufferToBase64URL,
decodeBase64URL,
} from '../../utils/encode'
import { AlgorithmTypes } from './types'
import {
JwtTokenInvalid,
JwtTokenNotBefore,
JwtTokenExpired,
JwtTokenSignatureMismatched,
JwtAlgorithmNotImplemented,
} from './types'
interface AlgorithmParams {
name: string
namedCurve?: string
hash?: {
name: string
}
}
enum CryptoKeyFormat {
RAW = 'raw',
PKCS8 = 'pkcs8',
SPKI = 'spki',
JWK = 'jwk',
}
enum CryptoKeyUsage {
Ecrypt = 'encrypt',
Decrypt = 'decrypt',
Sign = 'sign',
Verify = 'verify',
Deriverkey = 'deriveKey',
DeriveBits = 'deriveBits',
WrapKey = 'wrapKey',
UnwrapKey = 'unwrapKey',
}
const param = (name: AlgorithmTypes): AlgorithmParams => {
switch (name.toUpperCase()) {
case 'HS256':
return {
name: 'HMAC',
hash: {
name: 'SHA-256',
},
}
case 'HS384':
return {
name: 'HMAC',
hash: {
name: 'SHA-384',
},
}
case 'HS512':
return {
name: 'HMAC',
hash: {
name: 'SHA-512',
},
}
default:
throw new JwtAlgorithmNotImplemented(name)
}
}
const signing = async (
data: string,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<ArrayBuffer> => {
if (!crypto.subtle || !crypto.subtle.importKey) {
throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.')
}
const cryptoKey = await crypto.subtle.importKey(
CryptoKeyFormat.RAW,
utf8ToUint8Array(secret),
param(alg),
false,
[CryptoKeyUsage.Sign]
)
return await crypto.subtle.sign(param(alg), cryptoKey, utf8ToUint8Array(data))
}
export const sign = async (
payload: unknown,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<string> => {
const encodedPayload = await encodeBase64URL(JSON.stringify(payload))
const encodedHeader = await encodeBase64URL(JSON.stringify({ alg, typ: 'JWT' }))
const partialToken = `${encodedHeader}.${encodedPayload}`
const signature: string = await arrayBufferToBase64URL(await signing(partialToken, secret, alg))
return `${partialToken}.${signature}`
}
export const verify = async (
token: string,
secret: string,
alg: AlgorithmTypes = AlgorithmTypes.HS256
): Promise<boolean> => {
const tokenParts = token.split('.')
if (tokenParts.length !== 3) {
throw new JwtTokenInvalid(token)
}
const { payload } = decode(token)
if (payload.nbf && payload.nbf > Math.floor(Date.now() / 1000)) {
throw new JwtTokenNotBefore(token)
}
if (payload.exp && payload.exp <= Math.floor(Date.now() / 1000)) {
throw new JwtTokenExpired(token)
}
const signature: string = await arrayBufferToBase64URL(
await signing(tokenParts.slice(0, 2).join('.'), secret, alg)
)
if (signature !== tokenParts[2]) {
throw new JwtTokenSignatureMismatched(token)
}
return true
}
// eslint-disable-next-line
export const decode = (token: string): { header: any; payload: any } => {
try {
const [h, p] = token.split('.')
const header = JSON.parse(decodeBase64URL(h))
const payload = JSON.parse(decodeBase64URL(p))
return {
header,
payload,
}
} catch (e) {
throw new JwtTokenInvalid(token)
}
}