-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBearerAuthenticator.ts
113 lines (90 loc) · 3.64 KB
/
BearerAuthenticator.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
import BaseAuthenticator from "./Base";
import {
Algorithm,
BearerAuthenticateOpts,
BearerClassOptions,
IBearerClass,
Payload,
} from "../types/BearerAuthenticator";
class BearerAuthenticator extends BaseAuthenticator implements IBearerClass {
private secret: string;
readonly algorithm: Algorithm;
constructor(options: BearerClassOptions) {
super(options);
this.secret = options.secret;
this.algorithm = options.algorithm || { name: 'HMAC', hash: 'SHA-256', abbreviation: 'HS256' };
}
private base64UrlEncode(data: string): string {
const base64 = btoa(data);
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
private base64UrlDecode(base64Url: string): string {
let str: string = base64Url.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) {
str += '=';
}
return atob(str);
}
private async importKeyForAlgorithm(): Promise<CryptoKey> {
return await crypto.subtle.importKey('raw', new TextEncoder().encode(this.secret), this.algorithm, true, ['sign', 'verify']);
}
private async signData(data: Uint8Array): Promise<ArrayBuffer> {
const key = await this.importKeyForAlgorithm();
return await crypto.subtle.sign(this.algorithm, key, data);
}
async verfy(token: string): Promise<boolean> {
try {
const [headerB64, payloadB64, signatureB64] = token.split('.');
const header = JSON.parse(this.base64UrlDecode(headerB64));
const signature = Uint8Array.from(this.base64UrlDecode(signatureB64), c => c.charCodeAt(0));
if (header.alg !== this.algorithm.abbreviation) {
throw new Error('Invalid algorithm');
}
const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
const valid = await crypto.subtle.verify(this.algorithm, await this.importKeyForAlgorithm(), signature, data);
if (!valid) return false;
const payload = JSON.parse(this.base64UrlDecode(payloadB64));
const expiration = payload.exp;
if (!expiration) return true;
const currentTime = Math.floor(Date.now() / 1000);
return expiration > currentTime;
} catch (error) {
console.error('Error during verification:', error);
return false;
}
}
getPayload(token: string): Record<string, any> {
const [, payloadB64] = token.split('.');
const payload = JSON.parse(this.base64UrlDecode(payloadB64));
return payload;
}
getTokenFromRequest(request: Request): string | undefined {
const authorization = request.headers.get('Authorization');
if (!authorization || !authorization.startsWith('Bearer ')) return;
const [, token] = authorization.split('Bearer ');
return token;
}
async isAuthenticated({ request }: BearerAuthenticateOpts): Promise<boolean> {
const token = this.getTokenFromRequest(request);
if (!token) return false;
return await this.verfy(token);
}
async sign<P extends Payload>(payload: P): Promise<string> {
try {
const header = {
alg: this.algorithm.abbreviation,
typ: 'JWT'
};
const encodedHeader = this.base64UrlEncode(JSON.stringify(header));
const encodedPayload = this.base64UrlEncode(JSON.stringify(payload));
const data = new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`);
const signature = await this.signData(data);
const encodedSignature = this.base64UrlEncode(String.fromCharCode(...new Uint8Array(signature)));
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
} catch (error) {
console.error('Error during signing:', error);
throw new Error('Failed to sign the payload.');
}
}
}
export default BearerAuthenticator;