@travetto/auth 5.1.0 → 6.0.0-rc.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 +18 -10
- package/__index__.ts +3 -1
- package/package.json +2 -2
- package/src/config.ts +17 -0
- package/src/context.ts +10 -10
- package/src/internal/types.ts +0 -1
- package/src/service.ts +39 -0
- package/src/types/principal.ts +9 -9
- package/src/types/token.ts +1 -0
package/README.md
CHANGED
|
@@ -29,7 +29,11 @@ export interface Principal<D = AnyMap> {
|
|
|
29
29
|
/**
|
|
30
30
|
* Primary identifier for a user
|
|
31
31
|
*/
|
|
32
|
-
id: string;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
/**
|
|
34
|
+
* Unique identifier for the principal's lifecycle
|
|
35
|
+
*/
|
|
36
|
+
readonly sessionId?: string;
|
|
33
37
|
/**
|
|
34
38
|
* Date of expiration
|
|
35
39
|
*/
|
|
@@ -38,22 +42,18 @@ export interface Principal<D = AnyMap> {
|
|
|
38
42
|
* Date of issuance
|
|
39
43
|
*/
|
|
40
44
|
issuedAt?: Date;
|
|
41
|
-
/**
|
|
42
|
-
* Max age in seconds a principal is valid
|
|
43
|
-
*/
|
|
44
|
-
maxAge?: number;
|
|
45
45
|
/**
|
|
46
46
|
* The source of the issuance
|
|
47
47
|
*/
|
|
48
|
-
issuer?: string;
|
|
48
|
+
readonly issuer?: string;
|
|
49
49
|
/**
|
|
50
50
|
* Supplemental details
|
|
51
51
|
*/
|
|
52
|
-
details: D;
|
|
52
|
+
readonly details: D;
|
|
53
53
|
/**
|
|
54
54
|
* List of all provided permissions
|
|
55
55
|
*/
|
|
56
|
-
permissions?: string[];
|
|
56
|
+
readonly permissions?: string[];
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -126,15 +126,23 @@ export class AuthService {
|
|
|
126
126
|
* @param authenticators List of valid authentication sources
|
|
127
127
|
*/
|
|
128
128
|
async authenticate<T, C>(payload: T, context: C, authenticators: symbol[]): Promise<Principal | undefined>;
|
|
129
|
+
/**
|
|
130
|
+
* Manage expiry state, renewing if allowed
|
|
131
|
+
*/
|
|
132
|
+
manageExpiry(p?: Principal): void;
|
|
133
|
+
/**
|
|
134
|
+
* Enforce expiry, invalidating the principal if expired
|
|
135
|
+
*/
|
|
136
|
+
enforceExpiry(p?: Principal): Principal | undefined;
|
|
129
137
|
}
|
|
130
138
|
```
|
|
131
139
|
|
|
132
|
-
The [AuthService](https://github.com/travetto/travetto/tree/main/module/auth/src/service.ts#
|
|
140
|
+
The [AuthService](https://github.com/travetto/travetto/tree/main/module/auth/src/service.ts#L14) operates as the owner of the current auth state for a given "request". "Request" here implies a set of operations over a period of time, with the http request/response model being an easy point of reference. This could also tie to a CLI operation, or any other invocation that requires some concept of authentication and authorization.
|
|
133
141
|
|
|
134
142
|
The service allows for storing and retrieving the active [Principal Contract](https://github.com/travetto/travetto/tree/main/module/auth/src/types/principal.ts#L8), and/or the actively persisted auth token. This is extremely useful for other parts of the framework that may request authenticated information (if available). [Rest Auth](https://github.com/travetto/travetto/tree/main/module/auth-rest#readme "Rest authentication integration support for the Travetto framework") makes heavy use of this state for enforcing routes when authentication is required.
|
|
135
143
|
|
|
136
144
|
### Login
|
|
137
|
-
"Logging in" can be thought of going through the action of finding a single source that can authenticate the identity for the request credentials. Some times there may be more than one valid source of authentication that you want to leverage, and the first one to authenticate wins. The [AuthService](https://github.com/travetto/travetto/tree/main/module/auth/src/service.ts#
|
|
145
|
+
"Logging in" can be thought of going through the action of finding a single source that can authenticate the identity for the request credentials. Some times there may be more than one valid source of authentication that you want to leverage, and the first one to authenticate wins. The [AuthService](https://github.com/travetto/travetto/tree/main/module/auth/src/service.ts#L14) operates in this fashion, in which a set of credentials and potential [Authenticator Contract](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authenticator.ts#L14)s are submitted, and the service will attempt to authenticate.
|
|
138
146
|
|
|
139
147
|
Upon successful authentication, an optional [Authorizer Contract](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authorizer.ts#L8) may be invoked to authorize the authenticated user. The [Authenticator Contract](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authenticator.ts#L14) is assumed to be only one within the system, and should be tied to the specific product you are building for. The [Authorizer Contract](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authorizer.ts#L8) should be assumed to have multiple sources, and are generally specific to external third parties. All of these values are collected via the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module and will be auto-registered on startup.
|
|
140
148
|
|
package/__index__.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './src/service';
|
|
2
2
|
export * from './src/context';
|
|
3
|
+
export * from './src/config';
|
|
3
4
|
export * from './src/types/authenticator';
|
|
4
5
|
export * from './src/types/authorizer';
|
|
5
6
|
export * from './src/types/principal';
|
|
6
|
-
export * from './src/types/error';
|
|
7
|
+
export * from './src/types/error';
|
|
8
|
+
export * from './src/types/token';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0-rc.0",
|
|
4
4
|
"description": "Authentication scaffolding for the Travetto framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"authentication",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"directory": "module/auth"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@travetto/context": "^
|
|
26
|
+
"@travetto/context": "^6.0.0-rc.0"
|
|
27
27
|
},
|
|
28
28
|
"travetto": {
|
|
29
29
|
"displayName": "Authentication"
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Config } from '@travetto/config';
|
|
2
|
+
import { TimeSpan, TimeUtil } from '@travetto/runtime';
|
|
3
|
+
import { Ignore } from '@travetto/schema';
|
|
4
|
+
|
|
5
|
+
@Config('auth')
|
|
6
|
+
export class AuthConfig {
|
|
7
|
+
|
|
8
|
+
maxAge: TimeSpan | number = '1h';
|
|
9
|
+
rollingRenew: boolean = true;
|
|
10
|
+
|
|
11
|
+
@Ignore()
|
|
12
|
+
maxAgeMs: number;
|
|
13
|
+
|
|
14
|
+
postConstruct(): void {
|
|
15
|
+
this.maxAgeMs = TimeUtil.asMillis(this.maxAge);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/context.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Inject, Injectable } from '@travetto/di';
|
|
3
3
|
import { AsyncContext, AsyncContextValue } from '@travetto/context';
|
|
4
4
|
|
|
5
|
-
import { AuthToken } from './
|
|
5
|
+
import { AuthToken } from './types/token';
|
|
6
6
|
import { Principal } from './types/principal';
|
|
7
7
|
import { AuthenticatorState } from './types/authenticator';
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ type AuthContextShape = {
|
|
|
15
15
|
@Injectable()
|
|
16
16
|
export class AuthContext {
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
#value = new AsyncContextValue<AuthContextShape>(this);
|
|
19
19
|
|
|
20
20
|
@Inject()
|
|
21
21
|
context: AsyncContext;
|
|
@@ -25,49 +25,49 @@ export class AuthContext {
|
|
|
25
25
|
* @private
|
|
26
26
|
*/
|
|
27
27
|
init(): void {
|
|
28
|
-
this.#
|
|
28
|
+
this.#value.set({});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Get the principal, if set
|
|
33
33
|
*/
|
|
34
34
|
get principal(): Principal | undefined {
|
|
35
|
-
return this.#
|
|
35
|
+
return this.#value.get()?.principal;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Set principal
|
|
40
40
|
*/
|
|
41
41
|
set principal(p: Principal | undefined) {
|
|
42
|
-
this.#
|
|
42
|
+
this.#value.get()!.principal = p;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Get the authentication token, if it exists
|
|
47
47
|
*/
|
|
48
48
|
get authToken(): AuthToken | undefined {
|
|
49
|
-
return this.#
|
|
49
|
+
return this.#value.get()?.authToken;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Set/overwrite the user's authentication token
|
|
54
54
|
*/
|
|
55
55
|
set authToken(token: AuthToken | undefined) {
|
|
56
|
-
this.#
|
|
56
|
+
this.#value.get()!.authToken = token;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Get the authenticator state, if it exists
|
|
61
61
|
*/
|
|
62
62
|
get authenticatorState(): AuthenticatorState | undefined {
|
|
63
|
-
return this.#
|
|
63
|
+
return this.#value.get()?.authenticatorState;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Set/overwrite the authenticator state
|
|
68
68
|
*/
|
|
69
69
|
set authenticatorState(state: AuthenticatorState | undefined) {
|
|
70
|
-
this.#
|
|
70
|
+
this.#value.get()!.authenticatorState = state;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -75,6 +75,6 @@ export class AuthContext {
|
|
|
75
75
|
* @private
|
|
76
76
|
*/
|
|
77
77
|
async clear(): Promise<void> {
|
|
78
|
-
this.#
|
|
78
|
+
this.#value.set(undefined);
|
|
79
79
|
}
|
|
80
80
|
}
|
package/src/internal/types.ts
CHANGED
package/src/service.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { DependencyRegistry, Inject, Injectable } from '@travetto/di';
|
|
3
|
+
import { TimeUtil } from '@travetto/runtime';
|
|
3
4
|
|
|
4
5
|
import { AuthenticatorTarget } from './internal/types';
|
|
5
6
|
import { Principal } from './types/principal';
|
|
@@ -7,6 +8,7 @@ import { Authenticator } from './types/authenticator';
|
|
|
7
8
|
import { Authorizer } from './types/authorizer';
|
|
8
9
|
import { AuthenticationError } from './types/error';
|
|
9
10
|
import { AuthContext } from './context';
|
|
11
|
+
import { AuthConfig } from './config';
|
|
10
12
|
|
|
11
13
|
@Injectable()
|
|
12
14
|
export class AuthService {
|
|
@@ -14,6 +16,9 @@ export class AuthService {
|
|
|
14
16
|
@Inject()
|
|
15
17
|
authContext: AuthContext;
|
|
16
18
|
|
|
19
|
+
@Inject()
|
|
20
|
+
config: AuthConfig;
|
|
21
|
+
|
|
17
22
|
#authenticators = new Map<symbol, Promise<Authenticator>>();
|
|
18
23
|
|
|
19
24
|
@Inject()
|
|
@@ -72,4 +77,38 @@ export class AuthService {
|
|
|
72
77
|
// Take the last error and return
|
|
73
78
|
throw new AuthenticationError('Unable to authenticate', { cause: lastError });
|
|
74
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Manage expiry state, renewing if allowed
|
|
83
|
+
*/
|
|
84
|
+
manageExpiry(p?: Principal): void {
|
|
85
|
+
if (!p) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.config.maxAgeMs) {
|
|
90
|
+
p.expiresAt ??= TimeUtil.fromNow(this.config.maxAgeMs);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
p.issuedAt ??= new Date();
|
|
94
|
+
|
|
95
|
+
if (p.expiresAt && this.config.maxAgeMs && this.config.rollingRenew) { // Session behavior
|
|
96
|
+
const end = p.expiresAt.getTime();
|
|
97
|
+
const midPoint = end - this.config.maxAgeMs / 2;
|
|
98
|
+
if (Date.now() > midPoint) { // If we are past the half way mark, renew the token
|
|
99
|
+
p.issuedAt = new Date();
|
|
100
|
+
p.expiresAt = TimeUtil.fromNow(this.config.maxAgeMs); // This will trigger a re-send
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Enforce expiry, invalidating the principal if expired
|
|
107
|
+
*/
|
|
108
|
+
enforceExpiry(p?: Principal): Principal | undefined {
|
|
109
|
+
if (p && p.expiresAt && p.expiresAt.getTime() < Date.now()) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return p;
|
|
113
|
+
}
|
|
75
114
|
}
|
package/src/types/principal.ts
CHANGED
|
@@ -2,14 +2,18 @@ import { AnyMap } from '@travetto/runtime';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A user principal, including permissions and details
|
|
5
|
-
* @concrete ../internal/types#PrincipalTarget
|
|
6
5
|
* @augments `@travetto/rest:Context`
|
|
6
|
+
* @concrete ../internal/types#PrincipalTarget
|
|
7
7
|
*/
|
|
8
8
|
export interface Principal<D = AnyMap> {
|
|
9
9
|
/**
|
|
10
10
|
* Primary identifier for a user
|
|
11
11
|
*/
|
|
12
|
-
id: string;
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/**
|
|
14
|
+
* Unique identifier for the principal's lifecycle
|
|
15
|
+
*/
|
|
16
|
+
readonly sessionId?: string;
|
|
13
17
|
/**
|
|
14
18
|
* Date of expiration
|
|
15
19
|
*/
|
|
@@ -18,20 +22,16 @@ export interface Principal<D = AnyMap> {
|
|
|
18
22
|
* Date of issuance
|
|
19
23
|
*/
|
|
20
24
|
issuedAt?: Date;
|
|
21
|
-
/**
|
|
22
|
-
* Max age in seconds a principal is valid
|
|
23
|
-
*/
|
|
24
|
-
maxAge?: number;
|
|
25
25
|
/**
|
|
26
26
|
* The source of the issuance
|
|
27
27
|
*/
|
|
28
|
-
issuer?: string;
|
|
28
|
+
readonly issuer?: string;
|
|
29
29
|
/**
|
|
30
30
|
* Supplemental details
|
|
31
31
|
*/
|
|
32
|
-
details: D;
|
|
32
|
+
readonly details: D;
|
|
33
33
|
/**
|
|
34
34
|
* List of all provided permissions
|
|
35
35
|
*/
|
|
36
|
-
permissions?: string[];
|
|
36
|
+
readonly permissions?: string[];
|
|
37
37
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type AuthToken = { value: string, type: string };
|