@aion0/forge 0.10.27 → 0.10.28
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/RELEASE_NOTES.md +4 -7
- package/lib/auth/login-status.ts +5 -85
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.28
|
|
2
2
|
|
|
3
3
|
Released: 2026-06-02
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.27
|
|
6
6
|
|
|
7
7
|
### Other
|
|
8
|
-
- fix(
|
|
9
|
-
- fix(http): scope slash-collapse to pathname only (don't touch query/fragment)
|
|
10
|
-
- fix(http): collapse double-slash in URLs (base_url trailing-slash bug)
|
|
11
|
-
- fix(connector-test): wrap applyAuth + route handler so errors come back as JSON
|
|
8
|
+
- fix(login-status): remove GitLab 2FA row — 2fa_verify is interactive, can't probe
|
|
12
9
|
|
|
13
10
|
|
|
14
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.
|
|
11
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.27...v0.10.28
|
package/lib/auth/login-status.ts
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
|
-
import { spawn } from 'node:child_process';
|
|
23
22
|
import { getDataDir } from '../dirs';
|
|
24
23
|
import { listConnectorIds, getConnector, getInstalledConnector } from '../connectors/registry';
|
|
25
24
|
import { runConnectorTest } from '../connectors/test-runner';
|
|
@@ -151,40 +150,15 @@ export function listSources(): LoginSource[] {
|
|
|
151
150
|
});
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
out.push({
|
|
160
|
-
id: 'ext:gitlab-2fa',
|
|
161
|
-
label: 'GitLab 2FA',
|
|
162
|
-
category: 'external',
|
|
163
|
-
detail: `ssh git@${gitlabHost} 2fa_verify (BatchMode)`,
|
|
164
|
-
refresh: {
|
|
165
|
-
kind: 'show-command',
|
|
166
|
-
command: `ssh git@${gitlabHost} 2fa_verify`,
|
|
167
|
-
description:
|
|
168
|
-
'Run in a terminal — it will prompt for your 2FA token. Once accepted, future git fetch/push work for a while.',
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
}
|
|
153
|
+
// GitLab 2FA used to be an external row here, but `2fa_verify` is
|
|
154
|
+
// interactive auth (not a probe) and bare SSH greet doesn't catch
|
|
155
|
+
// expiry. Removed in favour of a static hint on the GitLab
|
|
156
|
+
// connector itself — user copies the command when they actually
|
|
157
|
+
// hit a 2FA expiry instead of being nagged proactively.
|
|
172
158
|
|
|
173
159
|
return out;
|
|
174
160
|
}
|
|
175
161
|
|
|
176
|
-
/** Pull the gitlab connector's host from its installed base_url, if any. */
|
|
177
|
-
function getGitLabHost(): string | null {
|
|
178
|
-
try {
|
|
179
|
-
const inst = getInstalledConnector('gitlab');
|
|
180
|
-
const baseUrl = (inst?.config as any)?.base_url as string | undefined;
|
|
181
|
-
if (!baseUrl) return null;
|
|
182
|
-
return new URL(baseUrl).hostname || null;
|
|
183
|
-
} catch {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
162
|
// ─── Checks ─────────────────────────────────────────────────────────
|
|
189
163
|
|
|
190
164
|
/**
|
|
@@ -197,8 +171,6 @@ export async function checkSource(source: LoginSource): Promise<LoginCheckResult
|
|
|
197
171
|
|
|
198
172
|
if (source.id.startsWith('connector:')) {
|
|
199
173
|
r = await checkConnector(source.id.slice('connector:'.length));
|
|
200
|
-
} else if (source.id === 'ext:gitlab-2fa') {
|
|
201
|
-
r = await checkGitLab2fa();
|
|
202
174
|
} else {
|
|
203
175
|
r = { ok: false, message: `unknown source ${source.id}` };
|
|
204
176
|
}
|
|
@@ -229,58 +201,6 @@ async function checkConnector(
|
|
|
229
201
|
}
|
|
230
202
|
}
|
|
231
203
|
|
|
232
|
-
async function checkGitLab2fa(): Promise<Omit<LoginCheckResult, 'checked_at' | 'duration_ms'>> {
|
|
233
|
-
const host = getGitLabHost();
|
|
234
|
-
if (!host) {
|
|
235
|
-
return {
|
|
236
|
-
ok: false,
|
|
237
|
-
message: 'gitlab connector not installed or base_url empty',
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
// Passive probe: SSH to the host with NO command. gitlab-shell prints
|
|
241
|
-
// "Welcome to GitLab, @user!" then closes — only triggers SSH key auth,
|
|
242
|
-
// never the interactive 2fa_verify push. Used here purely to confirm
|
|
243
|
-
// the SSH path + key + server are all reachable; if your setup gates
|
|
244
|
-
// git operations behind a separate 2FA window, this won't catch that
|
|
245
|
-
// (no known passive 2FA-status command).
|
|
246
|
-
return new Promise((resolve) => {
|
|
247
|
-
const proc = spawn(
|
|
248
|
-
'ssh',
|
|
249
|
-
['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=30', `git@${host}`],
|
|
250
|
-
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
|
251
|
-
);
|
|
252
|
-
let stdout = '';
|
|
253
|
-
let stderr = '';
|
|
254
|
-
const timer = setTimeout(() => {
|
|
255
|
-
try { proc.kill('SIGTERM'); } catch {}
|
|
256
|
-
resolve({ ok: false, message: 'timeout after 35s — host unreachable' });
|
|
257
|
-
}, 35_000);
|
|
258
|
-
proc.stdout?.on('data', (b) => { stdout += b.toString('utf-8'); });
|
|
259
|
-
proc.stderr?.on('data', (b) => { stderr += b.toString('utf-8'); });
|
|
260
|
-
proc.on('close', (code) => {
|
|
261
|
-
clearTimeout(timer);
|
|
262
|
-
const combined = (stdout + '\n' + stderr).trim().replace(/\s+/g, ' ');
|
|
263
|
-
// gitlab-shell exits 0 on greeting; "Welcome to GitLab" is the
|
|
264
|
-
// success marker regardless of exit code (some setups exit 1 with
|
|
265
|
-
// PTY warnings on stderr but still authenticate).
|
|
266
|
-
const welcomed = /Welcome to GitLab/i.test(combined);
|
|
267
|
-
if (welcomed) {
|
|
268
|
-
const m = combined.match(/Welcome to GitLab,?\s*@?[\w.-]+!?/i);
|
|
269
|
-
resolve({ ok: true, message: m ? m[0] : 'gitlab-shell greeted ok' });
|
|
270
|
-
} else {
|
|
271
|
-
resolve({
|
|
272
|
-
ok: false,
|
|
273
|
-
message: `exit ${code}: ${combined.slice(0, 400) || '(no output)'}`,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
proc.on('error', (e) => {
|
|
278
|
-
clearTimeout(timer);
|
|
279
|
-
resolve({ ok: false, message: e.message });
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
204
|
// ─── Bulk view ──────────────────────────────────────────────────────
|
|
285
205
|
|
|
286
206
|
export function listStatus(): LoginStatusRow[] {
|
package/package.json
CHANGED