@aexol/spectral 0.9.41 → 0.9.42
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/auth-helper.d.ts.map +1 -1
- package/dist/auth-helper.js +49 -11
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-helper.d.ts","sourceRoot":"","sources":["../src/auth-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"auth-helper.d.ts","sourceRoot":"","sources":["../src/auth-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA8FD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,GAAE,MAAyB,GACnC,OAAO,CAAC,WAAW,CAAC,CA8DtB;AAMD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAkDrE"}
|
package/dist/auth-helper.js
CHANGED
|
@@ -33,6 +33,34 @@ function deriveLandingUrl(backendUrl) {
|
|
|
33
33
|
}
|
|
34
34
|
return DEFAULT_BACKEND_URL;
|
|
35
35
|
}
|
|
36
|
+
function decodeJwtExp(jwt) {
|
|
37
|
+
const parts = jwt.split(".");
|
|
38
|
+
if (parts.length < 2)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const payloadJson = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
42
|
+
const payload = JSON.parse(payloadJson);
|
|
43
|
+
return typeof payload.exp === "number" ? payload.exp : null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isJwtExpired(jwt, skewSec = 60) {
|
|
50
|
+
const exp = decodeJwtExp(jwt);
|
|
51
|
+
if (exp === null)
|
|
52
|
+
return true;
|
|
53
|
+
return Date.now() / 1000 + skewSec >= exp;
|
|
54
|
+
}
|
|
55
|
+
function validateCallbackToken(token) {
|
|
56
|
+
if (!token || token.split(".").length !== 3) {
|
|
57
|
+
return { ok: false, reason: "Authorization callback returned an invalid token format." };
|
|
58
|
+
}
|
|
59
|
+
if (isJwtExpired(token)) {
|
|
60
|
+
return { ok: false, reason: "Authorization callback returned an expired token." };
|
|
61
|
+
}
|
|
62
|
+
return { ok: true };
|
|
63
|
+
}
|
|
36
64
|
/**
|
|
37
65
|
* Start a temporary HTTP server to receive the OAuth callback.
|
|
38
66
|
* Returns the server and a promise that resolves with the JWT token.
|
|
@@ -104,6 +132,10 @@ export async function runOAuthFlow(apiUrl, timeoutMs = OAUTH_TIMEOUT_MS) {
|
|
|
104
132
|
}
|
|
105
133
|
});
|
|
106
134
|
const token = await tokenPromise;
|
|
135
|
+
const validation = validateCallbackToken(token);
|
|
136
|
+
if (!validation.ok) {
|
|
137
|
+
return { ok: false, reason: validation.reason };
|
|
138
|
+
}
|
|
107
139
|
// Save the token, preserving existing config (especially teamApiKey).
|
|
108
140
|
const cfg = existingCfg ?? {
|
|
109
141
|
apiUrl: resolvedApiUrl,
|
|
@@ -159,19 +191,25 @@ export async function ensureAuthenticated() {
|
|
|
159
191
|
const token = existingCfg?.teamApiKey || existingCfg?.userJwt || "";
|
|
160
192
|
// If we have a token, verify it still works
|
|
161
193
|
if (token) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Token is still valid
|
|
166
|
-
return { ok: true, config: existingCfg };
|
|
194
|
+
// If the stored user JWT is locally known to be expired, skip the network roundtrip
|
|
195
|
+
if (existingCfg?.userJwt && isJwtExpired(existingCfg.userJwt)) {
|
|
196
|
+
// fall through to OAuth
|
|
167
197
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
198
|
+
else {
|
|
199
|
+
try {
|
|
200
|
+
const client = new AexolMcpClient(apiUrl, token);
|
|
201
|
+
await client.listTools();
|
|
202
|
+
// Token is still valid
|
|
203
|
+
return { ok: true, config: existingCfg };
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (!isAuthError(err)) {
|
|
207
|
+
// Non-auth error (network, server down) — don't re-auth, just report
|
|
208
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
209
|
+
return { ok: false, config: existingCfg, reason: `Backend unreachable: ${msg}` };
|
|
210
|
+
}
|
|
211
|
+
// Token is invalid/expired — fall through to OAuth
|
|
173
212
|
}
|
|
174
|
-
// Token is invalid/expired — fall through to OAuth
|
|
175
213
|
}
|
|
176
214
|
}
|
|
177
215
|
// No valid token — run OAuth flow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexol/spectral",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.42",
|
|
4
4
|
"description": "AI coding agent for Aexol with relay-based browser access.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@silvia-odwyer/photon-node": "^0.3.3",
|
|
76
76
|
"better-sqlite3": "^12.9.0",
|
|
77
77
|
"chalk": "^5.6.2",
|
|
78
|
+
"cheerio": "^1.2.0",
|
|
78
79
|
"cross-spawn": "^7.0.6",
|
|
79
80
|
"diff": "^8.0.0",
|
|
80
81
|
"get-east-asian-width": "^1.3.0",
|