@travetto/auth-session 6.0.0-rc.0 → 6.0.0-rc.2
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 -17
- package/__index__.ts +4 -3
- package/package.json +5 -5
- package/src/context.ts +62 -0
- package/src/service.ts +13 -57
- package/src/session.ts +1 -3
- package/support/test/server.ts +17 -17
- package/src/internal/types.ts +0 -4
package/README.md
CHANGED
|
@@ -13,11 +13,11 @@ npm install @travetto/auth-session
|
|
|
13
13
|
yarn add @travetto/auth-session
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
This is a module that adds session support to the [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the Travetto framework") framework, via [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") storage. The concept here, is that the [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the Travetto framework") module provides the solid foundation for ensuring authentication to the system, and transitively to the session data. The [Principal](https://github.com/travetto/travetto/tree/main/module/auth/src/types/principal.ts#
|
|
16
|
+
This is a module that adds session support to the [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the Travetto framework") framework, via [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") storage. The concept here, is that the [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the Travetto framework") module provides the solid foundation for ensuring authentication to the system, and transitively to the session data. The [Principal](https://github.com/travetto/travetto/tree/main/module/auth/src/types/principal.ts#L7) provides a session identifier, which refers to a unique authentication session. Each login will produce a novel session id. This id provides the contract between [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the Travetto framework") and[Auth Session](https://github.com/travetto/travetto/tree/main/module/auth-session#readme "Session provider for the travetto auth module.").
|
|
17
17
|
|
|
18
|
-
This session identifier, is then used when retrieving data from [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") storage. This storage mechanism is not tied to a request/response model, but the [
|
|
18
|
+
This session identifier, is then used when retrieving data from [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") storage. This storage mechanism is not tied to a request/response model, but the [Web Auth Session](https://github.com/travetto/travetto/tree/main/module/auth-web-session#readme "Web authentication session integration support for the Travetto framework") does provide a natural integration with the [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative api for Web Applications with support for the dependency injection.") module.
|
|
19
19
|
|
|
20
|
-
Within the framework the sessions are stored against any [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") implementation that provides [ModelExpirySupport](https://github.com/travetto/travetto/tree/main/module/model/src/
|
|
20
|
+
Within the framework the sessions are stored against any [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") implementation that provides [ModelExpirySupport](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10), as the data needs to be able to be expired appropriately. The list of supported model providers are:
|
|
21
21
|
* [Redis Model Support](https://github.com/travetto/travetto/tree/main/module/model-redis#readme "Redis backing for the travetto model module.")
|
|
22
22
|
* [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module.")
|
|
23
23
|
* [S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.")
|
|
@@ -25,31 +25,32 @@ Within the framework the sessions are stored against any [Data Modeling Support]
|
|
|
25
25
|
* [Elasticsearch Model Source](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch#readme "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.")
|
|
26
26
|
* [File Model Support](https://github.com/travetto/travetto/tree/main/module/model-file#readme "File system backing for the travetto model module.")
|
|
27
27
|
* [Memory Model Support](https://github.com/travetto/travetto/tree/main/module/model-memory#readme "Memory backing for the travetto model module.")
|
|
28
|
-
While the expiry is not necessarily a hard requirement, the implementation without it can be quite messy. To that end, the ability to add [ModelExpirySupport](https://github.com/travetto/travetto/tree/main/module/model/src/
|
|
28
|
+
While the expiry is not necessarily a hard requirement, the implementation without it can be quite messy. To that end, the ability to add [ModelExpirySupport](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10) to the model provider would be the natural extension point if more expiry support is needed.
|
|
29
29
|
|
|
30
30
|
**Code: Sample usage of Session Service**
|
|
31
31
|
```typescript
|
|
32
|
-
class
|
|
32
|
+
export class AuthSessionInterceptor implements WebInterceptor {
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*/
|
|
37
|
-
@Injectable()
|
|
38
|
-
export class AuthSessionInterceptor implements RestInterceptor {
|
|
39
|
-
|
|
40
|
-
dependsOn: Class<RestInterceptor>[] = [AuthContextInterceptor];
|
|
41
|
-
runsBefore: Class<RestInterceptor>[] = [];
|
|
34
|
+
category: WebInterceptorCategory = 'application';
|
|
35
|
+
dependsOn = [AuthContextInterceptor];
|
|
42
36
|
|
|
43
37
|
@Inject()
|
|
44
38
|
service: SessionService;
|
|
45
39
|
|
|
46
40
|
@Inject()
|
|
47
|
-
|
|
41
|
+
context: SessionContext;
|
|
42
|
+
|
|
43
|
+
@Inject()
|
|
44
|
+
webAsyncContext: WebAsyncContext;
|
|
45
|
+
|
|
46
|
+
postConstruct(): void {
|
|
47
|
+
this.webAsyncContext.registerSource(toConcrete<Session>(), () => this.context.get(true));
|
|
48
|
+
this.webAsyncContext.registerSource(toConcrete<SessionData>(), () => this.context.get(true).data);
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
async
|
|
51
|
+
async filter({ next }: WebChainedContext): Promise<WebResponse> {
|
|
50
52
|
try {
|
|
51
53
|
await this.service.load();
|
|
52
|
-
Object.defineProperty(ctx.req, 'session', { get: () => this.service.getOrCreate() });
|
|
53
54
|
return await next();
|
|
54
55
|
} finally {
|
|
55
56
|
await this.service.persist();
|
|
@@ -58,7 +59,7 @@ export class AuthSessionInterceptor implements RestInterceptor {
|
|
|
58
59
|
}
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
The [SessionService](https://github.com/travetto/travetto/tree/main/module/auth-session/src/service.ts#
|
|
62
|
+
The [SessionService](https://github.com/travetto/travetto/tree/main/module/auth-session/src/service.ts#L14) provides the basic integration with the [AuthContext](https://github.com/travetto/travetto/tree/main/module/auth/src/context.ts#L14) to authenticate and isolate session data. The usage is fairly simple, but the import pattern to follow is:
|
|
62
63
|
* load
|
|
63
64
|
* read/modify
|
|
64
65
|
* persist
|
package/__index__.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from './src/service';
|
|
2
|
-
export * from './src/
|
|
3
|
-
export * from './src/
|
|
1
|
+
export * from './src/service.ts';
|
|
2
|
+
export * from './src/context.ts';
|
|
3
|
+
export * from './src/model.ts';
|
|
4
|
+
export * from './src/session.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/auth-session",
|
|
3
|
-
"version": "6.0.0-rc.
|
|
3
|
+
"version": "6.0.0-rc.2",
|
|
4
4
|
"description": "Session provider for the travetto auth module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"directory": "module/auth-session"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/auth": "^6.0.0-rc.
|
|
29
|
-
"@travetto/config": "^6.0.0-rc.
|
|
30
|
-
"@travetto/model": "^6.0.0-rc.
|
|
28
|
+
"@travetto/auth": "^6.0.0-rc.2",
|
|
29
|
+
"@travetto/config": "^6.0.0-rc.2",
|
|
30
|
+
"@travetto/model": "^6.0.0-rc.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/test": "^6.0.0-rc.
|
|
33
|
+
"@travetto/test": "^6.0.0-rc.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/test": {
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Injectable, Inject } from '@travetto/di';
|
|
2
|
+
import { AsyncContext, AsyncContextValue } from '@travetto/context';
|
|
3
|
+
import { AuthContext, AuthenticationError } from '@travetto/auth';
|
|
4
|
+
|
|
5
|
+
import { Session } from './session.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Session context, injectable wherever needed
|
|
9
|
+
*/
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class SessionContext {
|
|
12
|
+
|
|
13
|
+
@Inject()
|
|
14
|
+
context: AsyncContext;
|
|
15
|
+
|
|
16
|
+
@Inject()
|
|
17
|
+
authContext: AuthContext;
|
|
18
|
+
|
|
19
|
+
#value = new AsyncContextValue<Session>(this, { failIfUnbound: { write: true } });
|
|
20
|
+
|
|
21
|
+
#create(): Session {
|
|
22
|
+
const principal = this.authContext.principal;
|
|
23
|
+
if (!principal) {
|
|
24
|
+
throw new AuthenticationError('Unable to establish session without first authenticating');
|
|
25
|
+
}
|
|
26
|
+
return new Session({
|
|
27
|
+
id: principal.sessionId,
|
|
28
|
+
expiresAt: principal.expiresAt,
|
|
29
|
+
issuedAt: principal.issuedAt,
|
|
30
|
+
action: 'create',
|
|
31
|
+
data: {},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get session if defined
|
|
37
|
+
*/
|
|
38
|
+
get(createIfMissing: true): Session;
|
|
39
|
+
get(): Session | undefined;
|
|
40
|
+
get(createIfMissing?: boolean): Session | undefined {
|
|
41
|
+
let val = this.#value.get();
|
|
42
|
+
if (!val && createIfMissing) {
|
|
43
|
+
this.set(val = this.#create());
|
|
44
|
+
}
|
|
45
|
+
return val;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Set the session state directly
|
|
50
|
+
*/
|
|
51
|
+
set(session: Session | undefined): void {
|
|
52
|
+
this.#value.set(session);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Destroy
|
|
57
|
+
*/
|
|
58
|
+
destroy(): void {
|
|
59
|
+
this.get()?.destroy();
|
|
60
|
+
this.set(undefined);
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/service.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { Injectable, Inject } from '@travetto/di';
|
|
2
|
-
import { isStorageSupported } from '@travetto/model/src/internal/service/common';
|
|
3
2
|
import { Runtime, Util } from '@travetto/runtime';
|
|
4
|
-
import { ModelExpirySupport, NotFoundError } from '@travetto/model';
|
|
5
|
-
import {
|
|
6
|
-
import { AuthContext, AuthenticationError, AuthService } from '@travetto/auth';
|
|
3
|
+
import { ModelExpirySupport, NotFoundError, ModelStorageUtil } from '@travetto/model';
|
|
4
|
+
import { AuthContext, AuthService } from '@travetto/auth';
|
|
7
5
|
|
|
8
|
-
import { Session } from './session';
|
|
9
|
-
import { SessionEntry, SessionModelSymbol } from './model';
|
|
6
|
+
import { Session } from './session.ts';
|
|
7
|
+
import { SessionEntry, SessionModelSymbol } from './model.ts';
|
|
8
|
+
import { SessionContext } from './context.ts';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
* during the normal lifecycle of requests.
|
|
11
|
+
* Service for supporting the session and managing the session state
|
|
14
12
|
*/
|
|
15
13
|
@Injectable()
|
|
16
14
|
export class SessionService {
|
|
17
15
|
|
|
18
16
|
@Inject()
|
|
19
|
-
context:
|
|
17
|
+
context: SessionContext;
|
|
20
18
|
|
|
21
19
|
@Inject()
|
|
22
20
|
authContext: AuthContext;
|
|
@@ -26,24 +24,15 @@ export class SessionService {
|
|
|
26
24
|
|
|
27
25
|
#modelService: ModelExpirySupport;
|
|
28
26
|
|
|
29
|
-
#session = new AsyncContextValue<Session>(this);
|
|
30
|
-
|
|
31
27
|
constructor(@Inject(SessionModelSymbol) service: ModelExpirySupport) {
|
|
32
28
|
this.#modelService = service;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
/**
|
|
36
|
-
* Disconnect active session
|
|
37
|
-
*/
|
|
38
|
-
clear(): void {
|
|
39
|
-
this.#session.set(undefined);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
31
|
/**
|
|
43
32
|
* Initialize service if none defined
|
|
44
33
|
*/
|
|
45
34
|
async postConstruct(): Promise<void> {
|
|
46
|
-
if (
|
|
35
|
+
if (ModelStorageUtil.isSupported(this.#modelService) && Runtime.dynamic) {
|
|
47
36
|
await this.#modelService.createModel?.(SessionEntry);
|
|
48
37
|
}
|
|
49
38
|
}
|
|
@@ -79,7 +68,7 @@ export class SessionService {
|
|
|
79
68
|
* Persist session
|
|
80
69
|
*/
|
|
81
70
|
async persist(): Promise<void> {
|
|
82
|
-
const session = this
|
|
71
|
+
const session = this.context.get();
|
|
83
72
|
|
|
84
73
|
// If missing or new and no data
|
|
85
74
|
if (!session || (session.action === 'create' && session.isEmpty())) {
|
|
@@ -110,48 +99,15 @@ export class SessionService {
|
|
|
110
99
|
}
|
|
111
100
|
|
|
112
101
|
/**
|
|
113
|
-
*
|
|
114
|
-
*/
|
|
115
|
-
getOrCreate(): Session {
|
|
116
|
-
const principal = this.authContext.principal;
|
|
117
|
-
if (!principal) {
|
|
118
|
-
throw new AuthenticationError('Unable to establish session without first authenticating');
|
|
119
|
-
}
|
|
120
|
-
const existing = this.#session.get();
|
|
121
|
-
const val = (existing?.action === 'destroy' ? undefined : existing) ??
|
|
122
|
-
new Session({
|
|
123
|
-
id: principal.sessionId,
|
|
124
|
-
expiresAt: principal.expiresAt,
|
|
125
|
-
issuedAt: principal.issuedAt,
|
|
126
|
-
action: 'create',
|
|
127
|
-
data: {},
|
|
128
|
-
});
|
|
129
|
-
this.#session.set(val);
|
|
130
|
-
return val;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Get session if defined
|
|
135
|
-
*/
|
|
136
|
-
get(): Session | undefined {
|
|
137
|
-
return this.#session.get();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Load from request
|
|
102
|
+
* Load from principal
|
|
142
103
|
*/
|
|
143
104
|
async load(): Promise<Session | undefined> {
|
|
144
|
-
if (!this
|
|
105
|
+
if (!this.context.get()) {
|
|
145
106
|
const principal = this.authContext.principal;
|
|
146
107
|
if (principal?.sessionId) {
|
|
147
|
-
this
|
|
108
|
+
this.context.set(await this.#load(principal.sessionId));
|
|
148
109
|
}
|
|
149
110
|
}
|
|
150
|
-
return this
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
destroy(): void {
|
|
154
|
-
this.get()?.destroy();
|
|
155
|
-
this.clear();
|
|
111
|
+
return this.context.get();
|
|
156
112
|
}
|
|
157
113
|
}
|
package/src/session.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { AnyMap, castKey, castTo } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @concrete
|
|
5
|
-
* @augments `@travetto/rest:Context`
|
|
4
|
+
* @concrete
|
|
6
5
|
*/
|
|
7
6
|
export interface SessionData extends AnyMap { }
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Full session object, with metadata
|
|
11
|
-
* @augments `@travetto/rest:Context`
|
|
12
10
|
*/
|
|
13
11
|
export class Session<T extends SessionData = SessionData> {
|
|
14
12
|
/**
|
package/support/test/server.ts
CHANGED
|
@@ -2,17 +2,16 @@ import assert from 'node:assert';
|
|
|
2
2
|
|
|
3
3
|
import { Suite, Test } from '@travetto/test';
|
|
4
4
|
import { Inject } from '@travetto/di';
|
|
5
|
-
import { SessionService } from '@travetto/auth-session';
|
|
5
|
+
import { SessionContext, SessionService } from '@travetto/auth-session';
|
|
6
6
|
import { AuthContext, AuthenticationError } from '@travetto/auth';
|
|
7
7
|
import { AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
8
8
|
import { Util } from '@travetto/runtime';
|
|
9
9
|
|
|
10
|
-
import { InjectableSuite } from '@travetto/di/support/test/suite';
|
|
11
|
-
import { BaseRestSuite } from '@travetto/rest/support/test/base';
|
|
10
|
+
import { InjectableSuite } from '@travetto/di/support/test/suite.ts';
|
|
12
11
|
|
|
13
12
|
@Suite()
|
|
14
13
|
@InjectableSuite()
|
|
15
|
-
export abstract class AuthSessionServerSuite
|
|
14
|
+
export abstract class AuthSessionServerSuite {
|
|
16
15
|
|
|
17
16
|
timeScale = 1;
|
|
18
17
|
|
|
@@ -22,33 +21,34 @@ export abstract class AuthSessionServerSuite extends BaseRestSuite {
|
|
|
22
21
|
@Inject()
|
|
23
22
|
session: SessionService;
|
|
24
23
|
|
|
24
|
+
@Inject()
|
|
25
|
+
sessionContext: SessionContext;
|
|
26
|
+
|
|
25
27
|
@Inject()
|
|
26
28
|
context: AsyncContext;
|
|
27
29
|
|
|
28
30
|
@WithAsyncContext()
|
|
29
31
|
@Test()
|
|
30
32
|
async testSessionEstablishment() {
|
|
31
|
-
await this.auth.init();
|
|
32
|
-
|
|
33
33
|
this.auth.principal = {
|
|
34
34
|
id: 'orange',
|
|
35
35
|
details: {},
|
|
36
36
|
sessionId: Util.uuid()
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
assert(this.
|
|
39
|
+
assert(this.sessionContext.get() === undefined);
|
|
40
40
|
assert(await this.session.load() === undefined);
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
assert(
|
|
44
|
-
|
|
42
|
+
const session = this.sessionContext.get(true);
|
|
43
|
+
assert(session.id === this.auth.principal.sessionId);
|
|
44
|
+
session.data = { name: 'bob' };
|
|
45
45
|
await this.session.persist();
|
|
46
46
|
|
|
47
|
-
this.
|
|
47
|
+
this.sessionContext.set(undefined); // Disconnect
|
|
48
48
|
|
|
49
49
|
assert(await this.session.load() !== undefined);
|
|
50
|
-
const
|
|
51
|
-
assert(
|
|
50
|
+
const session2 = this.sessionContext.get(true);
|
|
51
|
+
assert(session2.data?.name === 'bob');
|
|
52
52
|
|
|
53
53
|
this.auth.principal = {
|
|
54
54
|
id: 'orange',
|
|
@@ -56,17 +56,17 @@ export abstract class AuthSessionServerSuite extends BaseRestSuite {
|
|
|
56
56
|
sessionId: Util.uuid()
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
this.
|
|
59
|
+
this.sessionContext.set(undefined); // Disconnect
|
|
60
60
|
|
|
61
61
|
assert(await this.session.load() === undefined);
|
|
62
|
-
const
|
|
63
|
-
assert.deepStrictEqual(
|
|
62
|
+
const session3 = this.sessionContext.get(true);
|
|
63
|
+
assert.deepStrictEqual(session3.data, {});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
@WithAsyncContext()
|
|
67
67
|
@Test()
|
|
68
68
|
async testUnauthenticatedSession() {
|
|
69
|
-
await assert.throws(() => this.
|
|
69
|
+
await assert.throws(() => this.sessionContext.get(true), AuthenticationError);
|
|
70
70
|
|
|
71
71
|
}
|
|
72
72
|
}
|
package/src/internal/types.ts
DELETED