@jskit-ai/auth-web 0.1.119 → 0.1.121
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 +9 -5
- package/package.json +7 -6
- package/src/test/playwright.js +84 -0
- package/test/playwright.test.js +113 -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.121",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"description": "Auth web module: Fastify auth routes plus web login/sign-out scaffolds.",
|
|
7
7
|
"dependsOn": [
|
|
@@ -56,6 +56,10 @@ export default Object.freeze({
|
|
|
56
56
|
{
|
|
57
57
|
"subpath": "./server",
|
|
58
58
|
"summary": "Exports auth web server providers, controller/service classes, route builders, and HTTP schema modules."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"subpath": "./test/playwright",
|
|
62
|
+
"summary": "Exports the localhost-only Playwright helper for creating a dev-auth session in the tested browser context."
|
|
59
63
|
}
|
|
60
64
|
],
|
|
61
65
|
"containerTokens": {
|
|
@@ -260,10 +264,10 @@ export default Object.freeze({
|
|
|
260
264
|
"dependencies": {
|
|
261
265
|
"runtime": {
|
|
262
266
|
"@mdi/js": "^7.4.47",
|
|
263
|
-
"@jskit-ai/auth-core": "0.1.
|
|
264
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
265
|
-
"@jskit-ai/kernel": "0.1.
|
|
266
|
-
"@jskit-ai/shell-web": "0.1.
|
|
267
|
+
"@jskit-ai/auth-core": "0.1.118",
|
|
268
|
+
"@jskit-ai/http-runtime": "0.1.119",
|
|
269
|
+
"@jskit-ai/kernel": "0.1.121",
|
|
270
|
+
"@jskit-ai/shell-web": "0.1.121"
|
|
267
271
|
},
|
|
268
272
|
"dev": {}
|
|
269
273
|
},
|
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.121",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -16,14 +16,15 @@
|
|
|
16
16
|
"./client/runtime/authGuardRuntime": "./src/client/runtime/authGuardRuntime.js",
|
|
17
17
|
"./client/runtime/authHttpClient": "./src/client/runtime/authHttpClient.js",
|
|
18
18
|
"./client/runtime/oauthCallbackRuntime": "./src/client/runtime/oauthCallbackRuntime.js",
|
|
19
|
-
"./client/runtime/useSignOut": "./src/client/runtime/useSignOut.js"
|
|
19
|
+
"./client/runtime/useSignOut": "./src/client/runtime/useSignOut.js",
|
|
20
|
+
"./test/playwright": "./src/test/playwright.js"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@jskit-ai/auth-core": "0.1.
|
|
23
|
+
"@jskit-ai/auth-core": "0.1.118",
|
|
23
24
|
"@mdi/js": "^7.4.47",
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
26
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
25
|
+
"@jskit-ai/kernel": "0.1.121",
|
|
26
|
+
"@jskit-ai/shell-web": "0.1.121",
|
|
27
|
+
"@jskit-ai/http-runtime": "0.1.119"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"@tanstack/vue-query": "^5.90.5",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DEV_AUTH_SECRET_HEADER } from "@jskit-ai/auth-core/server/devAuth";
|
|
2
|
+
import { AUTH_PATHS } from "@jskit-ai/auth-core/shared/authPaths";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_LOCAL_BASE_URL = "http://127.0.0.1:4173";
|
|
5
|
+
|
|
6
|
+
function resolveLocalBaseUrl(value) {
|
|
7
|
+
const baseUrl = String(value || DEFAULT_LOCAL_BASE_URL).trim().replace(/\/+$/u, "");
|
|
8
|
+
let url;
|
|
9
|
+
try {
|
|
10
|
+
url = new URL(baseUrl);
|
|
11
|
+
} catch {
|
|
12
|
+
throw new Error("loginAsExistingUser requires a valid local baseURL.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
16
|
+
const isLocal = hostname === "localhost" ||
|
|
17
|
+
hostname.endsWith(".localhost") ||
|
|
18
|
+
hostname.startsWith("127.") ||
|
|
19
|
+
hostname === "::1";
|
|
20
|
+
if (!isLocal) {
|
|
21
|
+
throw new Error("loginAsExistingUser only sends the dev-auth secret to a local app URL.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return baseUrl;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveIdentity({ email = "", userId = "" } = {}) {
|
|
28
|
+
const normalizedEmail = String(email || "").trim();
|
|
29
|
+
const normalizedUserId = String(userId || "").trim();
|
|
30
|
+
if (Boolean(normalizedEmail) === Boolean(normalizedUserId)) {
|
|
31
|
+
throw new Error("loginAsExistingUser requires exactly one of email or userId.");
|
|
32
|
+
}
|
|
33
|
+
return normalizedEmail ? { email: normalizedEmail } : { userId: normalizedUserId };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function responseBody(response) {
|
|
37
|
+
const body = await response.text();
|
|
38
|
+
return body ? ` ${body}` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function loginAsExistingUser(page, {
|
|
42
|
+
email = "",
|
|
43
|
+
userId = "",
|
|
44
|
+
secret = process.env.AUTH_DEV_BYPASS_SECRET,
|
|
45
|
+
baseURL = process.env.PLAYWRIGHT_BASE_URL || DEFAULT_LOCAL_BASE_URL
|
|
46
|
+
} = {}) {
|
|
47
|
+
const context = page?.context?.();
|
|
48
|
+
if (!context?.request) {
|
|
49
|
+
throw new Error("loginAsExistingUser requires a Playwright page.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizedSecret = String(secret || "").trim();
|
|
53
|
+
if (!normalizedSecret) {
|
|
54
|
+
throw new Error("loginAsExistingUser requires AUTH_DEV_BYPASS_SECRET.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const localBaseUrl = resolveLocalBaseUrl(baseURL);
|
|
58
|
+
const identity = resolveIdentity({ email, userId });
|
|
59
|
+
const sessionResponse = await context.request.get(`${localBaseUrl}${AUTH_PATHS.SESSION}`);
|
|
60
|
+
if (!sessionResponse.ok()) {
|
|
61
|
+
throw new Error(`Session bootstrap failed: ${sessionResponse.status()}${await responseBody(sessionResponse)}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const session = await sessionResponse.json();
|
|
65
|
+
const csrfToken = String(session?.csrfToken || "").trim();
|
|
66
|
+
if (!csrfToken) {
|
|
67
|
+
throw new Error("Session bootstrap did not return csrfToken.");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const loginResponse = await context.request.post(`${localBaseUrl}${AUTH_PATHS.DEV_LOGIN_AS}`, {
|
|
71
|
+
data: identity,
|
|
72
|
+
headers: {
|
|
73
|
+
"csrf-token": csrfToken,
|
|
74
|
+
[DEV_AUTH_SECRET_HEADER]: normalizedSecret
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
if (!loginResponse.ok()) {
|
|
78
|
+
throw new Error(`Dev login failed: ${loginResponse.status()}${await responseBody(loginResponse)}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return loginResponse.json();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { loginAsExistingUser };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { DEV_AUTH_SECRET_HEADER } from "@jskit-ai/auth-core/server/devAuth";
|
|
4
|
+
import { loginAsExistingUser } from "../src/test/playwright.js";
|
|
5
|
+
|
|
6
|
+
function createResponse({ status = 200, body = {} } = {}) {
|
|
7
|
+
return {
|
|
8
|
+
ok() {
|
|
9
|
+
return status >= 200 && status < 300;
|
|
10
|
+
},
|
|
11
|
+
status() {
|
|
12
|
+
return status;
|
|
13
|
+
},
|
|
14
|
+
async json() {
|
|
15
|
+
return body;
|
|
16
|
+
},
|
|
17
|
+
async text() {
|
|
18
|
+
return JSON.stringify(body);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("local Playwright login exchanges CSRF and the private secret in the page context", async () => {
|
|
24
|
+
const calls = [];
|
|
25
|
+
const request = {
|
|
26
|
+
async get(url) {
|
|
27
|
+
calls.push({ method: "GET", url });
|
|
28
|
+
return createResponse({ body: { csrfToken: "csrf-test" } });
|
|
29
|
+
},
|
|
30
|
+
async post(url, options) {
|
|
31
|
+
calls.push({ method: "POST", url, options });
|
|
32
|
+
return createResponse({ body: { ok: true, userId: "7" } });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const page = {
|
|
36
|
+
context() {
|
|
37
|
+
return { request };
|
|
38
|
+
},
|
|
39
|
+
evaluate() {
|
|
40
|
+
throw new Error("The private exchange must not run in browser JavaScript.");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = await loginAsExistingUser(page, {
|
|
45
|
+
email: "ada@example.com",
|
|
46
|
+
secret: "private-test-secret"
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
assert.deepEqual(result, { ok: true, userId: "7" });
|
|
50
|
+
assert.deepEqual(calls, [
|
|
51
|
+
{
|
|
52
|
+
method: "GET",
|
|
53
|
+
url: "http://127.0.0.1:4173/api/session"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
method: "POST",
|
|
57
|
+
url: "http://127.0.0.1:4173/api/dev-auth/login-as",
|
|
58
|
+
options: {
|
|
59
|
+
data: { email: "ada@example.com" },
|
|
60
|
+
headers: {
|
|
61
|
+
"csrf-token": "csrf-test",
|
|
62
|
+
[DEV_AUTH_SECRET_HEADER]: "private-test-secret"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("local Playwright login refuses to send the private secret to a managed URL", async () => {
|
|
70
|
+
let requestCount = 0;
|
|
71
|
+
const page = {
|
|
72
|
+
context() {
|
|
73
|
+
return {
|
|
74
|
+
request: {
|
|
75
|
+
async get() {
|
|
76
|
+
requestCount += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
await assert.rejects(
|
|
84
|
+
loginAsExistingUser(page, {
|
|
85
|
+
email: "ada@example.com",
|
|
86
|
+
secret: "private-test-secret",
|
|
87
|
+
baseURL: "https://preview.example.test"
|
|
88
|
+
}),
|
|
89
|
+
/only sends the dev-auth secret to a local app URL/u
|
|
90
|
+
);
|
|
91
|
+
assert.equal(requestCount, 0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("local Playwright login requires one explicit existing-user identity", async () => {
|
|
95
|
+
let requestCount = 0;
|
|
96
|
+
const page = {
|
|
97
|
+
context() {
|
|
98
|
+
return {
|
|
99
|
+
request: {
|
|
100
|
+
async get() {
|
|
101
|
+
requestCount += 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
await assert.rejects(
|
|
109
|
+
loginAsExistingUser(page, { secret: "private-test-secret" }),
|
|
110
|
+
/exactly one of email or userId/u
|
|
111
|
+
);
|
|
112
|
+
assert.equal(requestCount, 0);
|
|
113
|
+
});
|