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.
Files changed (2) hide show
  1. package/dist/capture-token.js +16 -223
  2. package/package.json +1 -1
@@ -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 via reverse proxy (for Android/Termux without Playwright).
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(" Reverse proxy token capture (no Playwright)");
55
+ console.log(" Token Setup (Android/Termux)");
67
56
  console.log(" ========================================\n");
68
- let capturedToken = null;
69
- const tokenPromise = new Promise((resolve) => {
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("\n Waiting for login (up to 5 minutes)...\n");
268
- const data = await tokenPromise;
269
- if (data) {
270
- saveToken(data);
271
- console.log(`\n✓ Token captured and saved to ${TOKEN_FILE}`);
272
- console.log(` Access token expires in: ${data.expires_in ?? "?"} seconds`);
273
- console.log(` Refresh token expires in: ${data.refresh_expires_in ?? "?"} seconds`);
274
- return true;
275
- }
276
- // Fallback: manual paste
277
- console.log("\n Proxy capture failed. Let's try manual paste.");
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kalvium-worklog",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "description": "Auto-submit your daily Kalvium worklog without opening the website",
5
5
  "keywords": [
6
6
  "kalvium",