| 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
181
182
183
184
185
186
187
188
189 | 1x
1x
1x
1x
1x
1x
1x
1x
1x
4x
4x
4x
4x
4x
1x
4x
1x
4x
2x
2x
1x
1x
1x
1x
1x
4x
4x
4x
4x
1x
2x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
| import jwt from 'jsonwebtoken';
import {
AuthAccessToken,
AuthRefreshToken,
AuthServer,
Payload,
Scope
} from '../';
describe('Auth Server', () => {
const ONE_MINUTE = 1000 * 60;
const ONE_DAY = ONE_MINUTE * 60 * 24;
const ONE_MONTH = ONE_DAY * 30;
const ACCESS_TOKEN_SECRET = 'password';
const refreshTokens = new Map();
class AccessToken implements AuthAccessToken {
public buildPayload({
id,
companyId,
admin
}: {
id: string;
companyId: string;
admin: boolean;
}) {
const scope = admin
? authScope.create(['admin:read', 'admin:write'])
: '';
return { id, companyId, scope };
}
public create(payload: { uId: string; cId: string; scope: string }) {
return jwt.sign(payload, ACCESS_TOKEN_SECRET, {
expiresIn: '20m'
});
}
public verify(accessToken: string) {
const payload = jwt.verify(accessToken, ACCESS_TOKEN_SECRET, {
algorithms: ['HS256'],
clockTolerance: 80 // seconds to tolerate
});
// This should never happen cause our payload is a valid JSON
Iif (typeof payload === 'string') return {};
return payload;
}
}
class RefreshToken implements AuthRefreshToken {
public async getPayload(refreshToken: string, reset: () => any) {
reset();
return refreshTokens.get(refreshToken);
}
public async create({ id: userId }: { id: string }) {
const id = Date.now().toString();
refreshTokens.set(id, {
userId,
expireAt: new Date(Date.now() + ONE_MONTH)
});
return id;
}
public remove(refreshToken: string) {
return refreshTokens.delete(refreshToken);
}
}
const authPayload = new Payload({
uId: 'id',
cId: 'companyId',
scope: 'scope'
});
const authScope = new Scope({
admin: 'a'
});
const authServer = new AuthServer({
accessToken: new AccessToken(),
refreshToken: new RefreshToken(),
payload: authPayload,
scope: authScope
});
const expiredToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1SWQiOiJ1c2VyXzEyMyIsImNJZCI6ImNvbXBhbnlfMTIzIiwic2NvcGUiOiJhOnI6dyIsImlhdCI6MTUxODE0MTIzNCwiZXhwIjoxNTE4MTQyNDM0fQ.3ZRmx08htMX5KLsv8VhBVD8vjxHzWOiDDli7JXFf83Q';
// Payload to create a token
const userPayload = {
id: 'user_123',
companyId: 'company_123',
admin: true
};
// Payload got from a token
const tokenPayload = {
id: userPayload.id,
companyId: userPayload.companyId,
scope: 'a:r:w'
};
it('should set a default scope and payload', () => {
const auth = new AuthServer({
accessToken: new AccessToken(),
refreshToken: new RefreshToken()
});
expect(auth.scope).toBeInstanceOf(Scope);
expect(auth.payload).toBeInstanceOf(Payload);
});
it('creates an accessToken', () => {
expect(authServer.createAccessToken(userPayload)).toEqual({
accessToken: expect.any(String),
payload: tokenPayload
});
});
it('creates a refreshToken', async () => {
expect(typeof await authServer.createRefreshToken(userPayload)).toBe(
'string'
);
});
it('creates both tokens', async () => {
expect(await authServer.createTokens(userPayload)).toEqual({
refreshToken: expect.any(String),
accessToken: expect.any(String),
payload: tokenPayload
});
});
it('gets the payload for an accessToken', async () => {
const refreshToken = await authServer.createRefreshToken(userPayload);
const reset = () => {
// do nothing
};
expect(await authServer.getPayload(refreshToken, reset)).toEqual({
userId: userPayload.id,
expireAt: refreshTokens.get(refreshToken).expireAt
});
});
it('Removes a refreshToken', async () => {
const refreshToken = await authServer.createRefreshToken(userPayload);
expect(authServer.removeRefreshRoken(refreshToken)).toBe(true);
expect(authServer.removeRefreshRoken(refreshToken)).toBe(false);
expect(authServer.removeRefreshRoken('')).toBe(false);
});
describe('Verifies an accessToken', () => {
it('returns the payload', () => {
const at = authServer.createAccessToken(userPayload);
const decodedPayload = authServer.verify(at.accessToken);
expect(decodedPayload).toEqual(at.payload);
expect(decodedPayload).toEqual(tokenPayload);
});
it('throws if expired', () => {
expect(() => {
authServer.verify(expiredToken);
}).toThrow();
});
});
describe('decodes an accessToken', () => {
it('Returns the payload', () => {
const at = authServer.createAccessToken(userPayload);
const decodedPayload = authServer.decode(at.accessToken);
expect(decodedPayload).toEqual(at.payload);
expect(decodedPayload).toEqual(tokenPayload);
});
it('Returns null with empty accessToken', () => {
expect(authServer.decode('')).toBe(null);
});
it('Returns null if expired', () => {
expect(authServer.decode(expiredToken)).toBe(null);
});
});
});
|