@jskit-ai/auth-web 0.1.47 → 0.1.49
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.descriptor.mjs +6 -6
- package/package.json +5 -5
- package/src/server/constants/authActionIds.js +1 -0
- package/src/server/controllers/AuthController.js +12 -0
- package/src/server/providers/AuthRouteServiceProvider.js +4 -1
- package/src/server/routes/authRoutes.js +23 -1
- package/src/server/services/AuthWebService.js +13 -0
- package/test/providerRuntime.test.js +56 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/auth-web",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.49",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"description": "Auth web module: Fastify auth routes plus web login/sign-out scaffolds.",
|
|
7
7
|
"dependsOn": [
|
|
@@ -220,10 +220,10 @@ export default Object.freeze({
|
|
|
220
220
|
"@tanstack/vue-query": "5.92.12",
|
|
221
221
|
"@mdi/js": "^7.4.47",
|
|
222
222
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
223
|
-
"@jskit-ai/auth-core": "0.1.
|
|
224
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
225
|
-
"@jskit-ai/kernel": "0.1.
|
|
226
|
-
"@jskit-ai/shell-web": "0.1.
|
|
223
|
+
"@jskit-ai/auth-core": "0.1.47",
|
|
224
|
+
"@jskit-ai/http-runtime": "0.1.47",
|
|
225
|
+
"@jskit-ai/kernel": "0.1.48",
|
|
226
|
+
"@jskit-ai/shell-web": "0.1.47",
|
|
227
227
|
"vuetify": "^4.0.0"
|
|
228
228
|
},
|
|
229
229
|
"dev": {}
|
|
@@ -282,7 +282,7 @@ export default Object.freeze({
|
|
|
282
282
|
"file": "src/placement.js",
|
|
283
283
|
"position": "bottom",
|
|
284
284
|
"skipIfContains": "id: \"auth.profile.widget\"",
|
|
285
|
-
"value": "\naddPlacement({\n id: \"auth.profile.widget\",\n target: \"shell-layout:top-right\",\n surfaces: [\"*\"],\n order: 1000,\n componentToken: \"auth.web.profile.widget\"\n});\n\naddPlacement({\n id: \"auth.profile.menu.sign-in\",\n target: \"auth-profile-menu:primary-menu\",\n surfaces: [\"*\"],\n order: 200,\n componentToken: \"auth.web.profile.menu.link-item\",\n props: {\n label: \"Sign in\",\n to: \"/auth/login\"\n },\n when: ({ auth }) =>
|
|
285
|
+
"value": "\naddPlacement({\n id: \"auth.profile.widget\",\n target: \"shell-layout:top-right\",\n surfaces: [\"*\"],\n order: 1000,\n componentToken: \"auth.web.profile.widget\"\n});\n\naddPlacement({\n id: \"auth.profile.menu.sign-in\",\n target: \"auth-profile-menu:primary-menu\",\n surfaces: [\"*\"],\n order: 200,\n componentToken: \"auth.web.profile.menu.link-item\",\n props: {\n label: \"Sign in\",\n to: \"/auth/login\"\n },\n when: ({ auth }) => auth?.authenticated !== true\n});\n\naddPlacement({\n id: \"auth.profile.menu.sign-out\",\n target: \"auth-profile-menu:primary-menu\",\n surfaces: [\"*\"],\n order: 1000,\n componentToken: \"auth.web.profile.menu.link-item\",\n props: {\n label: \"Sign out\",\n to: \"/auth/signout\"\n },\n when: ({ auth }) => auth?.authenticated === true\n});\n",
|
|
286
286
|
"reason": "Append auth profile placement entries into app-owned placement registry.",
|
|
287
287
|
"category": "auth-web",
|
|
288
288
|
"id": "auth-web-placement-block"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@tanstack/vue-query": "^5.90.5",
|
|
21
|
-
"@jskit-ai/auth-core": "0.1.
|
|
21
|
+
"@jskit-ai/auth-core": "0.1.47",
|
|
22
22
|
"@mdi/js": "^7.4.47",
|
|
23
23
|
"@fastify/type-provider-typebox": "^6.1.0",
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
24
|
+
"@jskit-ai/kernel": "0.1.48",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.47",
|
|
26
26
|
"pinia": "^3.0.4",
|
|
27
27
|
"vuetify": "^4.0.0",
|
|
28
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
28
|
+
"@jskit-ai/http-runtime": "0.1.47"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -6,6 +6,7 @@ const AUTH_ACTION_IDS = Object.freeze({
|
|
|
6
6
|
LOGIN_OTP_VERIFY: "auth.login.otp.verify",
|
|
7
7
|
LOGIN_OAUTH_START: "auth.login.oauth.start",
|
|
8
8
|
LOGIN_OAUTH_COMPLETE: "auth.login.oauth.complete",
|
|
9
|
+
DEV_LOGIN_AS: "auth.dev.loginAs",
|
|
9
10
|
LOGOUT: "auth.logout",
|
|
10
11
|
SESSION_READ: "auth.session.read",
|
|
11
12
|
PASSWORD_RESET_REQUEST: "auth.password.reset.request",
|
|
@@ -91,6 +91,18 @@ class AuthController {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async devLoginAs(request, reply) {
|
|
95
|
+
const payload = request.body || {};
|
|
96
|
+
const result = await this.service.devLoginAs(request, payload);
|
|
97
|
+
this.service.writeSessionCookies(reply, result.session);
|
|
98
|
+
reply.code(200).send({
|
|
99
|
+
ok: true,
|
|
100
|
+
userId: result.profile.id,
|
|
101
|
+
username: result.profile.displayName,
|
|
102
|
+
email: result.profile.email
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
async logout(request, reply) {
|
|
95
107
|
let clearSession = true;
|
|
96
108
|
try {
|
|
@@ -20,7 +20,10 @@ class AuthRouteServiceProvider {
|
|
|
20
20
|
const router = app.make("jskit.http.router");
|
|
21
21
|
const authWebService = app.make("auth.web.service");
|
|
22
22
|
const controller = new AuthController({ service: authWebService });
|
|
23
|
-
const routes = buildRoutes(controller
|
|
23
|
+
const routes = buildRoutes(controller, {
|
|
24
|
+
includeDevLoginAs:
|
|
25
|
+
typeof authWebService?.isDevLoginAsAvailable === "function" ? authWebService.isDevLoginAsAvailable() : false
|
|
26
|
+
});
|
|
24
27
|
for (const route of routes) {
|
|
25
28
|
router.register(route.method, route.path, route, route.handler);
|
|
26
29
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
authLoginOtpVerifyCommand,
|
|
8
8
|
authLoginOAuthStartCommand,
|
|
9
9
|
authLoginOAuthCompleteCommand,
|
|
10
|
+
authDevLoginAsCommand,
|
|
10
11
|
authPasswordResetRequestCommand,
|
|
11
12
|
authPasswordRecoveryCompleteCommand,
|
|
12
13
|
authPasswordResetCommand,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
} from "@jskit-ai/auth-core/shared/commands";
|
|
16
17
|
import { AUTH_PATHS } from "@jskit-ai/auth-core/shared/authPaths";
|
|
17
18
|
|
|
18
|
-
function buildRoutes(controller) {
|
|
19
|
+
function buildRoutes(controller, { includeDevLoginAs = false } = {}) {
|
|
19
20
|
if (!controller) {
|
|
20
21
|
throw new Error("Auth routes require a controller instance.");
|
|
21
22
|
}
|
|
@@ -172,6 +173,27 @@ function buildRoutes(controller) {
|
|
|
172
173
|
},
|
|
173
174
|
handler: handler("oauthComplete")
|
|
174
175
|
},
|
|
176
|
+
...(includeDevLoginAs ? [{
|
|
177
|
+
path: AUTH_PATHS.DEV_LOGIN_AS,
|
|
178
|
+
method: "POST",
|
|
179
|
+
auth: "public",
|
|
180
|
+
meta: {
|
|
181
|
+
tags: ["auth"],
|
|
182
|
+
summary: "Dev-only: create a local session for an existing user"
|
|
183
|
+
},
|
|
184
|
+
bodyValidator: authDevLoginAsCommand.operation.bodyValidator,
|
|
185
|
+
responseValidators: withStandardErrorResponses(
|
|
186
|
+
{
|
|
187
|
+
200: authDevLoginAsCommand.operation.responseValidator
|
|
188
|
+
},
|
|
189
|
+
{ includeValidation400: true }
|
|
190
|
+
),
|
|
191
|
+
rateLimit: {
|
|
192
|
+
max: 30,
|
|
193
|
+
timeWindow: "1 minute"
|
|
194
|
+
},
|
|
195
|
+
handler: handler("devLoginAs")
|
|
196
|
+
}] : []),
|
|
175
197
|
{
|
|
176
198
|
path: AUTH_PATHS.PASSWORD_FORGOT,
|
|
177
199
|
method: "POST",
|
|
@@ -61,6 +61,19 @@ class AuthWebService {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
async devLoginAs(request, payload) {
|
|
65
|
+
return request.executeAction({
|
|
66
|
+
actionId: AUTH_ACTION_IDS.DEV_LOGIN_AS,
|
|
67
|
+
input: payload
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isDevLoginAsAvailable() {
|
|
72
|
+
return typeof this.authService?.isDevAuthBootstrapEnabled === "function"
|
|
73
|
+
? this.authService.isDevAuthBootstrapEnabled()
|
|
74
|
+
: false;
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
async logout(request) {
|
|
65
78
|
return request.executeAction({
|
|
66
79
|
actionId: AUTH_ACTION_IDS.LOGOUT
|
|
@@ -68,6 +68,12 @@ test("auth route provider registers routes and executes login/logout handlers",
|
|
|
68
68
|
profile: { displayName: "Ada" }
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
if (actionId === "auth.dev.loginAs") {
|
|
72
|
+
return {
|
|
73
|
+
session: { access_token: "dev-a", refresh_token: "dev-r" },
|
|
74
|
+
profile: { id: "7", displayName: "Dev Ada", email: "ada@example.com" }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
71
77
|
if (actionId === "auth.logout") {
|
|
72
78
|
return {
|
|
73
79
|
ok: true,
|
|
@@ -103,6 +109,9 @@ test("auth route provider registers routes and executes login/logout handlers",
|
|
|
103
109
|
assert.equal(resendConfirmationReply.statusCode, 200);
|
|
104
110
|
assert.equal(resendConfirmationReply.payload.ok, true);
|
|
105
111
|
|
|
112
|
+
const devLoginRoute = fastify.routes.find((route) => route.method === "POST" && route.url === "/api/dev-auth/login-as");
|
|
113
|
+
assert.equal(devLoginRoute, undefined);
|
|
114
|
+
|
|
106
115
|
const logoutRoute = fastify.routes.find((route) => route.method === "POST" && route.url === "/api/logout");
|
|
107
116
|
assert.ok(logoutRoute);
|
|
108
117
|
const logoutReply = createReplyStub();
|
|
@@ -113,3 +122,50 @@ test("auth route provider registers routes and executes login/logout handlers",
|
|
|
113
122
|
assert.equal(events.some((entry) => entry.type === "writeSession"), true);
|
|
114
123
|
assert.equal(events.some((entry) => entry.type === "clearSession"), true);
|
|
115
124
|
});
|
|
125
|
+
|
|
126
|
+
test("auth route provider registers dev login route only when auth service enables it", async () => {
|
|
127
|
+
const fastify = createFastifyStub();
|
|
128
|
+
const app = createApplication();
|
|
129
|
+
const httpRuntime = createHttpRuntime({ app, fastify });
|
|
130
|
+
|
|
131
|
+
const authService = {
|
|
132
|
+
isDevAuthBootstrapEnabled() {
|
|
133
|
+
return true;
|
|
134
|
+
},
|
|
135
|
+
writeSessionCookies() {},
|
|
136
|
+
clearSessionCookies() {},
|
|
137
|
+
getOAuthProviderCatalog() {
|
|
138
|
+
return { providers: [], defaultProvider: "" };
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
app.instance("authService", authService);
|
|
143
|
+
app.instance("actionExecutor", {
|
|
144
|
+
async execute({ actionId }) {
|
|
145
|
+
if (actionId === "auth.dev.loginAs") {
|
|
146
|
+
return {
|
|
147
|
+
session: { access_token: "dev-a", refresh_token: "dev-r" },
|
|
148
|
+
profile: { id: "7", displayName: "Dev Ada", email: "ada@example.com" }
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
class MockAuthProvider {
|
|
156
|
+
static id = "auth.provider";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await app.start({ providers: [MockAuthProvider, AuthWebServiceProvider, AuthRouteServiceProvider] });
|
|
160
|
+
|
|
161
|
+
const registration = httpRuntime.registerRoutes();
|
|
162
|
+
assert.equal(registration.routeCount > 0, true);
|
|
163
|
+
|
|
164
|
+
const devLoginRoute = fastify.routes.find((route) => route.method === "POST" && route.url === "/api/dev-auth/login-as");
|
|
165
|
+
assert.ok(devLoginRoute);
|
|
166
|
+
const devLoginReply = createReplyStub();
|
|
167
|
+
await devLoginRoute.handler({ body: { userId: "7" } }, devLoginReply);
|
|
168
|
+
assert.equal(devLoginReply.statusCode, 200);
|
|
169
|
+
assert.equal(devLoginReply.payload.userId, "7");
|
|
170
|
+
assert.equal(devLoginReply.payload.username, "Dev Ada");
|
|
171
|
+
});
|