kalvium-worklog 1.4.4 → 1.4.5
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/dist/capture-token.js +16 -223
- package/package.json +1 -1
package/dist/capture-token.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { PROFILE_DIR, TOKEN_FILE, ensureInstallDir, saveToken, } from "./config.js";
|
|
2
2
|
import * as readline from "readline";
|
|
3
|
-
import { createServer } from "http";
|
|
4
|
-
import { request as httpRequest } from "https";
|
|
5
|
-
import { execSync } from "child_process";
|
|
6
3
|
/**
|
|
7
4
|
* Check if Playwright is available on this platform.
|
|
8
5
|
* Playwright doesn't support Android/Termux.
|
|
@@ -50,231 +47,27 @@ export async function captureToken() {
|
|
|
50
47
|
return false;
|
|
51
48
|
}
|
|
52
49
|
/**
|
|
53
|
-
* Capture token
|
|
54
|
-
*
|
|
55
|
-
* Starts a local server on port 4567 that proxies kalvium.community.
|
|
56
|
-
* The user navigates to http://localhost:4567, logs in normally,
|
|
57
|
-
* and the proxy intercepts the Keycloak token exchange automatically.
|
|
58
|
-
*
|
|
59
|
-
* No javascript: injection, no bookmarklets — just log in normally.
|
|
50
|
+
* Capture token on platforms without Playwright (Android/Termux).
|
|
51
|
+
* Simple manual paste — user gets token from desktop and pastes it.
|
|
60
52
|
*/
|
|
61
53
|
async function captureTokenViaProxy() {
|
|
62
|
-
const PORT = 4567;
|
|
63
|
-
const TARGET = "kalvium.community";
|
|
64
|
-
const AUTH_TARGET = "auth.kalvium.community";
|
|
65
54
|
console.log("\n ========================================");
|
|
66
|
-
console.log("
|
|
55
|
+
console.log(" Token Setup (Android/Termux)");
|
|
67
56
|
console.log(" ========================================\n");
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const server = createServer((req, res) => {
|
|
71
|
-
// Handle CORS
|
|
72
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
73
|
-
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
74
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
75
|
-
if (req.method === "OPTIONS") {
|
|
76
|
-
res.writeHead(204);
|
|
77
|
-
res.end();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
81
|
-
// Check if we already captured the token — serve success page
|
|
82
|
-
if (url.pathname === "/__done__") {
|
|
83
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
84
|
-
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:40px">
|
|
85
|
-
<h2>✓ Token captured!</h2>
|
|
86
|
-
<p>You can close this tab and go back to Termux.</p>
|
|
87
|
-
</body></html>`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// Determine which backend to proxy to
|
|
91
|
-
let targetHost = TARGET;
|
|
92
|
-
let targetPath = req.url ?? "/";
|
|
93
|
-
// Route auth requests to auth.kalvium.community
|
|
94
|
-
if (url.pathname.startsWith("/auth/") || url.pathname.startsWith("/realms/")) {
|
|
95
|
-
targetHost = AUTH_TARGET;
|
|
96
|
-
targetPath = req.url ?? "/";
|
|
97
|
-
}
|
|
98
|
-
// Intercept token exchange requests
|
|
99
|
-
if (url.pathname.includes("openid-connect/token") &&
|
|
100
|
-
req.method === "POST") {
|
|
101
|
-
// Collect body
|
|
102
|
-
let body = "";
|
|
103
|
-
req.on("data", (chunk) => {
|
|
104
|
-
body += chunk;
|
|
105
|
-
});
|
|
106
|
-
req.on("end", () => {
|
|
107
|
-
// Forward to actual Keycloak
|
|
108
|
-
const proxyReq = httpRequest({
|
|
109
|
-
hostname: AUTH_TARGET,
|
|
110
|
-
port: 443,
|
|
111
|
-
path: url.pathname,
|
|
112
|
-
method: "POST",
|
|
113
|
-
headers: {
|
|
114
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
115
|
-
"content-length": Buffer.byteLength(body),
|
|
116
|
-
},
|
|
117
|
-
}, (proxyRes) => {
|
|
118
|
-
let responseBody = "";
|
|
119
|
-
proxyRes.on("data", (chunk) => {
|
|
120
|
-
responseBody += chunk;
|
|
121
|
-
});
|
|
122
|
-
proxyRes.on("end", () => {
|
|
123
|
-
// Try to capture token from response
|
|
124
|
-
try {
|
|
125
|
-
const data = JSON.parse(responseBody);
|
|
126
|
-
if (data.refresh_token) {
|
|
127
|
-
console.log(" Refresh token captured from proxy!");
|
|
128
|
-
capturedToken = data;
|
|
129
|
-
resolve(data);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// not JSON, ignore
|
|
134
|
-
}
|
|
135
|
-
// Send response back to browser
|
|
136
|
-
res.writeHead(proxyRes.statusCode ?? 200, {
|
|
137
|
-
"content-type": proxyRes.headers["content-type"] ?? "application/json",
|
|
138
|
-
"access-control-allow-origin": "*",
|
|
139
|
-
});
|
|
140
|
-
res.end(responseBody);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
proxyReq.on("error", () => {
|
|
144
|
-
res.writeHead(502);
|
|
145
|
-
res.end("Proxy error");
|
|
146
|
-
});
|
|
147
|
-
proxyReq.write(body);
|
|
148
|
-
proxyReq.end();
|
|
149
|
-
});
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// Proxy all other requests to kalvium.community
|
|
153
|
-
const proxyHeaders = {};
|
|
154
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
155
|
-
if (key === "host")
|
|
156
|
-
continue;
|
|
157
|
-
if (Array.isArray(value)) {
|
|
158
|
-
proxyHeaders[key] = value.join(", ");
|
|
159
|
-
}
|
|
160
|
-
else if (value) {
|
|
161
|
-
proxyHeaders[key] = value;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
proxyHeaders["host"] = targetHost;
|
|
165
|
-
const proxyReq = httpRequest({
|
|
166
|
-
hostname: targetHost,
|
|
167
|
-
port: 443,
|
|
168
|
-
path: targetPath,
|
|
169
|
-
method: req.method ?? "GET",
|
|
170
|
-
headers: proxyHeaders,
|
|
171
|
-
}, (proxyRes) => {
|
|
172
|
-
// Rewrite location headers to point back to proxy
|
|
173
|
-
const headers = {};
|
|
174
|
-
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
175
|
-
if (key === "location" && typeof value === "string") {
|
|
176
|
-
// Rewrite kalvium.community URLs to localhost
|
|
177
|
-
let rewritten = value
|
|
178
|
-
.replace(/https:\/\/kalvium\.community/g, `http://localhost:${PORT}`)
|
|
179
|
-
.replace(/https:\/\/auth\.kalvium\.community/g, `http://localhost:${PORT}`);
|
|
180
|
-
headers[key] = rewritten;
|
|
181
|
-
}
|
|
182
|
-
else if (key === "content-security-policy") {
|
|
183
|
-
// Relax CSP to allow the proxy to work
|
|
184
|
-
headers[key] = value;
|
|
185
|
-
}
|
|
186
|
-
else if (key === "strict-transport-security") {
|
|
187
|
-
// Skip HSTS
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
else if (Array.isArray(value)) {
|
|
191
|
-
headers[key] = value.join(", ");
|
|
192
|
-
}
|
|
193
|
-
else if (value) {
|
|
194
|
-
headers[key] = value;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// Rewrite set-cookie domains
|
|
198
|
-
if (headers["set-cookie"]) {
|
|
199
|
-
headers["set-cookie"] = headers["set-cookie"].replace(/domain=\.kalvium\.community/gi, `domain=localhost`);
|
|
200
|
-
}
|
|
201
|
-
res.writeHead(proxyRes.statusCode ?? 200, headers);
|
|
202
|
-
// For HTML responses, rewrite URLs
|
|
203
|
-
const contentType = proxyRes.headers["content-type"] ?? "";
|
|
204
|
-
if (contentType.includes("text/html") || contentType.includes("javascript") || contentType.includes("css")) {
|
|
205
|
-
let body = "";
|
|
206
|
-
proxyRes.on("data", (chunk) => {
|
|
207
|
-
body += chunk.toString();
|
|
208
|
-
});
|
|
209
|
-
proxyRes.on("end", () => {
|
|
210
|
-
// Rewrite absolute URLs to go through proxy
|
|
211
|
-
const rewritten = body
|
|
212
|
-
.replace(/https:\/\/kalvium\.community/g, `http://localhost:${PORT}`)
|
|
213
|
-
.replace(/https:\/\/auth\.kalvium\.community/g, `http://localhost:${PORT}`)
|
|
214
|
-
.replace(/https:\/\/student-api\.kalvium\.community/g, `http://localhost:${PORT}/api`);
|
|
215
|
-
res.end(rewritten);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
proxyRes.pipe(res);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
proxyReq.on("error", () => {
|
|
223
|
-
res.writeHead(502);
|
|
224
|
-
res.end("Proxy error — check your internet connection");
|
|
225
|
-
});
|
|
226
|
-
// Forward request body for POST/PUT
|
|
227
|
-
if (req.method === "POST" || req.method === "PUT") {
|
|
228
|
-
req.pipe(proxyReq);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
proxyReq.end();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
server.listen(PORT, () => {
|
|
235
|
-
console.log(` Proxy server running on http://localhost:${PORT}`);
|
|
236
|
-
});
|
|
237
|
-
// Timeout after 5 minutes
|
|
238
|
-
setTimeout(() => {
|
|
239
|
-
if (!capturedToken) {
|
|
240
|
-
server.close();
|
|
241
|
-
resolve(null);
|
|
242
|
-
}
|
|
243
|
-
}, 300000);
|
|
244
|
-
});
|
|
245
|
-
// Open the proxy URL in the phone's default browser
|
|
246
|
-
const proxyUrl = `http://localhost:${PORT}/internships`;
|
|
247
|
-
try {
|
|
248
|
-
if (process.platform === "android") {
|
|
249
|
-
execSync(`termux-open-url ${proxyUrl}`, { stdio: "ignore" });
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
execSync(`xdg-open ${proxyUrl} || open ${proxyUrl}`, {
|
|
253
|
-
stdio: "ignore",
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
console.log(` Opened ${proxyUrl} in your browser.`);
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
console.log(` Could not auto-open browser. Please open: ${proxyUrl}`);
|
|
260
|
-
}
|
|
261
|
-
console.log("\n ────────────────────────────────────────────");
|
|
262
|
-
console.log(" A page opened in your browser.");
|
|
263
|
-
console.log(" Just log in with Google normally.");
|
|
264
|
-
console.log(" The token is captured automatically — no");
|
|
265
|
-
console.log(" javascript: or bookmarks needed.");
|
|
57
|
+
console.log(" Playwright doesn't work on Android, so you");
|
|
58
|
+
console.log(" need to get your token from a desktop.\n");
|
|
266
59
|
console.log(" ────────────────────────────────────────────");
|
|
267
|
-
console.log("
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.log("
|
|
60
|
+
console.log(" ON YOUR DESKTOP/LAPTOP:");
|
|
61
|
+
console.log(" ────────────────────────────────────────────");
|
|
62
|
+
console.log(" 1. Install: npm install -g kalvium-worklog");
|
|
63
|
+
console.log(" 2. Run: kalvium-worklog login");
|
|
64
|
+
console.log(" 3. Log in with Google");
|
|
65
|
+
console.log(" 4. Run: cat ~/.kalvium/refresh_token.json");
|
|
66
|
+
console.log(" 5. Copy the entire JSON output\n");
|
|
67
|
+
console.log(" ────────────────────────────────────────────");
|
|
68
|
+
console.log(" THEN ON YOUR PHONE:");
|
|
69
|
+
console.log(" ────────────────────────────────────────────");
|
|
70
|
+
console.log(" 6. Paste the JSON below\n");
|
|
278
71
|
return captureTokenManual();
|
|
279
72
|
}
|
|
280
73
|
/**
|