@jskit-ai/auth-core 0.1.157 → 0.1.159
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/package.json +30 -45
- package/src/client/index.js +0 -1
- package/src/client/providers/AccessCoreClientProvider.js +11 -12
- package/src/server/actions/auth.contributor.js +40 -37
- package/src/server/authExtensions.js +155 -0
- package/src/server/authPolicyContextResolverRegistry.js +9 -120
- package/src/server/authServiceDecoratorRegistry.js +9 -64
- package/src/server/deferredAuthService.js +32 -0
- package/src/server/lib/actionContextContributor.js +2 -1
- package/src/server/providers/{AccessCoreServiceProvider.js → AuthAccessProvider.js} +11 -14
- package/src/server/providers/AuthExtensionsProvider.js +14 -0
- package/src/server/providers/AuthFeature.js +15 -0
- package/src/server/providers/AuthPolicyProvider.js +79 -0
- package/test/actionContextContributor.test.js +14 -6
- package/test/authActions.test.js +60 -237
- package/test/authPolicyContextResolverRegistry.test.js +9 -67
- package/test/authServiceDecoratorRegistry.test.js +21 -47
- package/test/providerRuntime.test.js +90 -141
- package/src/client/providers/FastifyAuthPolicyClientProvider.js +0 -13
- package/src/server/providers/AuthActionsServiceProvider.js +0 -76
- package/src/server/providers/FastifyAuthPolicyServiceProvider.js +0 -108
- package/src/server/services/authSessionEventsService.js +0 -29
|
@@ -1,70 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function normalizeAuthServiceDecorator(entry) {
|
|
6
|
-
if (typeof entry === "function") {
|
|
7
|
-
return Object.freeze({
|
|
8
|
-
decoratorId: String(entry.name || "anonymous"),
|
|
9
|
-
order: 0,
|
|
10
|
-
decorateAuthService: entry
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (!entry || typeof entry !== "object" || typeof entry.decorateAuthService !== "function") {
|
|
15
|
-
return null;
|
|
1
|
+
function registerAuthServiceDecorator(extensions, decorator) {
|
|
2
|
+
if (!extensions || typeof extensions.registerServiceDecorator !== "function") {
|
|
3
|
+
throw new TypeError("registerAuthServiceDecorator requires auth.extensions.");
|
|
16
4
|
}
|
|
17
|
-
|
|
18
|
-
const decoratorId = String(entry.decoratorId || "anonymous");
|
|
19
|
-
const order = Number.isFinite(entry.order) ? Number(entry.order) : 0;
|
|
20
|
-
|
|
21
|
-
return Object.freeze({
|
|
22
|
-
...entry,
|
|
23
|
-
decoratorId,
|
|
24
|
-
order,
|
|
25
|
-
decorateAuthService: entry.decorateAuthService
|
|
26
|
-
});
|
|
5
|
+
return extensions.registerServiceDecorator(decorator);
|
|
27
6
|
}
|
|
28
7
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function resolveAuthServiceDecorators(scope) {
|
|
36
|
-
return resolveTaggedEntries(scope, AUTH_SERVICE_DECORATOR_TAG)
|
|
37
|
-
.map((entry, index) => ({
|
|
38
|
-
decorator: normalizeAuthServiceDecorator(entry),
|
|
39
|
-
index
|
|
40
|
-
}))
|
|
41
|
-
.filter((entry) => Boolean(entry.decorator))
|
|
42
|
-
.sort((left, right) => {
|
|
43
|
-
if (left.decorator.order !== right.decorator.order) {
|
|
44
|
-
return left.decorator.order - right.decorator.order;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return left.index - right.index;
|
|
48
|
-
})
|
|
49
|
-
.map((entry) => entry.decorator);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function applyAuthServiceDecorators(scope, authService) {
|
|
53
|
-
let decoratedAuthService = authService;
|
|
54
|
-
|
|
55
|
-
for (const decorator of resolveAuthServiceDecorators(scope)) {
|
|
56
|
-
decoratedAuthService = decorator.decorateAuthService(decoratedAuthService);
|
|
57
|
-
if (!decoratedAuthService || typeof decoratedAuthService !== "object") {
|
|
58
|
-
throw new Error(`Auth service decorator "${decorator.decoratorId}" must return an auth service object.`);
|
|
59
|
-
}
|
|
8
|
+
function applyAuthServiceDecorators(extensions, authService) {
|
|
9
|
+
if (!extensions || typeof extensions.decorateService !== "function") {
|
|
10
|
+
throw new TypeError("applyAuthServiceDecorators requires auth.extensions.");
|
|
60
11
|
}
|
|
61
|
-
|
|
62
|
-
return decoratedAuthService;
|
|
12
|
+
return extensions.decorateService(authService);
|
|
63
13
|
}
|
|
64
14
|
|
|
65
|
-
export {
|
|
66
|
-
AUTH_SERVICE_DECORATOR_TAG,
|
|
67
|
-
applyAuthServiceDecorators,
|
|
68
|
-
registerAuthServiceDecorator,
|
|
69
|
-
resolveAuthServiceDecorators
|
|
70
|
-
};
|
|
15
|
+
export { applyAuthServiceDecorators, registerAuthServiceDecorator };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const attachByHandle = new WeakMap();
|
|
2
|
+
|
|
3
|
+
function createDeferredAuthService() {
|
|
4
|
+
let service = null;
|
|
5
|
+
const handle = new Proxy(Object.create(null), {
|
|
6
|
+
get(_target, property) {
|
|
7
|
+
if (property === Symbol.toStringTag) return "AuthService";
|
|
8
|
+
if (!service) throw new Error("Authentication service is not ready until runtime startup completes.");
|
|
9
|
+
const value = service[property];
|
|
10
|
+
return typeof value === "function" ? value.bind(service) : value;
|
|
11
|
+
},
|
|
12
|
+
has(_target, property) {
|
|
13
|
+
return service ? property in service : false;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
attachByHandle.set(handle, (value) => {
|
|
17
|
+
if (service) throw new Error("Authentication service is already initialized.");
|
|
18
|
+
service = value;
|
|
19
|
+
});
|
|
20
|
+
return handle;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function attachDeferredAuthService(handle, service) {
|
|
24
|
+
const attach = attachByHandle.get(handle);
|
|
25
|
+
if (!attach) throw new TypeError("attachDeferredAuthService requires a deferred auth service handle.");
|
|
26
|
+
if (!service || typeof service !== "object") {
|
|
27
|
+
throw new TypeError("attachDeferredAuthService requires an auth service object.");
|
|
28
|
+
}
|
|
29
|
+
attach(service);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { attachDeferredAuthService, createDeferredAuthService };
|
|
@@ -3,7 +3,8 @@ import { normalizePermissionList } from "@jskit-ai/kernel/shared/support/permiss
|
|
|
3
3
|
function createAuthActionContextContributor() {
|
|
4
4
|
return Object.freeze({
|
|
5
5
|
contributorId: "auth.policy.request-context",
|
|
6
|
-
contribute({
|
|
6
|
+
contribute({ context } = {}) {
|
|
7
|
+
const request = context?.requestMeta?.request || null;
|
|
7
8
|
const contribution = {};
|
|
8
9
|
const permissions = normalizePermissionList(request?.permissions);
|
|
9
10
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
1
2
|
import * as authConstraints from "../../shared/authConstraints.js";
|
|
2
3
|
import * as authMethods from "../../shared/authMethods.js";
|
|
3
4
|
import * as oauthProviders from "../../shared/oauthProviders.js";
|
|
@@ -7,7 +8,7 @@ import * as inviteTokens from "../inviteTokens.js";
|
|
|
7
8
|
import * as utils from "../utils.js";
|
|
8
9
|
import * as validators from "../validators.js";
|
|
9
10
|
|
|
10
|
-
const
|
|
11
|
+
const access = Object.freeze({
|
|
11
12
|
authConstraints,
|
|
12
13
|
authMethods,
|
|
13
14
|
oauthProviders,
|
|
@@ -18,18 +19,14 @@ const ACCESS_CORE_API = Object.freeze({
|
|
|
18
19
|
validators
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
app.singleton("auth.access", () => ACCESS_CORE_API);
|
|
22
|
+
const AuthAccessProvider = defineProvider({
|
|
23
|
+
id: "auth.access",
|
|
24
|
+
provides: {
|
|
25
|
+
access: "auth.access"
|
|
26
|
+
},
|
|
27
|
+
setup() {
|
|
28
|
+
return { access };
|
|
30
29
|
}
|
|
30
|
+
});
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export { AccessCoreServiceProvider };
|
|
32
|
+
export { AuthAccessProvider };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
2
|
+
import { createAuthExtensions } from "../authExtensions.js";
|
|
3
|
+
|
|
4
|
+
const AuthExtensionsProvider = defineProvider({
|
|
5
|
+
id: "auth.extensions",
|
|
6
|
+
provides: {
|
|
7
|
+
extensions: "auth.extensions"
|
|
8
|
+
},
|
|
9
|
+
setup() {
|
|
10
|
+
return { extensions: createAuthExtensions() };
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export { AuthExtensionsProvider };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineFeature } from "@jskit-ai/kernel/server/features";
|
|
2
|
+
import { buildAuthActions } from "../actions/auth.contributor.js";
|
|
3
|
+
|
|
4
|
+
const AuthFeature = defineFeature({
|
|
5
|
+
id: "auth.actions",
|
|
6
|
+
domain: "auth",
|
|
7
|
+
optional: {
|
|
8
|
+
authService: "auth.service"
|
|
9
|
+
},
|
|
10
|
+
actions({ authService }) {
|
|
11
|
+
return authService ? buildAuthActions({ authService }) : [];
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export { AuthFeature };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
2
|
+
import { parseBooleanFlag } from "../booleanFlag.js";
|
|
3
|
+
import { authPolicyPlugin } from "../lib/plugin.js";
|
|
4
|
+
import { createAuthActionContextContributor } from "../lib/actionContextContributor.js";
|
|
5
|
+
import { createAuthRouteVisibilityResolver } from "../lib/routeVisibilityResolver.js";
|
|
6
|
+
|
|
7
|
+
function parseList(value) {
|
|
8
|
+
return String(value || "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function defaultHasPermission({ permission, permissions = [] } = {}) {
|
|
12
|
+
return !permission || (Array.isArray(permissions) && permissions.includes(permission));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createPolicy({ authService, extensions, env }) {
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
async resolveActor(request) {
|
|
18
|
+
if (authService && typeof authService.authenticateRequest === "function") {
|
|
19
|
+
return authService.authenticateRequest(request);
|
|
20
|
+
}
|
|
21
|
+
return { authenticated: false, actor: null, transientFailure: false };
|
|
22
|
+
},
|
|
23
|
+
hasPermission: defaultHasPermission,
|
|
24
|
+
resolveContext(input = {}) {
|
|
25
|
+
return extensions.resolvePolicyContext(input);
|
|
26
|
+
},
|
|
27
|
+
options: Object.freeze({
|
|
28
|
+
nodeEnv: String(env.NODE_ENV || "development").trim() || "development",
|
|
29
|
+
apiPrefix: String(env.AUTH_API_PREFIX || "/api/").trim() || "/api/",
|
|
30
|
+
unsafeMethods: Object.freeze(parseList(env.AUTH_CSRF_UNSAFE_METHODS)),
|
|
31
|
+
csrfCookieOpts: Object.freeze({
|
|
32
|
+
secure: parseBooleanFlag(env.AUTH_CSRF_COOKIE_SECURE, false)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const AuthPolicyProvider = defineProvider({
|
|
39
|
+
id: "auth.policy",
|
|
40
|
+
requires: {
|
|
41
|
+
actions: "runtime.actions",
|
|
42
|
+
extensions: "auth.extensions",
|
|
43
|
+
fastify: "runtime.fastify",
|
|
44
|
+
http: "runtime.http",
|
|
45
|
+
env: "runtime.env"
|
|
46
|
+
},
|
|
47
|
+
optional: {
|
|
48
|
+
authService: "auth.service"
|
|
49
|
+
},
|
|
50
|
+
provides: {
|
|
51
|
+
policy: "auth.policy"
|
|
52
|
+
},
|
|
53
|
+
setup({ actions, authService, extensions, http, env }) {
|
|
54
|
+
const actionContext = createAuthActionContextContributor();
|
|
55
|
+
actions.registerContextContributor({
|
|
56
|
+
id: actionContext.contributorId,
|
|
57
|
+
contribute: actionContext.contribute
|
|
58
|
+
});
|
|
59
|
+
const visibility = createAuthRouteVisibilityResolver();
|
|
60
|
+
http.registerVisibilityResolver({
|
|
61
|
+
id: visibility.resolverId,
|
|
62
|
+
resolve: visibility.resolve
|
|
63
|
+
});
|
|
64
|
+
return { policy: createPolicy({ authService, extensions, env }) };
|
|
65
|
+
},
|
|
66
|
+
async boot({ fastify }, { outputs }) {
|
|
67
|
+
const policy = outputs.policy;
|
|
68
|
+
await authPolicyPlugin(
|
|
69
|
+
{
|
|
70
|
+
resolveActor: policy.resolveActor,
|
|
71
|
+
resolveContext: policy.resolveContext,
|
|
72
|
+
hasPermission: policy.hasPermission
|
|
73
|
+
},
|
|
74
|
+
policy.options
|
|
75
|
+
)(fastify);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export { AuthPolicyProvider };
|
|
@@ -6,11 +6,15 @@ test("auth action context contributor skips empty placeholder values", () => {
|
|
|
6
6
|
const contributor = createAuthActionContextContributor();
|
|
7
7
|
|
|
8
8
|
const contribution = contributor.contribute({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
context: {
|
|
10
|
+
requestMeta: {
|
|
11
|
+
request: {
|
|
12
|
+
user: null,
|
|
13
|
+
workspace: null,
|
|
14
|
+
membership: null,
|
|
15
|
+
permissions: []
|
|
16
|
+
}
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
});
|
|
16
20
|
|
|
@@ -33,7 +37,11 @@ test("auth action context contributor contributes real request context values",
|
|
|
33
37
|
permissions: ["workspace.settings.update", "", " "]
|
|
34
38
|
};
|
|
35
39
|
|
|
36
|
-
const contribution = contributor.contribute({
|
|
40
|
+
const contribution = contributor.contribute({
|
|
41
|
+
context: {
|
|
42
|
+
requestMeta: { request }
|
|
43
|
+
}
|
|
44
|
+
});
|
|
37
45
|
|
|
38
46
|
assert.deepEqual(contribution, {
|
|
39
47
|
actor: request.user,
|
package/test/authActions.test.js
CHANGED
|
@@ -1,265 +1,88 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { ActionRuntimeServiceProvider } from "@jskit-ai/kernel/server/actions";
|
|
6
|
-
import { AuthActionsServiceProvider } from "../src/server/providers/AuthActionsServiceProvider.js";
|
|
3
|
+
import { createActionProvider } from "@jskit-ai/kernel/server/actions";
|
|
4
|
+
import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
7
5
|
import { buildAuthActions } from "../src/server/actions/auth.contributor.js";
|
|
6
|
+
import { AuthFeature } from "../src/server/providers/AuthFeature.js";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
surfaceModeAll: "all",
|
|
12
|
-
surfaceDefaultId: "home",
|
|
13
|
-
surfaceDefinitions: {
|
|
14
|
-
home: { id: "home", pagesRoot: "", enabled: true, requiresAuth: false, requiresWorkspace: false },
|
|
15
|
-
console: {
|
|
16
|
-
id: "console",
|
|
17
|
-
pagesRoot: "console",
|
|
18
|
-
enabled: true,
|
|
19
|
-
requiresAuth: true,
|
|
20
|
-
requiresWorkspace: false
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
test("auth logout action delegates to selected auth provider and notifies session changes", async () => {
|
|
27
|
-
const action = buildAuthActions().find((definition) => definition.id === "auth.logout");
|
|
28
|
-
const request = {
|
|
29
|
-
id: "request-1"
|
|
30
|
-
};
|
|
8
|
+
test("auth logout action delegates directly to the selected auth service", async () => {
|
|
31
9
|
const calls = [];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
requestMeta: {
|
|
37
|
-
request
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
authService: {
|
|
42
|
-
async logout(receivedRequest) {
|
|
43
|
-
calls.push({
|
|
44
|
-
type: "logout",
|
|
45
|
-
request: receivedRequest
|
|
46
|
-
});
|
|
47
|
-
return {
|
|
48
|
-
ok: true,
|
|
49
|
-
clearSession: true
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
authSessionEventsService: {
|
|
54
|
-
async notifySessionChanged(payload) {
|
|
55
|
-
calls.push({
|
|
56
|
-
type: "notify",
|
|
57
|
-
context: payload.context
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
10
|
+
const authService = {
|
|
11
|
+
async logout(request) {
|
|
12
|
+
calls.push(request);
|
|
13
|
+
return { ok: true, clearSession: true };
|
|
61
14
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
15
|
+
};
|
|
16
|
+
const action = buildAuthActions({ authService }).find((definition) => definition.id === "auth.logout");
|
|
17
|
+
const request = { id: "request-1" };
|
|
18
|
+
assert.deepEqual(await action.execute({}, { requestMeta: { request } }), {
|
|
65
19
|
ok: true,
|
|
66
20
|
clearSession: true
|
|
67
21
|
});
|
|
68
|
-
assert.deepEqual(calls, [
|
|
69
|
-
{
|
|
70
|
-
type: "logout",
|
|
71
|
-
request
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: "notify",
|
|
75
|
-
context: {
|
|
76
|
-
requestMeta: {
|
|
77
|
-
request
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
]);
|
|
22
|
+
assert.deepEqual(calls, [request]);
|
|
82
23
|
});
|
|
83
24
|
|
|
84
|
-
test("shared dev login-as action passes the trusted request to
|
|
85
|
-
const
|
|
86
|
-
const request = {
|
|
87
|
-
headers: {
|
|
88
|
-
"x-jskit-dev-auth-secret": "secret"
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
const input = {
|
|
92
|
-
email: "ada@example.com"
|
|
93
|
-
};
|
|
25
|
+
test("shared dev login-as action passes the trusted request to auth.service", async () => {
|
|
26
|
+
const input = { email: "ada@example.com" };
|
|
27
|
+
const request = { headers: { "x-jskit-dev-auth-secret": "secret" } };
|
|
94
28
|
let received = null;
|
|
95
|
-
|
|
96
|
-
const result = await action.execute(input, {
|
|
97
|
-
requestMeta: {
|
|
98
|
-
request
|
|
99
|
-
}
|
|
100
|
-
}, {
|
|
29
|
+
const action = buildAuthActions({
|
|
101
30
|
authService: {
|
|
102
31
|
async devLoginAs(receivedRequest, receivedInput) {
|
|
103
|
-
received = {
|
|
104
|
-
|
|
105
|
-
request: receivedRequest
|
|
106
|
-
};
|
|
107
|
-
return {
|
|
108
|
-
ok: true
|
|
109
|
-
};
|
|
32
|
+
received = { input: receivedInput, request: receivedRequest };
|
|
33
|
+
return { ok: true };
|
|
110
34
|
}
|
|
111
35
|
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
assert.deepEqual(result, { ok: true });
|
|
36
|
+
}).find((definition) => definition.id === "auth.dev.loginAs");
|
|
37
|
+
assert.deepEqual(await action.execute(input, { requestMeta: { request } }), { ok: true });
|
|
115
38
|
assert.deepEqual(received, { input, request });
|
|
116
39
|
});
|
|
117
40
|
|
|
118
|
-
test("
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
ok: true,
|
|
131
|
-
|
|
132
|
-
};
|
|
133
|
-
},
|
|
134
|
-
async authenticateRequest() {
|
|
135
|
-
return {
|
|
136
|
-
authenticated: false,
|
|
137
|
-
actor: null,
|
|
138
|
-
transientFailure: false
|
|
139
|
-
};
|
|
41
|
+
test("AuthFeature contributes actions only when auth.service exists", async () => {
|
|
42
|
+
let actions = null;
|
|
43
|
+
const SelectedAuthProvider = defineProvider({
|
|
44
|
+
id: "test.auth.service",
|
|
45
|
+
provides: { service: "auth.service" },
|
|
46
|
+
setup() {
|
|
47
|
+
return {
|
|
48
|
+
service: {
|
|
49
|
+
async authenticateRequest() {
|
|
50
|
+
return { authenticated: false, actor: null, transientFailure: false };
|
|
51
|
+
},
|
|
52
|
+
async logout() {
|
|
53
|
+
return { ok: true, clearSession: true };
|
|
54
|
+
}
|
|
140
55
|
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
app.instance("appConfig", createAppConfigFixture());
|
|
146
|
-
app.instance("domainEvents", {
|
|
147
|
-
async publish(payload) {
|
|
148
|
-
published.push(payload);
|
|
56
|
+
};
|
|
149
57
|
}
|
|
150
58
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const definitions = actionExecutor.listDefinitions();
|
|
158
|
-
assert.equal(definitions.some((definition) => definition.id === "auth.login.password"), true);
|
|
159
|
-
assert.equal(definitions.some((definition) => definition.id === "auth.register"), true);
|
|
160
|
-
assert.equal(definitions.some((definition) => definition.id === "auth.dev.loginAs"), true);
|
|
161
|
-
assert.deepEqual(definitions.find((definition) => definition.id === "auth.session.read")?.surfaces, [
|
|
162
|
-
"home",
|
|
163
|
-
"console"
|
|
164
|
-
]);
|
|
165
|
-
|
|
166
|
-
const request = { id: "request-2" };
|
|
167
|
-
const result = await actionExecutor.execute({
|
|
168
|
-
actionId: "auth.logout",
|
|
169
|
-
input: {},
|
|
170
|
-
context: {
|
|
171
|
-
channel: "internal",
|
|
172
|
-
surface: "home",
|
|
173
|
-
requestMeta: { request },
|
|
174
|
-
actor: { id: 42 }
|
|
59
|
+
const ProbeProvider = defineProvider({
|
|
60
|
+
id: "test.auth.actions.probe",
|
|
61
|
+
requires: { catalogue: "runtime.actions" },
|
|
62
|
+
setup({ catalogue }) {
|
|
63
|
+
actions = catalogue;
|
|
64
|
+
return {};
|
|
175
65
|
}
|
|
176
66
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
ok: true,
|
|
180
|
-
clearSession: true
|
|
67
|
+
const runtime = createCapabilityRuntime({
|
|
68
|
+
providers: [createActionProvider(), SelectedAuthProvider, AuthFeature, ProbeProvider]
|
|
181
69
|
});
|
|
182
|
-
|
|
183
|
-
assert.equal(
|
|
184
|
-
assert.deepEqual(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
194
|
-
source: "auth",
|
|
195
|
-
entity: "session",
|
|
196
|
-
operation: "updated",
|
|
197
|
-
entityId: "42",
|
|
198
|
-
realtimeEvent: "auth.session.changed"
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
source: "users",
|
|
202
|
-
entity: "bootstrap",
|
|
203
|
-
operation: "updated",
|
|
204
|
-
entityId: "42",
|
|
205
|
-
realtimeEvent: "users.bootstrap.changed"
|
|
206
|
-
}
|
|
207
|
-
]
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("AuthActionsServiceProvider leaves unrelated actions usable when no auth provider is selected", async () => {
|
|
212
|
-
const app = createApplication();
|
|
213
|
-
|
|
214
|
-
class PublicActionProvider {
|
|
215
|
-
static id = "public.actions";
|
|
216
|
-
|
|
217
|
-
static startsAfter = ["runtime.actions"];
|
|
218
|
-
|
|
219
|
-
register(targetApp) {
|
|
220
|
-
targetApp.action({
|
|
221
|
-
id: "public.ping",
|
|
222
|
-
domain: "public",
|
|
223
|
-
version: 1,
|
|
224
|
-
kind: "query",
|
|
225
|
-
channels: ["internal"],
|
|
226
|
-
surfaces: ["home"],
|
|
227
|
-
permission: { require: "none" },
|
|
228
|
-
input: {
|
|
229
|
-
schema: createSchema({}),
|
|
230
|
-
mode: "patch"
|
|
231
|
-
},
|
|
232
|
-
output: null,
|
|
233
|
-
idempotency: "none",
|
|
234
|
-
audit: { actionName: "public.ping" },
|
|
235
|
-
observability: {},
|
|
236
|
-
async execute() {
|
|
237
|
-
return { ok: true };
|
|
238
|
-
}
|
|
239
|
-
});
|
|
70
|
+
await runtime.start();
|
|
71
|
+
assert.equal(actions.listDefinitions().some((definition) => definition.id === "auth.login.password"), true);
|
|
72
|
+
assert.deepEqual(actions.getDefinition("auth.session.read").surfaces, ["*"]);
|
|
73
|
+
|
|
74
|
+
let publicActions = null;
|
|
75
|
+
const EmptyProbe = defineProvider({
|
|
76
|
+
id: "test.auth.empty-probe",
|
|
77
|
+
requires: { catalogue: "runtime.actions" },
|
|
78
|
+
setup({ catalogue }) {
|
|
79
|
+
publicActions = catalogue;
|
|
80
|
+
return {};
|
|
240
81
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
app.instance("appConfig", createAppConfigFixture());
|
|
244
|
-
|
|
245
|
-
await app.start({
|
|
246
|
-
providers: [ActionRuntimeServiceProvider, AuthActionsServiceProvider, PublicActionProvider]
|
|
247
82
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
);
|
|
254
|
-
assert.deepEqual(
|
|
255
|
-
await actionExecutor.execute({
|
|
256
|
-
actionId: "public.ping",
|
|
257
|
-
input: {},
|
|
258
|
-
context: {
|
|
259
|
-
channel: "internal",
|
|
260
|
-
surface: "home"
|
|
261
|
-
}
|
|
262
|
-
}),
|
|
263
|
-
{ ok: true }
|
|
264
|
-
);
|
|
83
|
+
const noAuthRuntime = createCapabilityRuntime({
|
|
84
|
+
providers: [createActionProvider(), AuthFeature, EmptyProbe]
|
|
85
|
+
});
|
|
86
|
+
await noAuthRuntime.start();
|
|
87
|
+
assert.deepEqual(publicActions.listDefinitions(), []);
|
|
265
88
|
});
|