@travetto/auth-web 7.1.3 → 8.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/package.json +5 -5
- package/src/codec.ts +3 -3
- package/src/config.ts +3 -3
- package/src/interceptors/verify.ts +2 -2
- package/support/test/server.ts +17 -13
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ export interface Authenticator<T = unknown, C = unknown, P extends Principal = P
|
|
|
52
52
|
*
|
|
53
53
|
* @returns Valid principal if authenticated
|
|
54
54
|
* @returns undefined if authentication is valid, but incomplete (multi-step)
|
|
55
|
-
* @throws
|
|
55
|
+
* @throws Error if authentication fails
|
|
56
56
|
*/
|
|
57
57
|
authenticate(payload: T, context?: C): Promise<P | undefined> | P | undefined;
|
|
58
58
|
}
|
|
@@ -123,7 +123,7 @@ import type { Jwt, Verifier, SupportedAlgorithms } from 'njwt';
|
|
|
123
123
|
import { type AuthContext, AuthenticationError, type AuthToken, type Principal } from '@travetto/auth';
|
|
124
124
|
import { Injectable, Inject } from '@travetto/di';
|
|
125
125
|
import { type WebResponse, type WebRequest, type WebAsyncContext, CookieJar } from '@travetto/web';
|
|
126
|
-
import {
|
|
126
|
+
import { RuntimeError, castTo, TimeUtil } from '@travetto/runtime';
|
|
127
127
|
|
|
128
128
|
import { CommonPrincipalCodecSymbol, type PrincipalCodec } from './types.ts';
|
|
129
129
|
import type { WebAuthConfig } from './config.ts';
|
|
@@ -186,13 +186,13 @@ export class JWTPrincipalCodec implements PrincipalCodec {
|
|
|
186
186
|
async create(value: Principal, keyId: string = 'default'): Promise<string> {
|
|
187
187
|
const entry = this.config.keyMap[keyId];
|
|
188
188
|
if (!entry) {
|
|
189
|
-
throw new
|
|
189
|
+
throw new RuntimeError('Requested unknown key for signing');
|
|
190
190
|
}
|
|
191
191
|
// Weird issue with their ES module support
|
|
192
192
|
const { default: { create } } = await import('njwt');
|
|
193
193
|
const jwt = create({}, '-')
|
|
194
194
|
.setExpiration(value.expiresAt!)
|
|
195
|
-
.setIssuedAt(TimeUtil.
|
|
195
|
+
.setIssuedAt(TimeUtil.duration((value.issuedAt ?? new Date()).getTime(), 's'))
|
|
196
196
|
.setClaim('core', castTo({ ...value }))
|
|
197
197
|
.setIssuer(value.issuer!)
|
|
198
198
|
.setJti(value.sessionId!)
|
|
@@ -225,7 +225,7 @@ A trivial/sample custom [PrincipalCodec](https://github.com/travetto/travetto/tr
|
|
|
225
225
|
import type { Principal } from '@travetto/auth';
|
|
226
226
|
import type { PrincipalCodec } from '@travetto/auth-web';
|
|
227
227
|
import { Injectable } from '@travetto/di';
|
|
228
|
-
import {
|
|
228
|
+
import { BinaryMetadataUtil } from '@travetto/runtime';
|
|
229
229
|
import type { WebResponse, WebRequest } from '@travetto/web';
|
|
230
230
|
|
|
231
231
|
@Injectable()
|
|
@@ -234,7 +234,7 @@ export class CustomCodec implements PrincipalCodec {
|
|
|
234
234
|
|
|
235
235
|
decode(request: WebRequest): Promise<Principal | undefined> | Principal | undefined {
|
|
236
236
|
const [userId, sig] = request.headers.get('USER_ID')?.split(':') ?? [];
|
|
237
|
-
if (userId && sig ===
|
|
237
|
+
if (userId && sig === BinaryMetadataUtil.hash(userId + this.secret)) {
|
|
238
238
|
let principal: Principal | undefined;
|
|
239
239
|
// Lookup user from db, remote system, etc.,
|
|
240
240
|
return principal;
|
|
@@ -243,7 +243,7 @@ export class CustomCodec implements PrincipalCodec {
|
|
|
243
243
|
}
|
|
244
244
|
encode(response: WebResponse, data: Principal | undefined): WebResponse {
|
|
245
245
|
if (data) {
|
|
246
|
-
response.headers.set('USER_ID', `${data.id}:${
|
|
246
|
+
response.headers.set('USER_ID', `${data.id}:${BinaryMetadataUtil.hash(data.id + this.secret)}`);
|
|
247
247
|
}
|
|
248
248
|
return response;
|
|
249
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/auth-web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Web authentication integration support for the Travetto framework",
|
|
6
6
|
"keywords": [
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"directory": "module/auth-web"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/auth": "^
|
|
31
|
-
"@travetto/config": "^
|
|
32
|
-
"@travetto/web": "^
|
|
30
|
+
"@travetto/auth": "^8.0.0-alpha.0",
|
|
31
|
+
"@travetto/config": "^8.0.0-alpha.0",
|
|
32
|
+
"@travetto/web": "^8.0.0-alpha.0",
|
|
33
33
|
"njwt": "^2.0.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/test": "^
|
|
36
|
+
"@travetto/test": "^8.0.0-alpha.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/test": {
|
package/src/codec.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Jwt, Verifier, SupportedAlgorithms } from 'njwt';
|
|
|
3
3
|
import { type AuthContext, AuthenticationError, type AuthToken, type Principal } from '@travetto/auth';
|
|
4
4
|
import { Injectable, Inject } from '@travetto/di';
|
|
5
5
|
import { type WebResponse, type WebRequest, type WebAsyncContext, CookieJar } from '@travetto/web';
|
|
6
|
-
import {
|
|
6
|
+
import { RuntimeError, castTo, TimeUtil } from '@travetto/runtime';
|
|
7
7
|
|
|
8
8
|
import { CommonPrincipalCodecSymbol, type PrincipalCodec } from './types.ts';
|
|
9
9
|
import type { WebAuthConfig } from './config.ts';
|
|
@@ -66,13 +66,13 @@ export class JWTPrincipalCodec implements PrincipalCodec {
|
|
|
66
66
|
async create(value: Principal, keyId: string = 'default'): Promise<string> {
|
|
67
67
|
const entry = this.config.keyMap[keyId];
|
|
68
68
|
if (!entry) {
|
|
69
|
-
throw new
|
|
69
|
+
throw new RuntimeError('Requested unknown key for signing');
|
|
70
70
|
}
|
|
71
71
|
// Weird issue with their ES module support
|
|
72
72
|
const { default: { create } } = await import('njwt');
|
|
73
73
|
const jwt = create({}, '-')
|
|
74
74
|
.setExpiration(value.expiresAt!)
|
|
75
|
-
.setIssuedAt(TimeUtil.
|
|
75
|
+
.setIssuedAt(TimeUtil.duration((value.issuedAt ?? new Date()).getTime(), 's'))
|
|
76
76
|
.setClaim('core', castTo({ ...value }))
|
|
77
77
|
.setIssuer(value.issuer!)
|
|
78
78
|
.setJti(value.sessionId!)
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Config } from '@travetto/config';
|
|
2
|
-
import { Runtime,
|
|
2
|
+
import { Runtime, RuntimeError, BinaryMetadataUtil } from '@travetto/runtime';
|
|
3
3
|
import { Ignore, Secret } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
type KeyEntry = { key: string, id: string };
|
|
@@ -19,11 +19,11 @@ export class WebAuthConfig {
|
|
|
19
19
|
|
|
20
20
|
postConstruct(): void {
|
|
21
21
|
if (!this.signingKey && Runtime.production) {
|
|
22
|
-
throw new
|
|
22
|
+
throw new RuntimeError('The default signing key is only valid for development use, please specify a config value at web.auth.signingKey');
|
|
23
23
|
}
|
|
24
24
|
this.signingKey ??= 'dummy';
|
|
25
25
|
|
|
26
|
-
const all = [this.signingKey].flat().map(key => ({ key, id:
|
|
26
|
+
const all = [this.signingKey].flat().map(key => ({ key, id: BinaryMetadataUtil.hash(key, { length: 8 }) }));
|
|
27
27
|
this.keyMap = Object.fromEntries(all.map(entry => [entry.id, entry]));
|
|
28
28
|
this.keyMap.default = all[0];
|
|
29
29
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuntimeError, Util } from '@travetto/runtime';
|
|
2
2
|
import type { WebInterceptor, WebInterceptorCategory, WebChainedContext, WebResponse, WebInterceptorContext } from '@travetto/web';
|
|
3
3
|
import { Injectable, Inject } from '@travetto/di';
|
|
4
4
|
import { Config } from '@travetto/config';
|
|
@@ -72,7 +72,7 @@ export class AuthVerifyInterceptor implements WebInterceptor<WebAuthVerifyConfig
|
|
|
72
72
|
if (!principal) {
|
|
73
73
|
throw new AuthenticationError('User is unauthenticated');
|
|
74
74
|
} else if (!config.matcher(new Set(principal.permissions))) {
|
|
75
|
-
throw new
|
|
75
|
+
throw new RuntimeError('Access denied', { category: 'permissions' });
|
|
76
76
|
}
|
|
77
77
|
break;
|
|
78
78
|
}
|
package/support/test/server.ts
CHANGED
|
@@ -89,16 +89,20 @@ export abstract class AuthWebServerSuite extends BaseWebSuite {
|
|
|
89
89
|
@Inject()
|
|
90
90
|
config: WebAuthConfig;
|
|
91
91
|
|
|
92
|
-
getCookie(headers: WebHeaders): Cookie | undefined {
|
|
93
|
-
|
|
92
|
+
async getCookie(headers: WebHeaders): Promise<Cookie | undefined> {
|
|
93
|
+
const jar = new CookieJar();
|
|
94
|
+
await jar.importSetCookieHeader(headers.getSetCookie());
|
|
95
|
+
return jar.getAll()[0];
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
getCookieHeader(headers: WebHeaders): string | undefined {
|
|
97
|
-
|
|
98
|
+
async getCookieHeader(headers: WebHeaders): Promise<string | undefined> {
|
|
99
|
+
const jar = new CookieJar();
|
|
100
|
+
await jar.importSetCookieHeader(headers.getSetCookie());
|
|
101
|
+
return jar.exportCookieHeader();
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
getCookieExpires(headers: WebHeaders): Date | undefined {
|
|
101
|
-
const v = this.getCookie(headers)?.expires;
|
|
104
|
+
async getCookieExpires(headers: WebHeaders): Promise<Date | undefined> {
|
|
105
|
+
const v = (await this.getCookie(headers))?.expires;
|
|
102
106
|
return v ? new Date(v) : undefined;
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -151,7 +155,7 @@ export abstract class AuthWebServerSuite extends BaseWebSuite {
|
|
|
151
155
|
}
|
|
152
156
|
}, false);
|
|
153
157
|
assert(statusCode === 201);
|
|
154
|
-
const cookie = this.getCookieHeader(headers);
|
|
158
|
+
const cookie = await this.getCookieHeader(headers);
|
|
155
159
|
assert(cookie);
|
|
156
160
|
|
|
157
161
|
const { context: { httpStatusCode: lastStatus } } = await this.request({
|
|
@@ -222,7 +226,7 @@ export abstract class AuthWebServerSuite extends BaseWebSuite {
|
|
|
222
226
|
}
|
|
223
227
|
}, false);
|
|
224
228
|
assert(statusCode === 201);
|
|
225
|
-
const cookie = this.getCookieHeader(headers);
|
|
229
|
+
const cookie = await this.getCookieHeader(headers);
|
|
226
230
|
assert(cookie);
|
|
227
231
|
|
|
228
232
|
const { body, context: { httpStatusCode: lastStatus } } = await this.request({
|
|
@@ -250,17 +254,17 @@ export abstract class AuthWebServerSuite extends BaseWebSuite {
|
|
|
250
254
|
assert(statusCode === 201);
|
|
251
255
|
|
|
252
256
|
const start = Date.now();
|
|
253
|
-
const cookie = this.getCookieHeader(headers);
|
|
257
|
+
const cookie = await this.getCookieHeader(headers);
|
|
254
258
|
assert(cookie);
|
|
255
259
|
|
|
256
|
-
const expires = this.getCookieExpires(headers);
|
|
260
|
+
const expires = await this.getCookieExpires(headers);
|
|
257
261
|
assert(expires);
|
|
258
262
|
|
|
259
263
|
const { headers: selfHeaders, context: { httpStatusCode: lastStatus } } = await this.request({
|
|
260
264
|
context: { httpMethod: 'GET', path: '/test/auth/self' },
|
|
261
265
|
headers: { cookie }
|
|
262
266
|
}, false);
|
|
263
|
-
assert(this.getCookie(selfHeaders) === undefined);
|
|
267
|
+
assert(await this.getCookie(selfHeaders) === undefined);
|
|
264
268
|
assert(lastStatus === 200);
|
|
265
269
|
|
|
266
270
|
const used = (Date.now() - start);
|
|
@@ -272,9 +276,9 @@ export abstract class AuthWebServerSuite extends BaseWebSuite {
|
|
|
272
276
|
headers: { cookie }
|
|
273
277
|
}, false);
|
|
274
278
|
assert(lastStatus2 === 200);
|
|
275
|
-
assert(this.getCookie(selfHeadersRenew));
|
|
279
|
+
assert(await this.getCookie(selfHeadersRenew));
|
|
276
280
|
|
|
277
|
-
const expiresRenew = this.getCookieExpires(selfHeadersRenew);
|
|
281
|
+
const expiresRenew = await this.getCookieExpires(selfHeadersRenew);
|
|
278
282
|
assert(expiresRenew);
|
|
279
283
|
|
|
280
284
|
const delta = expiresRenew.getTime() - expires.getTime();
|