@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.
@@ -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.47",
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.45",
224
- "@jskit-ai/http-runtime": "0.1.45",
225
- "@jskit-ai/kernel": "0.1.46",
226
- "@jskit-ai/shell-web": "0.1.45",
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 }) => !Boolean(auth?.authenticated)\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 }) => Boolean(auth?.authenticated)\n});\n",
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.47",
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.45",
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.46",
25
- "@jskit-ai/shell-web": "0.1.45",
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.45"
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
+ });