@tityra/cli 1.1.3 → 1.1.4
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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/services/authService.js +34 -13
- package/dist/services/inputService.js +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.4
|
|
4
|
+
|
|
5
|
+
- Stop cancelled OAuth sign-in during discovery and token exchange without saving credentials.
|
|
6
|
+
- Reject malformed UTF-8 JSON from files and standard input before sending account changes.
|
|
7
|
+
- Update the development dependency override for the minimatch security fix.
|
|
8
|
+
|
|
3
9
|
## 1.1.3
|
|
4
10
|
|
|
5
11
|
- Reject monitoring enablement before transport and direct customers to recurring credit approval in Tityra.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Tityra's website content audits review content delivery, structure and available
|
|
|
6
6
|
|
|
7
7
|
## Project status
|
|
8
8
|
|
|
9
|
-
Version `1.1.
|
|
9
|
+
Version `1.1.4` stops cancelled OAuth sign-in from continuing authorisation or saving credentials, and rejects malformed UTF-8 JSON before sending account changes. Credit purchases and spending approval take place in Tityra. Stable releases use npm's `latest` tag; check the [npm package page](https://www.npmjs.com/package/@tityra/cli) for the currently published version. CI targets Node.js 22 and 24 on Linux, macOS and Windows; local regression tests use isolated fixtures rather than live customer accounts.
|
|
10
10
|
|
|
11
11
|
## Basic functionality
|
|
12
12
|
|
|
@@ -26,6 +26,10 @@ const opaque = (value) => typeof value === 'string' &&
|
|
|
26
26
|
value.length <= 8192 &&
|
|
27
27
|
/^[A-Za-z0-9._~+/-]+=*$/.test(value);
|
|
28
28
|
const loopback = (hostname) => ['127.0.0.1', 'localhost', '[::1]'].includes(hostname);
|
|
29
|
+
function assertAuthorizationActive(signal) {
|
|
30
|
+
if (signal?.aborted)
|
|
31
|
+
throw new AuthError('Authorisation was cancelled.', 'authorization_cancelled');
|
|
32
|
+
}
|
|
29
33
|
export function normalizeBaseUrl(value = DEFAULT_BASE_URL, allowLocalhost = false) {
|
|
30
34
|
let url;
|
|
31
35
|
try {
|
|
@@ -60,18 +64,23 @@ function endpoint(value, issuer) {
|
|
|
60
64
|
return url.href;
|
|
61
65
|
}
|
|
62
66
|
async function jsonRequest(url, init = {}, operation = 'authorisation') {
|
|
67
|
+
assertAuthorizationActive(init.signal);
|
|
63
68
|
let response;
|
|
64
69
|
try {
|
|
65
70
|
response = await fetch(url, {
|
|
66
71
|
...init,
|
|
67
72
|
redirect: 'error',
|
|
68
73
|
credentials: 'omit',
|
|
69
|
-
signal: init.signal
|
|
74
|
+
signal: init.signal
|
|
75
|
+
? AbortSignal.any([init.signal, AbortSignal.timeout(15000)])
|
|
76
|
+
: AbortSignal.timeout(15000),
|
|
70
77
|
});
|
|
71
78
|
}
|
|
72
79
|
catch {
|
|
80
|
+
assertAuthorizationActive(init.signal);
|
|
73
81
|
throw new AuthError(`The ${operation} request could not be completed. No automatic retry was made.`, 'network_error');
|
|
74
82
|
}
|
|
83
|
+
assertAuthorizationActive(init.signal);
|
|
75
84
|
if (!response.ok) {
|
|
76
85
|
await response.body?.cancel();
|
|
77
86
|
throw new AuthError(`The Tityra server rejected the ${operation} request (HTTP ${response.status}).`, 'oauth_request_failed', response.status);
|
|
@@ -101,11 +110,13 @@ async function jsonRequest(url, init = {}, operation = 'authorisation') {
|
|
|
101
110
|
catch (error) {
|
|
102
111
|
if (error instanceof AuthError)
|
|
103
112
|
throw error;
|
|
113
|
+
assertAuthorizationActive(init.signal);
|
|
104
114
|
throw new AuthError('The OAuth response could not be read. No automatic retry was made.', 'network_error');
|
|
105
115
|
}
|
|
106
116
|
finally {
|
|
107
117
|
reader.releaseLock();
|
|
108
118
|
}
|
|
119
|
+
assertAuthorizationActive(init.signal);
|
|
109
120
|
const bytes = new Uint8Array(size);
|
|
110
121
|
let offset = 0;
|
|
111
122
|
for (const chunk of chunks) {
|
|
@@ -122,9 +133,9 @@ async function jsonRequest(url, init = {}, operation = 'authorisation') {
|
|
|
122
133
|
}
|
|
123
134
|
throw new AuthError('The Tityra server returned an invalid OAuth response.', 'invalid_response');
|
|
124
135
|
}
|
|
125
|
-
export async function discoverAuthorizationServer(baseUrl = DEFAULT_BASE_URL, allowLocalhost = false) {
|
|
136
|
+
export async function discoverAuthorizationServer(baseUrl = DEFAULT_BASE_URL, allowLocalhost = false, signal) {
|
|
126
137
|
const issuer = normalizeBaseUrl(baseUrl, allowLocalhost);
|
|
127
|
-
const metadata = await jsonRequest(issuer + '/.well-known/oauth-authorization-server', {}, 'discovery');
|
|
138
|
+
const metadata = await jsonRequest(issuer + '/.well-known/oauth-authorization-server', { signal }, 'discovery');
|
|
128
139
|
if (metadata.issuer !== issuer ||
|
|
129
140
|
!Array.isArray(metadata.code_challenge_methods_supported) ||
|
|
130
141
|
!metadata.code_challenge_methods_supported.includes('S256') ||
|
|
@@ -281,7 +292,8 @@ const postForm = (body) => ({
|
|
|
281
292
|
body,
|
|
282
293
|
});
|
|
283
294
|
export async function login(options = {}) {
|
|
284
|
-
|
|
295
|
+
assertAuthorizationActive(options.signal);
|
|
296
|
+
const metadata = await discoverAuthorizationServer(options.baseUrl, options.allowLocalhost, options.signal);
|
|
285
297
|
const scopes = [...new Set(options.scopes || DEFAULT_SCOPES)];
|
|
286
298
|
if (!scopes.length || scopes.some((scope) => !metadata.scopes_supported.includes(scope)))
|
|
287
299
|
throw new AuthError('Request one or more supported OAuth permissions.', 'invalid_scope');
|
|
@@ -296,6 +308,7 @@ export async function login(options = {}) {
|
|
|
296
308
|
try {
|
|
297
309
|
const client = await jsonRequest(metadata.registration_endpoint, {
|
|
298
310
|
method: 'POST',
|
|
311
|
+
signal: options.signal,
|
|
299
312
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
300
313
|
body: JSON.stringify({
|
|
301
314
|
client_name: 'Tityra CLI',
|
|
@@ -325,21 +338,29 @@ export async function login(options = {}) {
|
|
|
325
338
|
state,
|
|
326
339
|
}))
|
|
327
340
|
url.searchParams.set(key, value);
|
|
341
|
+
assertAuthorizationActive(options.signal);
|
|
328
342
|
if (options.onAuthorize)
|
|
329
343
|
await options.onAuthorize(url.href);
|
|
330
344
|
else
|
|
331
345
|
process.stderr.write(`Open this URL in your browser to authorise Tityra CLI:\n${url.href}\n`);
|
|
332
346
|
const code = await callback.code;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
assertAuthorizationActive(options.signal);
|
|
348
|
+
const tokens = await jsonRequest(metadata.token_endpoint, {
|
|
349
|
+
...postForm(new URLSearchParams({
|
|
350
|
+
grant_type: 'authorization_code',
|
|
351
|
+
client_id: client.client_id,
|
|
352
|
+
code,
|
|
353
|
+
redirect_uri: callback.redirectUri,
|
|
354
|
+
code_verifier: verifier,
|
|
355
|
+
resource: metadata.issuer + '/mcp',
|
|
356
|
+
})),
|
|
357
|
+
signal: options.signal,
|
|
358
|
+
}, 'token exchange');
|
|
341
359
|
const credentials = tokenCredentials(tokens, metadata.issuer, client.client_id, scopes);
|
|
342
|
-
await (options.store || new CredentialStore()).
|
|
360
|
+
await (options.store || new CredentialStore()).withLock(metadata.issuer, async (locked) => {
|
|
361
|
+
assertAuthorizationActive(options.signal);
|
|
362
|
+
await locked.write(credentials);
|
|
363
|
+
});
|
|
343
364
|
return {
|
|
344
365
|
issuer: credentials.issuer,
|
|
345
366
|
scopes: credentials.scopes,
|
|
@@ -58,7 +58,7 @@ export async function writeInput(kind, options) {
|
|
|
58
58
|
usage('Input must not exceed 64 KiB.');
|
|
59
59
|
let input;
|
|
60
60
|
try {
|
|
61
|
-
input = JSON.parse(
|
|
61
|
+
input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
62
62
|
}
|
|
63
63
|
catch {
|
|
64
64
|
usage('The input is not valid JSON.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tityra/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Official Tityra CLI for projects, audits, reports and findings. Built for developers, AI agents and automated workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -73,6 +73,9 @@
|
|
|
73
73
|
"@types/prompts": "2.4.9"
|
|
74
74
|
},
|
|
75
75
|
"overrides": {
|
|
76
|
+
"@typescript-eslint/typescript-estree": {
|
|
77
|
+
"minimatch": "11.0.4"
|
|
78
|
+
},
|
|
76
79
|
"markdownlint": {
|
|
77
80
|
"re2js": "2.9.1"
|
|
78
81
|
},
|