@furystack/rest-service 13.0.0 → 14.1.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/CHANGELOG.md +124 -0
- package/README.md +47 -28
- package/esm/actions/error-action.d.ts +7 -2
- package/esm/actions/error-action.d.ts.map +1 -1
- package/esm/actions/error-action.js +7 -2
- package/esm/actions/error-action.js.map +1 -1
- package/esm/actions/get-current-user.d.ts +1 -4
- package/esm/actions/get-current-user.d.ts.map +1 -1
- package/esm/actions/get-current-user.js +1 -4
- package/esm/actions/get-current-user.js.map +1 -1
- package/esm/actions/get-current-user.spec.js +10 -6
- package/esm/actions/get-current-user.spec.js.map +1 -1
- package/esm/actions/is-authenticated.d.ts +3 -4
- package/esm/actions/is-authenticated.d.ts.map +1 -1
- package/esm/actions/is-authenticated.js +3 -4
- package/esm/actions/is-authenticated.js.map +1 -1
- package/esm/actions/is-authenticated.spec.js +10 -6
- package/esm/actions/is-authenticated.spec.js.map +1 -1
- package/esm/actions/logout-action.spec.js +11 -9
- package/esm/actions/logout-action.spec.js.map +1 -1
- package/esm/actions/logout.d.ts +2 -6
- package/esm/actions/logout.d.ts.map +1 -1
- package/esm/actions/logout.js +3 -7
- package/esm/actions/logout.js.map +1 -1
- package/esm/actions/not-found-action.d.ts +1 -3
- package/esm/actions/not-found-action.d.ts.map +1 -1
- package/esm/actions/not-found-action.js +1 -3
- package/esm/actions/not-found-action.js.map +1 -1
- package/esm/actions/password-login-action.d.ts +5 -12
- package/esm/actions/password-login-action.d.ts.map +1 -1
- package/esm/actions/password-login-action.js +6 -13
- package/esm/actions/password-login-action.js.map +1 -1
- package/esm/actions/password-login-action.spec.js +23 -28
- package/esm/actions/password-login-action.spec.js.map +1 -1
- package/esm/authenticate.d.ts +5 -11
- package/esm/authenticate.d.ts.map +1 -1
- package/esm/authenticate.js +6 -12
- package/esm/authenticate.js.map +1 -1
- package/esm/authenticate.spec.js +41 -33
- package/esm/authenticate.spec.js.map +1 -1
- package/esm/authentication-providers/authentication-provider.d.ts +17 -0
- package/esm/authentication-providers/authentication-provider.d.ts.map +1 -1
- package/esm/authentication-providers/basic-auth-provider.d.ts +7 -0
- package/esm/authentication-providers/basic-auth-provider.d.ts.map +1 -1
- package/esm/authentication-providers/basic-auth-provider.js +7 -0
- package/esm/authentication-providers/basic-auth-provider.js.map +1 -1
- package/esm/authentication-providers/cookie-auth-provider.d.ts.map +1 -1
- package/esm/authentication-providers/cookie-auth-provider.js +8 -0
- package/esm/authentication-providers/cookie-auth-provider.js.map +1 -1
- package/esm/authorize.d.ts +5 -17
- package/esm/authorize.d.ts.map +1 -1
- package/esm/authorize.js +5 -17
- package/esm/authorize.js.map +1 -1
- package/esm/authorize.spec.js +34 -29
- package/esm/authorize.spec.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.d.ts +6 -11
- package/esm/endpoint-generators/create-delete-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.js +7 -11
- package/esm/endpoint-generators/create-delete-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.spec.js +14 -21
- package/esm/endpoint-generators/create-delete-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts +11 -11
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.js +13 -12
- package/esm/endpoint-generators/create-get-collection-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js +43 -70
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts +6 -11
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.js +7 -11
- package/esm/endpoint-generators/create-get-entity-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js +16 -33
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.d.ts +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.js +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.js.map +1 -1
- package/esm/endpoint-generators/create-get-schema-action.d.ts +1 -1
- package/esm/endpoint-generators/create-get-schema-action.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-schema-action.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.d.ts +7 -13
- package/esm/endpoint-generators/create-patch-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.js +8 -13
- package/esm/endpoint-generators/create-patch-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.spec.js +10 -17
- package/esm/endpoint-generators/create-patch-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.d.ts +7 -13
- package/esm/endpoint-generators/create-post-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.js +8 -13
- package/esm/endpoint-generators/create-post-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.spec.js +41 -14
- package/esm/endpoint-generators/create-post-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/utils.d.ts +17 -2
- package/esm/endpoint-generators/utils.d.ts.map +1 -1
- package/esm/endpoint-generators/utils.js +33 -20
- package/esm/endpoint-generators/utils.js.map +1 -1
- package/esm/get-schema-from-api.d.ts +1 -1
- package/esm/get-schema-from-api.d.ts.map +1 -1
- package/esm/get-schema-from-api.js +1 -0
- package/esm/get-schema-from-api.js.map +1 -1
- package/esm/get-schema-from-api.spec.js.map +1 -1
- package/esm/helpers.d.ts +54 -88
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +90 -92
- package/esm/helpers.js.map +1 -1
- package/esm/helpers.spec.d.ts +2 -1
- package/esm/helpers.spec.d.ts.map +1 -1
- package/esm/helpers.spec.js +41 -38
- package/esm/helpers.spec.js.map +1 -1
- package/esm/http-authentication-settings.d.ts +56 -12
- package/esm/http-authentication-settings.d.ts.map +1 -1
- package/esm/http-authentication-settings.js +27 -29
- package/esm/http-authentication-settings.js.map +1 -1
- package/esm/http-server-pool.d.ts +64 -0
- package/esm/http-server-pool.d.ts.map +1 -0
- package/esm/http-server-pool.js +95 -0
- package/esm/http-server-pool.js.map +1 -0
- package/esm/http-server-pool.spec.d.ts +2 -0
- package/esm/http-server-pool.spec.d.ts.map +1 -0
- package/esm/http-server-pool.spec.js +193 -0
- package/esm/http-server-pool.spec.js.map +1 -0
- package/esm/http-user-context.d.ts +34 -44
- package/esm/http-user-context.d.ts.map +1 -1
- package/esm/http-user-context.js +75 -95
- package/esm/http-user-context.js.map +1 -1
- package/esm/http-user-context.spec.d.ts +8 -2
- package/esm/http-user-context.spec.d.ts.map +1 -1
- package/esm/http-user-context.spec.js +226 -332
- package/esm/http-user-context.spec.js.map +1 -1
- package/esm/identity-cache-keys.d.ts +28 -0
- package/esm/identity-cache-keys.d.ts.map +1 -0
- package/esm/identity-cache-keys.js +13 -0
- package/esm/identity-cache-keys.js.map +1 -0
- package/esm/identity-cache-keys.spec.d.ts +2 -0
- package/esm/identity-cache-keys.spec.d.ts.map +1 -0
- package/esm/identity-cache-keys.spec.js +21 -0
- package/esm/identity-cache-keys.spec.js.map +1 -0
- package/esm/identity-event-bus.d.ts +102 -0
- package/esm/identity-event-bus.d.ts.map +1 -0
- package/esm/identity-event-bus.js +151 -0
- package/esm/identity-event-bus.js.map +1 -0
- package/esm/identity-event-bus.spec.d.ts +2 -0
- package/esm/identity-event-bus.spec.d.ts.map +1 -0
- package/esm/identity-event-bus.spec.js +392 -0
- package/esm/identity-event-bus.spec.js.map +1 -0
- package/esm/index.d.ts +9 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +9 -4
- package/esm/index.js.map +1 -1
- package/esm/login-response-strategy.d.ts +11 -10
- package/esm/login-response-strategy.d.ts.map +1 -1
- package/esm/login-response-strategy.js +7 -6
- package/esm/login-response-strategy.js.map +1 -1
- package/esm/login-response-strategy.spec.js +25 -29
- package/esm/login-response-strategy.spec.js.map +1 -1
- package/esm/openapi/generate-openapi-document.spec.js.map +1 -1
- package/esm/proxy-manager.spec.js +152 -176
- package/esm/proxy-manager.spec.js.map +1 -1
- package/esm/proxy-runtime.d.ts +27 -0
- package/esm/proxy-runtime.d.ts.map +1 -0
- package/esm/proxy-runtime.js +45 -0
- package/esm/proxy-runtime.js.map +1 -0
- package/esm/proxy-runtime.spec.d.ts +2 -0
- package/esm/proxy-runtime.spec.d.ts.map +1 -0
- package/esm/proxy-runtime.spec.js +39 -0
- package/esm/proxy-runtime.spec.js.map +1 -0
- package/esm/request-action-implementation.d.ts +25 -0
- package/esm/request-action-implementation.d.ts.map +1 -1
- package/esm/request-action-implementation.js +9 -0
- package/esm/request-action-implementation.js.map +1 -1
- package/esm/rest-api-runtime.d.ts +76 -0
- package/esm/rest-api-runtime.d.ts.map +1 -0
- package/esm/rest-api-runtime.js +131 -0
- package/esm/rest-api-runtime.js.map +1 -0
- package/esm/rest-api-runtime.spec.d.ts +2 -0
- package/esm/rest-api-runtime.spec.d.ts.map +1 -0
- package/esm/rest-api-runtime.spec.js +58 -0
- package/esm/rest-api-runtime.spec.js.map +1 -0
- package/esm/rest-service.integration.spec.d.ts +1 -1
- package/esm/rest-service.integration.spec.d.ts.map +1 -1
- package/esm/rest-service.integration.spec.js +57 -80
- package/esm/rest-service.integration.spec.js.map +1 -1
- package/esm/rest.integration.test.js +26 -84
- package/esm/rest.integration.test.js.map +1 -1
- package/esm/server-telemetry.d.ts +49 -0
- package/esm/server-telemetry.d.ts.map +1 -0
- package/esm/server-telemetry.js +25 -0
- package/esm/server-telemetry.js.map +1 -0
- package/esm/server-telemetry.spec.d.ts +2 -0
- package/esm/server-telemetry.spec.d.ts.map +1 -0
- package/esm/server-telemetry.spec.js +52 -0
- package/esm/server-telemetry.spec.js.map +1 -0
- package/esm/static-server-manager.spec.js +60 -125
- package/esm/static-server-manager.spec.js.map +1 -1
- package/esm/static-site-runtime.d.ts +21 -0
- package/esm/static-site-runtime.d.ts.map +1 -0
- package/esm/static-site-runtime.js +48 -0
- package/esm/static-site-runtime.js.map +1 -0
- package/esm/static-site-runtime.spec.d.ts +2 -0
- package/esm/static-site-runtime.spec.d.ts.map +1 -0
- package/esm/static-site-runtime.spec.js +30 -0
- package/esm/static-site-runtime.spec.js.map +1 -0
- package/esm/user-resolution-cache.d.ts +65 -0
- package/esm/user-resolution-cache.d.ts.map +1 -0
- package/esm/user-resolution-cache.js +101 -0
- package/esm/user-resolution-cache.js.map +1 -0
- package/esm/user-resolution-cache.spec.d.ts +2 -0
- package/esm/user-resolution-cache.spec.d.ts.map +1 -0
- package/esm/user-resolution-cache.spec.js +220 -0
- package/esm/user-resolution-cache.spec.js.map +1 -0
- package/esm/user-store.d.ts +39 -0
- package/esm/user-store.d.ts.map +1 -0
- package/esm/user-store.js +62 -0
- package/esm/user-store.js.map +1 -0
- package/esm/user-store.spec.d.ts +2 -0
- package/esm/user-store.spec.d.ts.map +1 -0
- package/esm/user-store.spec.js +72 -0
- package/esm/user-store.spec.js.map +1 -0
- package/esm/validate.d.ts +13 -12
- package/esm/validate.d.ts.map +1 -1
- package/esm/validate.integration.spec.js +27 -28
- package/esm/validate.integration.spec.js.map +1 -1
- package/esm/validate.js +12 -0
- package/esm/validate.js.map +1 -1
- package/package.json +14 -12
- package/src/actions/error-action.ts +7 -3
- package/src/actions/get-current-user.spec.ts +11 -10
- package/src/actions/get-current-user.ts +1 -4
- package/src/actions/is-authenticated.spec.ts +12 -7
- package/src/actions/is-authenticated.ts +3 -4
- package/src/actions/logout-action.spec.ts +12 -12
- package/src/actions/logout.ts +3 -7
- package/src/actions/not-found-action.ts +1 -3
- package/src/actions/password-login-action.spec.ts +30 -30
- package/src/actions/password-login-action.ts +6 -13
- package/src/authenticate.spec.ts +45 -51
- package/src/authenticate.ts +6 -12
- package/src/authentication-providers/authentication-provider.ts +17 -0
- package/src/authentication-providers/basic-auth-provider.ts +7 -0
- package/src/authentication-providers/cookie-auth-provider.ts +8 -0
- package/src/authorize.spec.ts +37 -39
- package/src/authorize.ts +6 -17
- package/src/endpoint-generators/create-delete-endpoint.spec.ts +16 -24
- package/src/endpoint-generators/create-delete-endpoint.ts +11 -17
- package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +45 -78
- package/src/endpoint-generators/create-get-collection-endpoint.ts +17 -19
- package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +17 -33
- package/src/endpoint-generators/create-get-entity-endpoint.ts +11 -17
- package/src/endpoint-generators/create-get-openapi-document-action.ts +2 -2
- package/src/endpoint-generators/create-get-schema-action.ts +1 -1
- package/src/endpoint-generators/create-patch-endpoint.spec.ts +11 -18
- package/src/endpoint-generators/create-patch-endpoint.ts +12 -23
- package/src/endpoint-generators/create-post-endpoint.spec.ts +45 -14
- package/src/endpoint-generators/create-post-endpoint.ts +13 -23
- package/src/endpoint-generators/utils.ts +36 -28
- package/src/get-schema-from-api.spec.ts +1 -1
- package/src/get-schema-from-api.ts +2 -1
- package/src/helpers.spec.ts +51 -41
- package/src/helpers.ts +140 -109
- package/src/http-authentication-settings.ts +78 -24
- package/src/http-server-pool.spec.ts +220 -0
- package/src/http-server-pool.ts +169 -0
- package/src/http-user-context.spec.ts +246 -378
- package/src/http-user-context.ts +122 -92
- package/src/identity-cache-keys.spec.ts +24 -0
- package/src/identity-cache-keys.ts +31 -0
- package/src/identity-event-bus.spec.ts +285 -0
- package/src/identity-event-bus.ts +230 -0
- package/src/index.ts +9 -4
- package/src/login-response-strategy.spec.ts +32 -31
- package/src/login-response-strategy.ts +14 -12
- package/src/openapi/generate-openapi-document.spec.ts +2 -2
- package/src/proxy-manager.spec.ts +158 -188
- package/src/proxy-runtime.spec.ts +50 -0
- package/src/proxy-runtime.ts +75 -0
- package/src/request-action-implementation.ts +25 -0
- package/src/rest-api-runtime.spec.ts +77 -0
- package/src/rest-api-runtime.ts +204 -0
- package/src/rest-service.integration.spec.ts +65 -85
- package/src/rest.integration.test.ts +27 -87
- package/src/server-telemetry.spec.ts +56 -0
- package/src/server-telemetry.ts +43 -0
- package/src/static-server-manager.spec.ts +63 -151
- package/src/static-site-runtime.spec.ts +39 -0
- package/src/static-site-runtime.ts +75 -0
- package/src/user-resolution-cache.spec.ts +241 -0
- package/src/user-resolution-cache.ts +164 -0
- package/src/user-store.spec.ts +86 -0
- package/src/user-store.ts +69 -0
- package/src/validate.integration.spec.ts +40 -35
- package/src/validate.ts +14 -12
- package/esm/api-manager.d.ts +0 -104
- package/esm/api-manager.d.ts.map +0 -1
- package/esm/api-manager.js +0 -179
- package/esm/api-manager.js.map +0 -1
- package/esm/proxy-manager.d.ts +0 -53
- package/esm/proxy-manager.d.ts.map +0 -1
- package/esm/proxy-manager.js +0 -84
- package/esm/proxy-manager.js.map +0 -1
- package/esm/server-manager.d.ts +0 -48
- package/esm/server-manager.d.ts.map +0 -1
- package/esm/server-manager.js +0 -100
- package/esm/server-manager.js.map +0 -1
- package/esm/static-server-manager.d.ts +0 -19
- package/esm/static-server-manager.d.ts.map +0 -1
- package/esm/static-server-manager.js +0 -65
- package/esm/static-server-manager.js.map +0 -1
- package/src/api-manager.ts +0 -304
- package/src/proxy-manager.ts +0 -102
- package/src/server-manager.ts +0 -135
- package/src/static-server-manager.ts +0 -90
|
@@ -1,233 +1,192 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getDataSetFor, getRepository } from '@furystack/repository';
|
|
5
|
-
import { PasswordAuthenticator, PasswordCredential, PasswordResetToken, UnauthenticatedError, usePasswordPolicy, } from '@furystack/security';
|
|
1
|
+
import { InMemoryStore, User as UserModel, useSystemIdentityContext } from '@furystack/core';
|
|
2
|
+
import { createInjector } from '@furystack/inject';
|
|
3
|
+
import { PasswordAuthenticator, PasswordCredential, PasswordCredentialStore, PasswordResetToken, PasswordResetTokenStore, UnauthenticatedError, usePasswordPolicy, } from '@furystack/security';
|
|
6
4
|
import { usingAsync } from '@furystack/utils';
|
|
7
5
|
import { describe, expect, it, vi } from 'vitest';
|
|
8
6
|
import { useHttpAuthentication } from './helpers.js';
|
|
9
7
|
import { HttpUserContext } from './http-user-context.js';
|
|
10
8
|
import { DefaultSession } from './models/default-session.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
import { SessionDataSet, SessionStore, UserStore } from './user-store.js';
|
|
10
|
+
/**
|
|
11
|
+
* Binds in-memory implementations for every persistent token required by
|
|
12
|
+
* the authentication subsystem (users, sessions, credentials, reset tokens)
|
|
13
|
+
* on the provided injector, then installs the default password policy and
|
|
14
|
+
* HTTP-authentication stack.
|
|
15
|
+
*/
|
|
16
|
+
export const prepareInjector = (i, options) => {
|
|
17
|
+
i.bind(UserStore, () => new InMemoryStore({ model: UserModel, primaryKey: 'username' }));
|
|
18
|
+
i.bind(SessionStore, () => new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }));
|
|
19
|
+
i.bind(PasswordCredentialStore, () => new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' }));
|
|
20
|
+
i.bind(PasswordResetTokenStore, () => new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }));
|
|
21
21
|
usePasswordPolicy(i);
|
|
22
22
|
useHttpAuthentication(i, { enableBasicAuth: options?.enableBasicAuth ?? true });
|
|
23
23
|
};
|
|
24
24
|
const setupUser = async (i, userName, password) => {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
await
|
|
29
|
-
await sm.getStoreFor(User, 'username').add({ username: userName, roles: [] });
|
|
25
|
+
const authenticator = i.get(PasswordAuthenticator);
|
|
26
|
+
const credential = await authenticator.hasher.createCredential(userName, password);
|
|
27
|
+
await i.get(PasswordCredentialStore).add(credential);
|
|
28
|
+
await i.get(UserStore).add({ username: userName, roles: [] });
|
|
30
29
|
};
|
|
31
30
|
describe('HttpUserContext', () => {
|
|
32
31
|
const request = { headers: {} };
|
|
33
|
-
const response = {};
|
|
34
32
|
const testUser = { username: 'testUser', roles: ['grantedRole1', 'grantedRole2'] };
|
|
35
|
-
it('
|
|
36
|
-
await usingAsync(
|
|
37
|
-
|
|
38
|
-
const ctx = i.
|
|
39
|
-
expect(ctx).
|
|
33
|
+
it('resolves to an HttpUserContext via the DI token', async () => {
|
|
34
|
+
await usingAsync(createInjector(), async (i) => {
|
|
35
|
+
prepareInjector(i);
|
|
36
|
+
const ctx = i.get(HttpUserContext);
|
|
37
|
+
expect(ctx).toBeDefined();
|
|
38
|
+
expect(ctx.authentication).toBeDefined();
|
|
40
39
|
});
|
|
41
40
|
});
|
|
42
41
|
describe('isAuthenticated', () => {
|
|
43
|
-
it('
|
|
44
|
-
await usingAsync(
|
|
45
|
-
|
|
46
|
-
const ctx = i.
|
|
42
|
+
it('returns true when the cached user is present', async () => {
|
|
43
|
+
await usingAsync(createInjector(), async (i) => {
|
|
44
|
+
prepareInjector(i);
|
|
45
|
+
const ctx = i.get(HttpUserContext);
|
|
47
46
|
ctx.getCurrentUser = vi.fn(async () => testUser);
|
|
48
|
-
|
|
49
|
-
expect(
|
|
50
|
-
expect(ctx.getCurrentUser).toBeCalled();
|
|
47
|
+
expect(await ctx.isAuthenticated(request)).toBe(true);
|
|
48
|
+
expect(ctx.getCurrentUser).toHaveBeenCalled();
|
|
51
49
|
});
|
|
52
50
|
});
|
|
53
|
-
it('
|
|
54
|
-
await usingAsync(
|
|
55
|
-
|
|
56
|
-
const ctx = i.
|
|
51
|
+
it('returns false when getCurrentUser rejects', async () => {
|
|
52
|
+
await usingAsync(createInjector(), async (i) => {
|
|
53
|
+
prepareInjector(i);
|
|
54
|
+
const ctx = i.get(HttpUserContext);
|
|
57
55
|
ctx.getCurrentUser = vi.fn(async () => {
|
|
58
|
-
throw Error(':(');
|
|
56
|
+
throw new Error(':(');
|
|
59
57
|
});
|
|
60
|
-
await
|
|
61
|
-
expect(ctx.getCurrentUser).toBeCalled();
|
|
58
|
+
expect(await ctx.isAuthenticated(request)).toBe(false);
|
|
62
59
|
});
|
|
63
60
|
});
|
|
64
61
|
});
|
|
65
62
|
describe('isAuthorized', () => {
|
|
66
|
-
it('
|
|
67
|
-
await usingAsync(
|
|
68
|
-
|
|
69
|
-
const ctx = i.
|
|
63
|
+
it('returns true when the current user has every requested role', async () => {
|
|
64
|
+
await usingAsync(createInjector(), async (i) => {
|
|
65
|
+
prepareInjector(i);
|
|
66
|
+
const ctx = i.get(HttpUserContext);
|
|
70
67
|
ctx.getCurrentUser = vi.fn(async () => testUser);
|
|
71
|
-
|
|
72
|
-
expect(value).toBe(true);
|
|
73
|
-
expect(ctx.getCurrentUser).toBeCalled();
|
|
68
|
+
expect(await ctx.isAuthorized(request, 'grantedRole1', 'grantedRole2')).toBe(true);
|
|
74
69
|
});
|
|
75
70
|
});
|
|
76
|
-
it('
|
|
77
|
-
await usingAsync(
|
|
78
|
-
|
|
79
|
-
const ctx = i.
|
|
71
|
+
it('returns false when any requested role is missing', async () => {
|
|
72
|
+
await usingAsync(createInjector(), async (i) => {
|
|
73
|
+
prepareInjector(i);
|
|
74
|
+
const ctx = i.get(HttpUserContext);
|
|
80
75
|
ctx.getCurrentUser = vi.fn(async () => testUser);
|
|
81
|
-
|
|
82
|
-
expect(value).toBe(false);
|
|
83
|
-
expect(ctx.getCurrentUser).toBeCalled();
|
|
76
|
+
expect(await ctx.isAuthorized(request, 'grantedRole1', 'otherRole')).toBe(false);
|
|
84
77
|
});
|
|
85
78
|
});
|
|
86
79
|
});
|
|
87
80
|
describe('authenticateUser', () => {
|
|
88
|
-
it('
|
|
89
|
-
await usingAsync(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await expect(ctx.authenticateUser('user', 'password')).rejects.toThrowError(UnauthenticatedError);
|
|
81
|
+
it('throws UnauthenticatedError when the credential store is empty', async () => {
|
|
82
|
+
await usingAsync(createInjector(), async (i) => {
|
|
83
|
+
prepareInjector(i);
|
|
84
|
+
await expect(i.get(HttpUserContext).authenticateUser('user', 'password')).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
93
85
|
});
|
|
94
86
|
});
|
|
95
|
-
it('
|
|
96
|
-
await usingAsync(
|
|
97
|
-
|
|
87
|
+
it('throws UnauthenticatedError when the password does not match', async () => {
|
|
88
|
+
await usingAsync(createInjector(), async (i) => {
|
|
89
|
+
prepareInjector(i);
|
|
98
90
|
await setupUser(i, 'user', 'pass123');
|
|
99
|
-
await expect(i.
|
|
91
|
+
await expect(i.get(HttpUserContext).authenticateUser('user', 'wrong')).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
100
92
|
});
|
|
101
93
|
});
|
|
102
|
-
it('
|
|
103
|
-
await usingAsync(
|
|
104
|
-
|
|
94
|
+
it('throws UnauthenticatedError for an unknown username', async () => {
|
|
95
|
+
await usingAsync(createInjector(), async (i) => {
|
|
96
|
+
prepareInjector(i);
|
|
105
97
|
await setupUser(i, 'otherUser', 'pass123');
|
|
106
|
-
await expect(i.
|
|
98
|
+
await expect(i.get(HttpUserContext).authenticateUser('user', 'pass123')).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
107
99
|
});
|
|
108
100
|
});
|
|
109
|
-
it('
|
|
110
|
-
await usingAsync(
|
|
111
|
-
|
|
101
|
+
it('throws UnauthenticatedError when the user exists only as a credential', async () => {
|
|
102
|
+
await usingAsync(createInjector(), async (i) => {
|
|
103
|
+
prepareInjector(i);
|
|
112
104
|
await setupUser(i, 'user', 'pass123');
|
|
113
|
-
await
|
|
105
|
+
await i.get(UserStore).remove('user');
|
|
106
|
+
await expect(i.get(HttpUserContext).authenticateUser('user', 'pass123')).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
114
107
|
});
|
|
115
108
|
});
|
|
116
|
-
it('
|
|
117
|
-
await usingAsync(
|
|
118
|
-
|
|
109
|
+
it('returns the user on valid credentials', async () => {
|
|
110
|
+
await usingAsync(createInjector(), async (i) => {
|
|
111
|
+
prepareInjector(i);
|
|
119
112
|
await setupUser(i, 'user', 'pass123');
|
|
120
|
-
await i.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
it('Should return the user when the username and password matches', async () => {
|
|
125
|
-
await usingAsync(new Injector(), async (i) => {
|
|
126
|
-
await prepareInjector(i);
|
|
127
|
-
await setupUser(i, 'user', 'pass123');
|
|
128
|
-
const ctx = i.getInstance(HttpUserContext);
|
|
129
|
-
const value = await ctx.authenticateUser('user', 'pass123');
|
|
130
|
-
expect(value).toEqual({ username: 'user', roles: [] });
|
|
113
|
+
expect(await i.get(HttpUserContext).authenticateUser('user', 'pass123')).toEqual({
|
|
114
|
+
username: 'user',
|
|
115
|
+
roles: [],
|
|
116
|
+
});
|
|
131
117
|
});
|
|
132
118
|
});
|
|
133
119
|
});
|
|
134
120
|
describe('getSessionIdFromRequest', () => {
|
|
135
|
-
it('
|
|
136
|
-
await usingAsync(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const sid = ctx.getSessionIdFromRequest(request);
|
|
140
|
-
expect(sid).toBeNull();
|
|
121
|
+
it('returns null when no headers are present', async () => {
|
|
122
|
+
await usingAsync(createInjector(), async (i) => {
|
|
123
|
+
prepareInjector(i);
|
|
124
|
+
expect(i.get(HttpUserContext).getSessionIdFromRequest(request)).toBeNull();
|
|
141
125
|
});
|
|
142
126
|
});
|
|
143
|
-
it('
|
|
144
|
-
await usingAsync(
|
|
145
|
-
|
|
146
|
-
const requestWithCookie = {
|
|
147
|
-
|
|
148
|
-
const sid = ctx.getSessionIdFromRequest(requestWithCookie);
|
|
149
|
-
expect(sid).toBeNull();
|
|
127
|
+
it('returns null when no session cookie is present', async () => {
|
|
128
|
+
await usingAsync(createInjector(), async (i) => {
|
|
129
|
+
prepareInjector(i);
|
|
130
|
+
const requestWithCookie = { headers: { cookie: 'a=2;b=3;' } };
|
|
131
|
+
expect(i.get(HttpUserContext).getSessionIdFromRequest(requestWithCookie)).toBeNull();
|
|
150
132
|
});
|
|
151
133
|
});
|
|
152
|
-
it('
|
|
153
|
-
await usingAsync(
|
|
154
|
-
|
|
155
|
-
const ctx = i.
|
|
134
|
+
it('returns the session id when the configured cookie is present', async () => {
|
|
135
|
+
await usingAsync(createInjector(), async (i) => {
|
|
136
|
+
prepareInjector(i);
|
|
137
|
+
const ctx = i.get(HttpUserContext);
|
|
156
138
|
const requestWithAuthCookie = {
|
|
157
|
-
...request,
|
|
158
139
|
headers: { cookie: `a=2;b=3;${ctx.authentication.cookieName}=666;c=4;` },
|
|
159
140
|
};
|
|
160
|
-
|
|
161
|
-
expect(sid).toBe('666');
|
|
141
|
+
expect(ctx.getSessionIdFromRequest(requestWithAuthCookie)).toBe('666');
|
|
162
142
|
});
|
|
163
143
|
});
|
|
164
144
|
});
|
|
165
145
|
describe('authenticateRequest', () => {
|
|
166
|
-
it('
|
|
167
|
-
await usingAsync(
|
|
168
|
-
|
|
146
|
+
it('authenticates with Basic Auth when enabled and credentials are valid', async () => {
|
|
147
|
+
await usingAsync(createInjector(), async (i) => {
|
|
148
|
+
prepareInjector(i);
|
|
169
149
|
await setupUser(i, 'testuser', 'password');
|
|
170
|
-
const
|
|
171
|
-
const result = await ctx.authenticateRequest({
|
|
150
|
+
const result = await i.get(HttpUserContext).authenticateRequest({
|
|
172
151
|
headers: { authorization: `Basic dGVzdHVzZXI6cGFzc3dvcmQ=` },
|
|
173
152
|
});
|
|
174
153
|
expect(result.username).toBe('testuser');
|
|
175
154
|
});
|
|
176
155
|
});
|
|
177
|
-
it('
|
|
178
|
-
await usingAsync(
|
|
179
|
-
|
|
156
|
+
it('does not attempt Basic Auth when it is disabled', async () => {
|
|
157
|
+
await usingAsync(createInjector(), async (i) => {
|
|
158
|
+
prepareInjector(i, { enableBasicAuth: false });
|
|
180
159
|
await setupUser(i, 'testuser', 'password');
|
|
181
|
-
|
|
182
|
-
await expect(ctx.authenticateRequest({
|
|
160
|
+
await expect(i.get(HttpUserContext).authenticateRequest({
|
|
183
161
|
headers: { authorization: `Basic dGVzdHVzZXI6cGFzc3dvcmQ=` },
|
|
184
|
-
})).rejects.
|
|
162
|
+
})).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
185
163
|
});
|
|
186
164
|
});
|
|
187
|
-
it('
|
|
188
|
-
await usingAsync(
|
|
189
|
-
|
|
190
|
-
const ctx = i.
|
|
165
|
+
it('throws when the cookie session id does not exist', async () => {
|
|
166
|
+
await usingAsync(createInjector(), async (i) => {
|
|
167
|
+
prepareInjector(i);
|
|
168
|
+
const ctx = i.get(HttpUserContext);
|
|
191
169
|
await expect(ctx.authenticateRequest({
|
|
192
|
-
headers: { cookie: `${ctx.authentication.cookieName}=666
|
|
193
|
-
})).rejects.
|
|
170
|
+
headers: { cookie: `${ctx.authentication.cookieName}=666;` },
|
|
171
|
+
})).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
194
172
|
});
|
|
195
173
|
});
|
|
196
|
-
it('
|
|
197
|
-
await usingAsync(
|
|
198
|
-
|
|
199
|
-
const ctx = i.
|
|
200
|
-
await i.
|
|
201
|
-
|
|
202
|
-
username: testUser.username,
|
|
203
|
-
});
|
|
204
|
-
await expect(ctx.authenticateRequest({
|
|
205
|
-
headers: { cookie: `${ctx.authentication.cookieName}=666;a=3` },
|
|
206
|
-
})).rejects.toThrowError(UnauthenticatedError);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
it('Should authenticate with cookie, if the session IDs matches', async () => {
|
|
210
|
-
await usingAsync(new Injector(), async (i) => {
|
|
211
|
-
await prepareInjector(i);
|
|
212
|
-
const ctx = i.getInstance(HttpUserContext);
|
|
213
|
-
await i.getInstance(StoreManager).getStoreFor(DefaultSession, 'sessionId').add({
|
|
214
|
-
sessionId: '666',
|
|
215
|
-
username: testUser.username,
|
|
216
|
-
});
|
|
217
|
-
await i
|
|
218
|
-
.getInstance(StoreManager)
|
|
219
|
-
.getStoreFor(User, 'username')
|
|
220
|
-
.add({ ...testUser });
|
|
174
|
+
it('authenticates via cookie when the session id matches a stored user', async () => {
|
|
175
|
+
await usingAsync(createInjector(), async (i) => {
|
|
176
|
+
prepareInjector(i);
|
|
177
|
+
const ctx = i.get(HttpUserContext);
|
|
178
|
+
await i.get(SessionStore).add({ sessionId: '666', username: testUser.username });
|
|
179
|
+
await i.get(UserStore).add({ ...testUser });
|
|
221
180
|
const result = await ctx.authenticateRequest({
|
|
222
|
-
headers: { cookie: `${ctx.authentication.cookieName}=666
|
|
181
|
+
headers: { cookie: `${ctx.authentication.cookieName}=666;` },
|
|
223
182
|
});
|
|
224
183
|
expect(result).toEqual(testUser);
|
|
225
184
|
});
|
|
226
185
|
});
|
|
227
|
-
it('
|
|
228
|
-
await usingAsync(
|
|
229
|
-
|
|
230
|
-
const ctx = i.
|
|
186
|
+
it('walks providers in order and stops at the first match', async () => {
|
|
187
|
+
await usingAsync(createInjector(), async (i) => {
|
|
188
|
+
prepareInjector(i);
|
|
189
|
+
const ctx = i.get(HttpUserContext);
|
|
231
190
|
const provider1 = vi.fn(async () => null);
|
|
232
191
|
const provider2 = vi.fn(async () => testUser);
|
|
233
192
|
ctx.authentication.authenticationProviders = [
|
|
@@ -240,10 +199,10 @@ describe('HttpUserContext', () => {
|
|
|
240
199
|
expect(result).toBe(testUser);
|
|
241
200
|
});
|
|
242
201
|
});
|
|
243
|
-
it('
|
|
244
|
-
await usingAsync(
|
|
245
|
-
|
|
246
|
-
const ctx = i.
|
|
202
|
+
it('propagates a provider-thrown error and skips remaining providers', async () => {
|
|
203
|
+
await usingAsync(createInjector(), async (i) => {
|
|
204
|
+
prepareInjector(i);
|
|
205
|
+
const ctx = i.get(HttpUserContext);
|
|
247
206
|
const provider1 = vi.fn(async () => {
|
|
248
207
|
throw new UnauthenticatedError();
|
|
249
208
|
});
|
|
@@ -252,210 +211,145 @@ describe('HttpUserContext', () => {
|
|
|
252
211
|
{ name: 'test-1', authenticate: provider1 },
|
|
253
212
|
{ name: 'test-2', authenticate: provider2 },
|
|
254
213
|
];
|
|
255
|
-
await expect(ctx.authenticateRequest(request)).rejects.
|
|
214
|
+
await expect(ctx.authenticateRequest(request)).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
256
215
|
expect(provider2).not.toHaveBeenCalled();
|
|
257
216
|
});
|
|
258
217
|
});
|
|
259
|
-
it('
|
|
260
|
-
await usingAsync(
|
|
261
|
-
|
|
262
|
-
const ctx = i.
|
|
218
|
+
it('throws UnauthenticatedError when no provider returns a user', async () => {
|
|
219
|
+
await usingAsync(createInjector(), async (i) => {
|
|
220
|
+
prepareInjector(i);
|
|
221
|
+
const ctx = i.get(HttpUserContext);
|
|
263
222
|
ctx.authentication.authenticationProviders = [{ name: 'test-1', authenticate: async () => null }];
|
|
264
|
-
await expect(ctx.authenticateRequest(request)).rejects.
|
|
223
|
+
await expect(ctx.authenticateRequest(request)).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
265
224
|
});
|
|
266
225
|
});
|
|
267
226
|
});
|
|
268
227
|
describe('getCurrentUser', () => {
|
|
269
|
-
it('
|
|
270
|
-
await usingAsync(
|
|
271
|
-
|
|
272
|
-
const ctx = i.
|
|
228
|
+
it('caches the result of authenticateRequest by session id when the cookie provider yields a key', async () => {
|
|
229
|
+
await usingAsync(createInjector(), async (i) => {
|
|
230
|
+
prepareInjector(i);
|
|
231
|
+
const ctx = i.get(HttpUserContext);
|
|
232
|
+
const cookiedRequest = {
|
|
233
|
+
headers: { cookie: `${ctx.authentication.cookieName}=session-1;` },
|
|
234
|
+
};
|
|
273
235
|
ctx.authenticateRequest = vi.fn(async () => testUser);
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
expect(ctx.authenticateRequest).
|
|
277
|
-
expect(
|
|
278
|
-
expect(
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
describe('cookieLogin', () => {
|
|
283
|
-
it('Should return the current user from authenticateRequest() once per request', async () => {
|
|
284
|
-
await usingAsync(new Injector(), async (i) => {
|
|
285
|
-
await prepareInjector(i);
|
|
286
|
-
const ctx = i.getInstance(HttpUserContext);
|
|
287
|
-
const setHeader = vi.fn();
|
|
288
|
-
const addMock = vi.fn(async () => ({}));
|
|
289
|
-
// @ts-expect-error
|
|
290
|
-
ctx.getSessionDataSet = vi.fn(() => ({ add: addMock }));
|
|
291
|
-
const authResult = await ctx.cookieLogin(testUser, { setHeader });
|
|
292
|
-
expect(authResult).toBe(testUser);
|
|
293
|
-
expect(setHeader).toBeCalled();
|
|
294
|
-
expect(addMock).toBeCalled();
|
|
236
|
+
const first = await ctx.getCurrentUser(cookiedRequest);
|
|
237
|
+
const second = await ctx.getCurrentUser(cookiedRequest);
|
|
238
|
+
expect(ctx.authenticateRequest).toHaveBeenCalledTimes(1);
|
|
239
|
+
expect(first).toBe(testUser);
|
|
240
|
+
expect(second).toBe(testUser);
|
|
295
241
|
});
|
|
296
242
|
});
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
const removeMock = vi.fn(async () => undefined);
|
|
305
|
-
const sessionDataSetMock = {
|
|
306
|
-
add: vi.fn(async () => ({})),
|
|
307
|
-
find: vi.fn(async () => [{ sessionId: 'example-session-id' }]),
|
|
308
|
-
remove: removeMock,
|
|
309
|
-
primaryKey: 'sessionId',
|
|
243
|
+
it('treats distinct sessions as distinct cache entries', async () => {
|
|
244
|
+
await usingAsync(createInjector(), async (i) => {
|
|
245
|
+
prepareInjector(i);
|
|
246
|
+
const ctx = i.get(HttpUserContext);
|
|
247
|
+
const otherUser = { username: 'otherUser', roles: [] };
|
|
248
|
+
const requestA = {
|
|
249
|
+
headers: { cookie: `${ctx.authentication.cookieName}=session-a;` },
|
|
310
250
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
expect(
|
|
251
|
+
const requestB = {
|
|
252
|
+
headers: { cookie: `${ctx.authentication.cookieName}=session-b;` },
|
|
253
|
+
};
|
|
254
|
+
const authenticateRequest = vi
|
|
255
|
+
.fn()
|
|
256
|
+
.mockResolvedValueOnce(testUser)
|
|
257
|
+
.mockResolvedValueOnce(otherUser);
|
|
258
|
+
ctx.authenticateRequest = authenticateRequest;
|
|
259
|
+
expect(await ctx.getCurrentUser(requestA)).toBe(testUser);
|
|
260
|
+
expect(await ctx.getCurrentUser(requestB)).toBe(otherUser);
|
|
261
|
+
expect(await ctx.getCurrentUser(requestA)).toBe(testUser);
|
|
262
|
+
expect(authenticateRequest).toHaveBeenCalledTimes(2);
|
|
320
263
|
});
|
|
321
264
|
});
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
ctx.
|
|
333
|
-
|
|
334
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
335
|
-
expect(handler).toHaveBeenCalledWith({ user: testUser });
|
|
265
|
+
it('bypasses the cache when no provider yields a key (e.g. Basic Auth, anonymous)', async () => {
|
|
266
|
+
await usingAsync(createInjector(), async (i) => {
|
|
267
|
+
prepareInjector(i);
|
|
268
|
+
const ctx = i.get(HttpUserContext);
|
|
269
|
+
const anonRequest = { headers: {} };
|
|
270
|
+
const authenticateRequest = vi
|
|
271
|
+
.fn()
|
|
272
|
+
.mockResolvedValue(testUser);
|
|
273
|
+
ctx.authenticateRequest = authenticateRequest;
|
|
274
|
+
await ctx.getCurrentUser(anonRequest);
|
|
275
|
+
await ctx.getCurrentUser(anonRequest);
|
|
276
|
+
expect(authenticateRequest).toHaveBeenCalledTimes(2);
|
|
336
277
|
});
|
|
337
278
|
});
|
|
338
|
-
it('
|
|
339
|
-
await usingAsync(
|
|
340
|
-
|
|
341
|
-
const ctx = i.
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
add: vi.fn(async () => ({})),
|
|
346
|
-
find: vi.fn(async () => [{ sessionId: 'sid' }]),
|
|
347
|
-
remove: vi.fn(async () => undefined),
|
|
348
|
-
primaryKey: 'sessionId',
|
|
279
|
+
it('drops the local cache entry on cookieLogout so the next request re-authenticates', async () => {
|
|
280
|
+
await usingAsync(createInjector(), async (i) => {
|
|
281
|
+
prepareInjector(i);
|
|
282
|
+
const ctx = i.get(HttpUserContext);
|
|
283
|
+
const sessionId = 'session-logout';
|
|
284
|
+
const cookiedRequest = {
|
|
285
|
+
headers: { cookie: `${ctx.authentication.cookieName}=${sessionId};` },
|
|
349
286
|
};
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
287
|
+
await i.get(SessionStore).add({ sessionId, username: testUser.username });
|
|
288
|
+
await i.get(UserStore).add(testUser);
|
|
289
|
+
const authenticateRequest = vi
|
|
290
|
+
.fn()
|
|
291
|
+
.mockResolvedValue(testUser);
|
|
292
|
+
ctx.authenticateRequest = authenticateRequest;
|
|
293
|
+
await ctx.getCurrentUser(cookiedRequest);
|
|
294
|
+
expect(authenticateRequest).toHaveBeenCalledTimes(1);
|
|
295
|
+
await ctx.cookieLogout(cookiedRequest, { setHeader: vi.fn() });
|
|
296
|
+
await ctx.getCurrentUser(cookiedRequest);
|
|
297
|
+
expect(authenticateRequest).toHaveBeenCalledTimes(2);
|
|
356
298
|
});
|
|
357
299
|
});
|
|
358
|
-
it('
|
|
359
|
-
await usingAsync(
|
|
360
|
-
|
|
361
|
-
const ctx = i.
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
ctx.
|
|
368
|
-
await ctx.
|
|
369
|
-
|
|
370
|
-
const userDataSet = getDataSetFor(systemInjector, User, 'username');
|
|
371
|
-
await userDataSet.remove(systemInjector, testUser.username);
|
|
372
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
it('Should emit onSessionInvalidated when the session entity is removed', async () => {
|
|
376
|
-
await usingAsync(new Injector(), async (i) => {
|
|
377
|
-
await prepareInjector(i);
|
|
378
|
-
const ctx = i.getInstance(HttpUserContext);
|
|
379
|
-
const sm = i.getInstance(StoreManager);
|
|
380
|
-
await sm.getStoreFor(User, 'username').add(testUser);
|
|
381
|
-
const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test');
|
|
382
|
-
await sm.getStoreFor(PasswordCredential, 'userName').add(pw);
|
|
383
|
-
const handler = vi.fn();
|
|
384
|
-
ctx.addListener('onSessionInvalidated', handler);
|
|
385
|
-
let sessionId = '';
|
|
386
|
-
await ctx.cookieLogin(testUser, {
|
|
387
|
-
setHeader: (_headerName, headerValue) => {
|
|
388
|
-
sessionId = headerValue.split('=')[1].split(';')[0];
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
const systemInjector = useSystemIdentityContext({ injector: i, username: 'test' });
|
|
392
|
-
const sessionDataSet = getDataSetFor(systemInjector, DefaultSession, 'sessionId');
|
|
393
|
-
await sessionDataSet.remove(systemInjector, sessionId);
|
|
394
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
300
|
+
it('re-walks providers after a failed authenticateRequest', async () => {
|
|
301
|
+
await usingAsync(createInjector(), async (i) => {
|
|
302
|
+
prepareInjector(i);
|
|
303
|
+
const ctx = i.get(HttpUserContext);
|
|
304
|
+
const authenticateRequest = vi
|
|
305
|
+
.fn()
|
|
306
|
+
.mockRejectedValueOnce(new UnauthenticatedError())
|
|
307
|
+
.mockResolvedValueOnce(testUser);
|
|
308
|
+
ctx.authenticateRequest = authenticateRequest;
|
|
309
|
+
await expect(ctx.getCurrentUser(request)).rejects.toBeInstanceOf(UnauthenticatedError);
|
|
310
|
+
expect(await ctx.getCurrentUser(request)).toBe(testUser);
|
|
311
|
+
expect(authenticateRequest).toHaveBeenCalledTimes(2);
|
|
395
312
|
});
|
|
396
313
|
});
|
|
397
314
|
});
|
|
398
|
-
describe('
|
|
399
|
-
it('
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const ctx = i.
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
await
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
expect(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const updatedUserFromContext = await ctx.getCurrentUser(request);
|
|
415
|
-
expect(updatedUserFromContext.roles).toEqual(['newFancyRole']);
|
|
416
|
-
await userDataSet.update(systemInjector, testUser.username, { ...updatedUser, roles: [] });
|
|
417
|
-
const reloadedUserFromContext = await ctx.getCurrentUser(request);
|
|
418
|
-
expect(reloadedUserFromContext.roles).toEqual([]);
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
it('Should remove current user when the user is removed from the store', () => {
|
|
422
|
-
return usingAsync(new Injector(), async (i) => {
|
|
423
|
-
await prepareInjector(i);
|
|
424
|
-
const ctx = i.getInstance(HttpUserContext);
|
|
425
|
-
const sm = i.getInstance(StoreManager);
|
|
426
|
-
await sm.getStoreFor(User, 'username').add(testUser);
|
|
427
|
-
const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test');
|
|
428
|
-
await sm.getStoreFor(PasswordCredential, 'userName').add(pw);
|
|
429
|
-
await ctx.cookieLogin(testUser, { setHeader: vi.fn() });
|
|
430
|
-
const originalUser = await ctx.getCurrentUser(request);
|
|
431
|
-
expect(originalUser).toEqual(testUser);
|
|
432
|
-
const systemInjector = useSystemIdentityContext({ injector: i, username: 'test' });
|
|
433
|
-
const userDataSet = getDataSetFor(systemInjector, User, 'username');
|
|
434
|
-
await userDataSet.remove(systemInjector, testUser.username);
|
|
435
|
-
await expect(() => ctx.getCurrentUser(request)).rejects.toThrowError(UnauthenticatedError);
|
|
315
|
+
describe('cookieLogin / cookieLogout', () => {
|
|
316
|
+
it('persists the session, sets the cookie and emits onLogin', async () => {
|
|
317
|
+
await usingAsync(createInjector(), async (i) => {
|
|
318
|
+
prepareInjector(i);
|
|
319
|
+
const ctx = i.get(HttpUserContext);
|
|
320
|
+
const setHeader = vi.fn();
|
|
321
|
+
const onLogin = vi.fn();
|
|
322
|
+
ctx.addListener('onLogin', onLogin);
|
|
323
|
+
const result = await ctx.cookieLogin(testUser, { setHeader });
|
|
324
|
+
expect(result).toBe(testUser);
|
|
325
|
+
expect(setHeader).toHaveBeenCalledTimes(1);
|
|
326
|
+
expect(onLogin).toHaveBeenCalledWith({ user: testUser });
|
|
327
|
+
await usingAsync(useSystemIdentityContext({ injector: i, username: 'spec' }), async (systemScope) => {
|
|
328
|
+
const sessionDataSet = systemScope.get(SessionDataSet);
|
|
329
|
+
expect(await sessionDataSet.count(systemScope)).toBe(1);
|
|
330
|
+
});
|
|
436
331
|
});
|
|
437
332
|
});
|
|
438
|
-
it('
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const ctx = i.
|
|
442
|
-
const
|
|
443
|
-
|
|
333
|
+
it('clears the session cookie, removes the stored session and emits onLogout', async () => {
|
|
334
|
+
await usingAsync(createInjector(), async (i) => {
|
|
335
|
+
prepareInjector(i);
|
|
336
|
+
const ctx = i.get(HttpUserContext);
|
|
337
|
+
const onLogout = vi.fn();
|
|
338
|
+
ctx.addListener('onLogout', onLogout);
|
|
444
339
|
let sessionId = '';
|
|
445
|
-
const pw = await i.getInstance(PasswordAuthenticator).hasher.createCredential(testUser.username, 'test');
|
|
446
|
-
await sm.getStoreFor(PasswordCredential, 'userName').add(pw);
|
|
447
340
|
await ctx.cookieLogin(testUser, {
|
|
448
|
-
setHeader: (
|
|
449
|
-
sessionId =
|
|
450
|
-
return {};
|
|
341
|
+
setHeader: (_name, value) => {
|
|
342
|
+
sessionId = value.split('=')[1].split(';')[0];
|
|
451
343
|
},
|
|
452
344
|
});
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
await
|
|
458
|
-
|
|
345
|
+
const logoutSetHeader = vi.fn();
|
|
346
|
+
await ctx.cookieLogout({ headers: { cookie: `${ctx.authentication.cookieName}=${sessionId};` } }, { setHeader: logoutSetHeader });
|
|
347
|
+
expect(logoutSetHeader).toHaveBeenCalledWith('Set-Cookie', `${ctx.authentication.cookieName}=; Path=/; HttpOnly`);
|
|
348
|
+
expect(onLogout).toHaveBeenCalledWith(undefined);
|
|
349
|
+
await usingAsync(useSystemIdentityContext({ injector: i, username: 'spec' }), async (systemScope) => {
|
|
350
|
+
const sessionDataSet = systemScope.get(SessionDataSet);
|
|
351
|
+
expect(await sessionDataSet.count(systemScope)).toBe(0);
|
|
352
|
+
});
|
|
459
353
|
});
|
|
460
354
|
});
|
|
461
355
|
});
|