@alphafox/cli 0.1.1 → 0.1.3
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/README.md +2 -2
- package/dist/http/client.js +64 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# alphafox-cli
|
|
2
2
|
|
|
3
3
|
Alphafox CLI (`alphafox`) — Agent and human entry for the versioned Public Application API on alphafox-web.
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ Alphafox CLI (`alphafox`) — Agent and human entry for the versioned Public App
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @alphafox/cli
|
|
9
9
|
# or
|
|
10
|
-
npx
|
|
10
|
+
npx alphafox-cli version
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick start
|
package/dist/http/client.js
CHANGED
|
@@ -50,13 +50,15 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
50
50
|
if (!options.skipAuth) {
|
|
51
51
|
const tokens = (0, store_1.loadTokens)(options.profile.name, env);
|
|
52
52
|
if (tokens) {
|
|
53
|
-
// Never send tokens to a different
|
|
53
|
+
// Never send tokens to a different site than the profile audience.
|
|
54
|
+
// Apex/www (and trailing host variants) of the same registrable domain
|
|
55
|
+
// are treated as equivalent so production domain redirects do not break CLI.
|
|
54
56
|
const tokenAudienceOrigin = originOf(tokens.audience);
|
|
55
57
|
const targetOrigin = originOf(url);
|
|
56
58
|
if (tokens.audience &&
|
|
57
59
|
tokenAudienceOrigin &&
|
|
58
60
|
targetOrigin &&
|
|
59
|
-
tokenAudienceOrigin
|
|
61
|
+
!sameAuthSite(tokenAudienceOrigin, targetOrigin)) {
|
|
60
62
|
throw Object.assign(new Error("Refusing to send stored tokens to a different origin than token audience (fail-closed)."), {
|
|
61
63
|
status: 403,
|
|
62
64
|
type: "authorization",
|
|
@@ -72,12 +74,15 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
72
74
|
const init = {
|
|
73
75
|
method: options.method.toUpperCase(),
|
|
74
76
|
headers,
|
|
77
|
+
// Follow redirects ourselves so Authorization is re-attached after apex→www.
|
|
78
|
+
// fetch()'s automatic redirect drops Authorization on cross-origin hops.
|
|
79
|
+
redirect: "manual",
|
|
75
80
|
};
|
|
76
81
|
if (options.body !== undefined) {
|
|
77
82
|
headers["Content-Type"] = "application/json";
|
|
78
83
|
init.body = JSON.stringify(options.body);
|
|
79
84
|
}
|
|
80
|
-
const response = await fetchImpl
|
|
85
|
+
const response = await fetchFollowingAuthRedirects(fetchImpl, url, init);
|
|
81
86
|
const bodyText = await response.text();
|
|
82
87
|
let json = null;
|
|
83
88
|
try {
|
|
@@ -97,7 +102,44 @@ async function apiRequest(options, env = process.env, fetchImpl = fetch) {
|
|
|
97
102
|
json,
|
|
98
103
|
};
|
|
99
104
|
}
|
|
105
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
106
|
+
/**
|
|
107
|
+
* Re-issue requests on same-site redirects while keeping Authorization.
|
|
108
|
+
* Needed because alphafox.app → www.alphafox.app is a cross-origin hop for fetch.
|
|
109
|
+
*/
|
|
110
|
+
async function fetchFollowingAuthRedirects(fetchImpl, startUrl, init, maxHops = 5) {
|
|
111
|
+
let url = startUrl;
|
|
112
|
+
let method = (init.method ?? "GET").toUpperCase();
|
|
113
|
+
let body = init.body;
|
|
114
|
+
let response = await fetchImpl(url, { ...init, method, body, redirect: "manual" });
|
|
115
|
+
for (let hop = 0; hop < maxHops && REDIRECT_STATUSES.has(response.status); hop++) {
|
|
116
|
+
const location = response.headers.get("location");
|
|
117
|
+
if (!location) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
const nextUrl = new URL(location, url).toString();
|
|
121
|
+
if (!sameAuthSite(originOf(url), originOf(nextUrl))) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
// 303 switches to GET without body; 301/302 historically do for non-GET.
|
|
125
|
+
if (response.status === 303 ||
|
|
126
|
+
((response.status === 301 || response.status === 302) && method !== "GET" && method !== "HEAD")) {
|
|
127
|
+
method = "GET";
|
|
128
|
+
body = undefined;
|
|
129
|
+
}
|
|
130
|
+
url = nextUrl;
|
|
131
|
+
response = await fetchImpl(url, {
|
|
132
|
+
...init,
|
|
133
|
+
method,
|
|
134
|
+
body,
|
|
135
|
+
redirect: "manual",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return response;
|
|
139
|
+
}
|
|
100
140
|
function originOf(value) {
|
|
141
|
+
if (!value)
|
|
142
|
+
return null;
|
|
101
143
|
try {
|
|
102
144
|
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
103
145
|
return new URL(value).origin;
|
|
@@ -108,3 +150,22 @@ function originOf(value) {
|
|
|
108
150
|
return null;
|
|
109
151
|
}
|
|
110
152
|
}
|
|
153
|
+
/** Apex and www hosts of the same site share auth tokens. */
|
|
154
|
+
function sameAuthSite(a, b) {
|
|
155
|
+
if (!a || !b)
|
|
156
|
+
return false;
|
|
157
|
+
if (a === b)
|
|
158
|
+
return true;
|
|
159
|
+
try {
|
|
160
|
+
const ua = new URL(a);
|
|
161
|
+
const ub = new URL(b);
|
|
162
|
+
if (ua.protocol !== ub.protocol)
|
|
163
|
+
return false;
|
|
164
|
+
const ha = ua.hostname.replace(/^www\./i, "").toLowerCase();
|
|
165
|
+
const hb = ub.hostname.replace(/^www\./i, "").toLowerCase();
|
|
166
|
+
return ha === hb && ua.port === ub.port;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CLI_CONTRACT_VERSION = exports.CLI_VERSION = exports.CLI_PACKAGE = exports.CLI_NAME = void 0;
|
|
4
4
|
exports.CLI_NAME = "alphafox";
|
|
5
5
|
exports.CLI_PACKAGE = "@alphafox/cli";
|
|
6
|
-
exports.CLI_VERSION = "0.1.
|
|
6
|
+
exports.CLI_VERSION = "0.1.2";
|
|
7
7
|
exports.CLI_CONTRACT_VERSION = "2026-08-11";
|