@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 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#L12) 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.
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#L12) 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.
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": "5.1.0",
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": "^5.1.0"
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 './internal/types';
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
- #auth = new AsyncContextValue<AuthContextShape>(this);
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.#auth.set({});
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.#auth.get()?.principal;
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.#auth.get()!.principal = p;
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.#auth.get()?.authToken;
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.#auth.get()!.authToken = token;
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.#auth.get()?.authenticatorState;
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.#auth.get()!.authenticatorState = state;
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.#auth.set(undefined);
78
+ this.#value.set(undefined);
79
79
  }
80
80
  }
@@ -9,4 +9,3 @@ export class PrincipalTarget {
9
9
  }
10
10
  export class AuthorizerTarget { }
11
11
  export class AuthenticatorTarget { }
12
- export type AuthToken = { value: string, type: string };
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
  }
@@ -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 };