@valbuild/server 0.113.0 → 0.115.0
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/declarations/src/ValOps.d.ts +0 -7
- package/dist/declarations/src/ValOpsFS.d.ts +0 -7
- package/dist/declarations/src/ValOpsHttp.d.ts +0 -13
- package/dist/declarations/src/ValRouter.d.ts +26 -0
- package/dist/declarations/src/getValidationErrorFileRef.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/login.d.ts +43 -16
- package/dist/declarations/src/patchStore.d.ts +6 -6
- package/dist/valbuild-server.cjs.dev.js +184 -187
- package/dist/valbuild-server.cjs.prod.js +184 -187
- package/dist/valbuild-server.esm.js +183 -186
- package/package.json +4 -4
|
@@ -5352,13 +5352,6 @@ class ValOpsFS extends ValOps {
|
|
|
5352
5352
|
};
|
|
5353
5353
|
}
|
|
5354
5354
|
}
|
|
5355
|
-
async getCommitSummary() {
|
|
5356
|
-
return {
|
|
5357
|
-
error: {
|
|
5358
|
-
message: "Val is in development / local mode. Cannot generate summary"
|
|
5359
|
-
}
|
|
5360
|
-
};
|
|
5361
|
-
}
|
|
5362
5355
|
async getStat(params) {
|
|
5363
5356
|
// In ValOpsFS, we don't have a websocket server to listen to file changes so we use long-polling.
|
|
5364
5357
|
// If a file that Val depends on changes, we break the connection and tell the client to request again to get the latest values.
|
|
@@ -6536,9 +6529,6 @@ const MetadataRes = z.z.object({
|
|
|
6536
6529
|
metadata: Metadata,
|
|
6537
6530
|
type: z.z.union([z.z.literal("file"), z.z.literal("image")]).nullable()
|
|
6538
6531
|
});
|
|
6539
|
-
const SummaryResponse = z.z.object({
|
|
6540
|
-
commitSummary: z.z.string()
|
|
6541
|
-
});
|
|
6542
6532
|
const GetApplicablePatches = z.z.object({
|
|
6543
6533
|
patches: z.z.array(z.z.object({
|
|
6544
6534
|
path: z.z.string(),
|
|
@@ -6725,69 +6715,6 @@ class ValOpsHttp extends ValOps {
|
|
|
6725
6715
|
};
|
|
6726
6716
|
}
|
|
6727
6717
|
}
|
|
6728
|
-
async getCommitSummary(preparedCommit) {
|
|
6729
|
-
try {
|
|
6730
|
-
var _res$headers$get2;
|
|
6731
|
-
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit-summary`, {
|
|
6732
|
-
method: "POST",
|
|
6733
|
-
headers: {
|
|
6734
|
-
...this.authHeaders,
|
|
6735
|
-
"Content-Type": "application/json"
|
|
6736
|
-
},
|
|
6737
|
-
body: JSON.stringify({
|
|
6738
|
-
patchedSourceFiles: preparedCommit.patchedSourceFiles,
|
|
6739
|
-
previousSourceFiles: preparedCommit.previousSourceFiles
|
|
6740
|
-
})
|
|
6741
|
-
});
|
|
6742
|
-
if (res.ok) {
|
|
6743
|
-
const json = await res.json();
|
|
6744
|
-
const parsed = SummaryResponse.safeParse(json);
|
|
6745
|
-
if (parsed.success) {
|
|
6746
|
-
return {
|
|
6747
|
-
commitSummary: parsed.data.commitSummary
|
|
6748
|
-
};
|
|
6749
|
-
}
|
|
6750
|
-
console.error(`Could not parse summary response. Error: ${zodValidationError.fromError(parsed.error).toString()}`);
|
|
6751
|
-
return {
|
|
6752
|
-
error: {
|
|
6753
|
-
message: `Cannot get the summary of your changes. An error has been logged. Possible cause: the current version of Val might be too old. Please try again later or contact the developers on your team.`
|
|
6754
|
-
}
|
|
6755
|
-
};
|
|
6756
|
-
}
|
|
6757
|
-
if (res.status === 401) {
|
|
6758
|
-
console.error("Unauthorized to get summary");
|
|
6759
|
-
return {
|
|
6760
|
-
error: {
|
|
6761
|
-
message: "Could not get summary. Although your user is authorized, the application has authorization issues. Contact the developers on your team and ask them to verify the api keys."
|
|
6762
|
-
}
|
|
6763
|
-
};
|
|
6764
|
-
}
|
|
6765
|
-
const unknownErrorMessage = `Could not get summary. HTTP error: ${res.status} ${res.statusText}`;
|
|
6766
|
-
if ((_res$headers$get2 = res.headers.get("Content-Type")) !== null && _res$headers$get2 !== void 0 && _res$headers$get2.includes("application/json")) {
|
|
6767
|
-
const json = await res.json();
|
|
6768
|
-
const message = internal.getErrorMessageFromUnknownJson(json, unknownErrorMessage);
|
|
6769
|
-
console.error("Summary error:", message);
|
|
6770
|
-
return {
|
|
6771
|
-
error: {
|
|
6772
|
-
message
|
|
6773
|
-
}
|
|
6774
|
-
};
|
|
6775
|
-
}
|
|
6776
|
-
console.error(unknownErrorMessage);
|
|
6777
|
-
return {
|
|
6778
|
-
error: {
|
|
6779
|
-
message: unknownErrorMessage
|
|
6780
|
-
}
|
|
6781
|
-
};
|
|
6782
|
-
} catch (e) {
|
|
6783
|
-
console.error("Could not get summary (connection error?):", e);
|
|
6784
|
-
return {
|
|
6785
|
-
error: {
|
|
6786
|
-
message: `Could not get summary. Error: ${e instanceof Error ? e.message : JSON.stringify(e)}`
|
|
6787
|
-
}
|
|
6788
|
-
};
|
|
6789
|
-
}
|
|
6790
|
-
}
|
|
6791
6718
|
async getStat(params) {
|
|
6792
6719
|
if (!(params !== null && params !== void 0 && params.profileId)) {
|
|
6793
6720
|
return {
|
|
@@ -7115,7 +7042,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7115
7042
|
coreVersion: core.Internal.VERSION.core
|
|
7116
7043
|
})
|
|
7117
7044
|
}).then(async res => {
|
|
7118
|
-
var _res$headers$
|
|
7045
|
+
var _res$headers$get2;
|
|
7119
7046
|
if (res.ok) {
|
|
7120
7047
|
const parsed = SavePatchResponse.safeParse(await res.json());
|
|
7121
7048
|
if (parsed.success) {
|
|
@@ -7134,7 +7061,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7134
7061
|
message: "Conflict: " + (await res.text())
|
|
7135
7062
|
});
|
|
7136
7063
|
}
|
|
7137
|
-
if ((_res$headers$
|
|
7064
|
+
if ((_res$headers$get2 = res.headers.get("Content-Type")) !== null && _res$headers$get2 !== void 0 && _res$headers$get2.includes("application/json")) {
|
|
7138
7065
|
const json = await res.json();
|
|
7139
7066
|
const message = internal.getErrorMessageFromUnknownJson(json, "Unknown error");
|
|
7140
7067
|
return fp.result.err({
|
|
@@ -7417,51 +7344,9 @@ class ValOpsHttp extends ValOps {
|
|
|
7417
7344
|
};
|
|
7418
7345
|
});
|
|
7419
7346
|
}
|
|
7420
|
-
async getCommitMessage(preparedCommit) {
|
|
7421
|
-
var _res$headers$get4;
|
|
7422
|
-
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit-summary`, {
|
|
7423
|
-
method: "POST",
|
|
7424
|
-
headers: {
|
|
7425
|
-
...this.authHeaders,
|
|
7426
|
-
"Content-Type": "application/json"
|
|
7427
|
-
},
|
|
7428
|
-
body: JSON.stringify({
|
|
7429
|
-
patchedSourceFiles: preparedCommit.patchedSourceFiles,
|
|
7430
|
-
previousSourceFiles: preparedCommit.previousSourceFiles
|
|
7431
|
-
})
|
|
7432
|
-
});
|
|
7433
|
-
if (res.ok) {
|
|
7434
|
-
const json = await res.json();
|
|
7435
|
-
const parsed = SummaryResponse.safeParse(json);
|
|
7436
|
-
if (parsed.success) {
|
|
7437
|
-
return {
|
|
7438
|
-
commitSummary: parsed.data.commitSummary
|
|
7439
|
-
};
|
|
7440
|
-
}
|
|
7441
|
-
return {
|
|
7442
|
-
error: {
|
|
7443
|
-
message: `Could not parse commit summary response. Error: ${zodValidationError.fromError(parsed.error)}`
|
|
7444
|
-
}
|
|
7445
|
-
};
|
|
7446
|
-
}
|
|
7447
|
-
if ((_res$headers$get4 = res.headers.get("Content-Type")) !== null && _res$headers$get4 !== void 0 && _res$headers$get4.includes("application/json")) {
|
|
7448
|
-
const json = await res.json();
|
|
7449
|
-
const message = internal.getErrorMessageFromUnknownJson(json, "Unknown error");
|
|
7450
|
-
return {
|
|
7451
|
-
error: {
|
|
7452
|
-
message
|
|
7453
|
-
}
|
|
7454
|
-
};
|
|
7455
|
-
}
|
|
7456
|
-
return {
|
|
7457
|
-
error: {
|
|
7458
|
-
message: "Could not get commit message. HTTP error: " + res.status + " " + res.statusText
|
|
7459
|
-
}
|
|
7460
|
-
};
|
|
7461
|
-
}
|
|
7462
7347
|
async commit(prepared, message, committer, filesDirectory, newBranch) {
|
|
7463
7348
|
try {
|
|
7464
|
-
var _res$headers$
|
|
7349
|
+
var _res$headers$get3;
|
|
7465
7350
|
const existingBranch = this.branch;
|
|
7466
7351
|
const res = await fetch(`${this.contentUrl}/v1/${this.project}/commit`, {
|
|
7467
7352
|
method: "POST",
|
|
@@ -7498,7 +7383,7 @@ class ValOpsHttp extends ValOps {
|
|
|
7498
7383
|
}
|
|
7499
7384
|
};
|
|
7500
7385
|
}
|
|
7501
|
-
if ((_res$headers$
|
|
7386
|
+
if ((_res$headers$get3 = res.headers.get("Content-Type")) !== null && _res$headers$get3 !== void 0 && _res$headers$get3.includes("application/json")) {
|
|
7502
7387
|
const json = z.z.object({
|
|
7503
7388
|
isNotFastForward: z.z.boolean().optional()
|
|
7504
7389
|
}).safeParse(await res.json());
|
|
@@ -9151,49 +9036,6 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
9151
9036
|
});
|
|
9152
9037
|
}
|
|
9153
9038
|
},
|
|
9154
|
-
"/commit-summary": {
|
|
9155
|
-
GET: async req => {
|
|
9156
|
-
const cookies = req.cookies;
|
|
9157
|
-
const auth = getAuth(cookies);
|
|
9158
|
-
if (auth.error) {
|
|
9159
|
-
return {
|
|
9160
|
-
status: 401,
|
|
9161
|
-
json: {
|
|
9162
|
-
message: auth.error
|
|
9163
|
-
}
|
|
9164
|
-
};
|
|
9165
|
-
}
|
|
9166
|
-
const query = req.query;
|
|
9167
|
-
const patchIds = query.patch_id;
|
|
9168
|
-
const patches = await serverOps.fetchPatches({
|
|
9169
|
-
patchIds,
|
|
9170
|
-
excludePatchOps: false
|
|
9171
|
-
});
|
|
9172
|
-
const analysis = serverOps.analyzePatches(patches.patches, patches.commits, commit);
|
|
9173
|
-
const preparedCommit = await serverOps.prepare({
|
|
9174
|
-
...analysis,
|
|
9175
|
-
...patches
|
|
9176
|
-
});
|
|
9177
|
-
const res = await serverOps.getCommitSummary(preparedCommit);
|
|
9178
|
-
if (res.error) {
|
|
9179
|
-
console.error("Failed to summarize", res.error);
|
|
9180
|
-
return {
|
|
9181
|
-
status: 400,
|
|
9182
|
-
json: {
|
|
9183
|
-
message: res.error.message
|
|
9184
|
-
}
|
|
9185
|
-
};
|
|
9186
|
-
}
|
|
9187
|
-
return {
|
|
9188
|
-
status: 200,
|
|
9189
|
-
json: {
|
|
9190
|
-
baseSha: await serverOps.getBaseSha(),
|
|
9191
|
-
patchIds,
|
|
9192
|
-
commitSummary: res.commitSummary
|
|
9193
|
-
}
|
|
9194
|
-
};
|
|
9195
|
-
}
|
|
9196
|
-
},
|
|
9197
9039
|
"/save": {
|
|
9198
9040
|
POST: async req => {
|
|
9199
9041
|
const cookies = req.cookies;
|
|
@@ -9476,11 +9318,18 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
9476
9318
|
}
|
|
9477
9319
|
const json = await upstreamRes.json();
|
|
9478
9320
|
const wsUrl = options.valContentUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:") + `/v1/${options.project}/ai/connect`;
|
|
9321
|
+
// Forwarded as-is, minus anything that is not a string: which
|
|
9322
|
+
// providers are reachable is the content server's to decide, and
|
|
9323
|
+
// this only has to carry the answer without editorialising.
|
|
9324
|
+
const providers = Array.isArray(json.providers) ? json.providers.filter(provider => typeof provider === "string") : undefined;
|
|
9479
9325
|
return {
|
|
9480
9326
|
status: 200,
|
|
9481
9327
|
json: {
|
|
9482
9328
|
nonce: json.nonce,
|
|
9483
|
-
wsUrl
|
|
9329
|
+
wsUrl,
|
|
9330
|
+
...(providers ? {
|
|
9331
|
+
providers
|
|
9332
|
+
} : {})
|
|
9484
9333
|
}
|
|
9485
9334
|
};
|
|
9486
9335
|
} catch (err) {
|
|
@@ -10498,6 +10347,10 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10498
10347
|
const maybeValProject = opts.project || process.env.VAL_PROJECT;
|
|
10499
10348
|
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
10500
10349
|
const valContentUrl = opts.valContentUrl || process.env.VAL_CONTENT_URL || core.DEFAULT_CONTENT_HOST;
|
|
10350
|
+
warnIfInsecureUrls({
|
|
10351
|
+
valBuildUrl,
|
|
10352
|
+
valContentUrl
|
|
10353
|
+
});
|
|
10501
10354
|
if (isProxyMode) {
|
|
10502
10355
|
var _opts$versions, _opts$versions2;
|
|
10503
10356
|
if (!maybeApiKey || !maybeValSecret) {
|
|
@@ -10539,7 +10392,6 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10539
10392
|
};
|
|
10540
10393
|
} else {
|
|
10541
10394
|
const cwd = process.cwd();
|
|
10542
|
-
const valBuildUrl = opts.valBuildUrl || process.env.VAL_BUILD_URL || "https://admin.val.build";
|
|
10543
10395
|
return {
|
|
10544
10396
|
mode: "fs",
|
|
10545
10397
|
cwd,
|
|
@@ -10556,6 +10408,84 @@ async function initHandlerOptions(route, opts, config) {
|
|
|
10556
10408
|
}
|
|
10557
10409
|
}
|
|
10558
10410
|
|
|
10411
|
+
/**
|
|
10412
|
+
* Hosts we send credentials to, and what each one puts at risk. They differ:
|
|
10413
|
+
* only `valBuildUrl` hands back the app token that becomes the session cookie,
|
|
10414
|
+
* so a single shared sentence would overstate one and understate the other.
|
|
10415
|
+
*/
|
|
10416
|
+
|
|
10417
|
+
const CREDENTIAL_BEARING_URLS = ["valBuildUrl", "valContentUrl"];
|
|
10418
|
+
const WHAT_IS_AT_RISK = {
|
|
10419
|
+
valBuildUrl: "Val's api key is sent to this host, and the token it returns is what this server signs into the session cookie, " + "so both can be read - and the token replaced - by anyone on the network path.",
|
|
10420
|
+
valContentUrl: "Val's api key, or the caller's personal access token, is sent to this host, " + "so it can be read by anyone on the network path."
|
|
10421
|
+
};
|
|
10422
|
+
|
|
10423
|
+
// NOTE: `URL.hostname` keeps the brackets on an IPv6 literal, so this is
|
|
10424
|
+
// "[::1]" and not "::1" - and `http://[0:0:0:0:0:0:0:1]` normalises to the
|
|
10425
|
+
// same short form before it gets here. Dropping the brackets looks like a
|
|
10426
|
+
// tidy-up and silently stops matching IPv6 loopback.
|
|
10427
|
+
const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "[::1]"];
|
|
10428
|
+
|
|
10429
|
+
/**
|
|
10430
|
+
* The URL as it is safe to print. `http://user:pass@host` is a legal override,
|
|
10431
|
+
* and a warning about credential exposure that puts the password in the log
|
|
10432
|
+
* would be the very thing it is warning about.
|
|
10433
|
+
*/
|
|
10434
|
+
function forLog(parsed) {
|
|
10435
|
+
if (!parsed.username && !parsed.password) {
|
|
10436
|
+
return parsed.href;
|
|
10437
|
+
}
|
|
10438
|
+
const redacted = new URL(parsed.href);
|
|
10439
|
+
redacted.username = "";
|
|
10440
|
+
redacted.password = "";
|
|
10441
|
+
return `${redacted.href} (credentials redacted)`;
|
|
10442
|
+
}
|
|
10443
|
+
|
|
10444
|
+
/**
|
|
10445
|
+
* Returns a warning if `url` would send credentials somewhere they can be read
|
|
10446
|
+
* off the wire, or null if it is fine.
|
|
10447
|
+
*
|
|
10448
|
+
* Both URLs default to https, but each is overridable - `opts.valBuildUrl` /
|
|
10449
|
+
* `VAL_BUILD_URL`, `opts.valContentUrl` / `VAL_CONTENT_URL` - and neither
|
|
10450
|
+
* override has ever been scheme-checked. Point one at a plain http host and the
|
|
10451
|
+
* api key goes out in clear text, and whatever comes back is whatever the
|
|
10452
|
+
* network says: for `valBuildUrl` that includes the app token this server
|
|
10453
|
+
* re-signs into the session cookie.
|
|
10454
|
+
*
|
|
10455
|
+
* Loopback over http is exempt: that is a val.build running on the developer's
|
|
10456
|
+
* own machine, and there is no network to be on the wrong side of.
|
|
10457
|
+
*
|
|
10458
|
+
* This warns rather than throws. Both overrides are set by the operator, not by
|
|
10459
|
+
* an attacker, so this is a misconfiguration to surface - not untrusted input to
|
|
10460
|
+
* reject - and refusing to boot would break anyone deliberately pointing at an
|
|
10461
|
+
* internal http host today.
|
|
10462
|
+
*/
|
|
10463
|
+
function insecureUrlWarning(name, url) {
|
|
10464
|
+
let parsed;
|
|
10465
|
+
try {
|
|
10466
|
+
parsed = new URL(url);
|
|
10467
|
+
} catch {
|
|
10468
|
+
// NOTE: the URL is not echoed here. It did not parse, so there is nothing
|
|
10469
|
+
// to redact with, and an unparseable string can still hold a password.
|
|
10470
|
+
return `Val: ${name} is not a valid URL.`;
|
|
10471
|
+
}
|
|
10472
|
+
if (parsed.protocol === "https:") {
|
|
10473
|
+
return null;
|
|
10474
|
+
}
|
|
10475
|
+
if (parsed.protocol === "http:" && (LOOPBACK_HOSTNAMES.includes(parsed.hostname) || parsed.hostname.endsWith(".localhost"))) {
|
|
10476
|
+
return null;
|
|
10477
|
+
}
|
|
10478
|
+
return `Val: ${name} is set to ${forLog(parsed)}, which is not https. ` + `${WHAT_IS_AT_RISK[name]} ` + `Use https, or a loopback address for local development.`;
|
|
10479
|
+
}
|
|
10480
|
+
function warnIfInsecureUrls(urls) {
|
|
10481
|
+
for (const name of CREDENTIAL_BEARING_URLS) {
|
|
10482
|
+
const warning = insecureUrlWarning(name, urls[name]);
|
|
10483
|
+
if (warning) {
|
|
10484
|
+
console.warn(warning);
|
|
10485
|
+
}
|
|
10486
|
+
}
|
|
10487
|
+
}
|
|
10488
|
+
|
|
10559
10489
|
// TODO: remove
|
|
10560
10490
|
async function safeReadGit(cwd) {
|
|
10561
10491
|
async function findGitHead(currentDir, depth) {
|
|
@@ -12559,7 +12489,17 @@ async function findAndEvalValConfigFile(projectRoot) {
|
|
|
12559
12489
|
}
|
|
12560
12490
|
|
|
12561
12491
|
/**
|
|
12562
|
-
* The Val login
|
|
12492
|
+
* The Val login flow, as reusable primitives.
|
|
12493
|
+
*
|
|
12494
|
+
* This is an RFC 8628 device authorization grant. The shape that matters:
|
|
12495
|
+
* {@link ValDeviceAuthorization.deviceCode} is a secret this process holds and
|
|
12496
|
+
* polls with, while {@link ValDeviceAuthorization.userCode} is the short string
|
|
12497
|
+
* the human reads out of the terminal and types into a browser. Only the device
|
|
12498
|
+
* code can collect a token.
|
|
12499
|
+
*
|
|
12500
|
+
* Keep them apart. Show the user code; never print, log or put the device code
|
|
12501
|
+
* in a URL. An earlier version of this flow used one value for both jobs, which
|
|
12502
|
+
* meant anyone who saw the verification link could collect the token it led to.
|
|
12563
12503
|
*
|
|
12564
12504
|
* The CLI wraps these with terminal output, and `@valbuild/language-server`
|
|
12565
12505
|
* wraps them with LSP `window/showDocument` and progress reporting. Neither the
|
|
@@ -12573,6 +12513,21 @@ const DEFAULT_LOGIN_HOST = "https://admin.val.build";
|
|
|
12573
12513
|
function defaultHost() {
|
|
12574
12514
|
return process.env.VAL_BUILD_URL || DEFAULT_LOGIN_HOST;
|
|
12575
12515
|
}
|
|
12516
|
+
|
|
12517
|
+
/**
|
|
12518
|
+
* What gets shown on the approval screen so the person can tell which terminal
|
|
12519
|
+
* is asking. Self-reported and therefore a hint, not proof — the server treats
|
|
12520
|
+
* it as untrusted display text.
|
|
12521
|
+
*/
|
|
12522
|
+
function defaultDeviceName() {
|
|
12523
|
+
try {
|
|
12524
|
+
return `${os__default["default"].hostname()} (${os__default["default"].platform()})`;
|
|
12525
|
+
} catch {
|
|
12526
|
+
// hostname() can throw on locked-down containers. A missing device name is
|
|
12527
|
+
// not worth failing a login over; the server renders "Unknown".
|
|
12528
|
+
return "";
|
|
12529
|
+
}
|
|
12530
|
+
}
|
|
12576
12531
|
class ValLoginError extends Error {
|
|
12577
12532
|
constructor(code, message, details) {
|
|
12578
12533
|
super(message);
|
|
@@ -12582,20 +12537,24 @@ class ValLoginError extends Error {
|
|
|
12582
12537
|
}
|
|
12583
12538
|
}
|
|
12584
12539
|
|
|
12585
|
-
/** A login attempt that is waiting for the user to
|
|
12540
|
+
/** A login attempt that is waiting for the user to approve it in a browser. */
|
|
12586
12541
|
|
|
12587
12542
|
/**
|
|
12588
|
-
* Begin a login attempt. The caller is responsible for getting
|
|
12589
|
-
*
|
|
12543
|
+
* Begin a login attempt. The caller is responsible for getting the user code
|
|
12544
|
+
* and verification URL in front of the user.
|
|
12590
12545
|
*/
|
|
12591
12546
|
async function startValLogin(options = {}) {
|
|
12592
12547
|
var _response$headers$get;
|
|
12593
12548
|
const host = options.host ?? defaultHost();
|
|
12549
|
+
const deviceName = options.deviceName ?? defaultDeviceName();
|
|
12594
12550
|
const response = await fetch(`${host}/api/login`, {
|
|
12595
12551
|
method: "POST",
|
|
12596
12552
|
headers: {
|
|
12597
12553
|
"Content-Type": "application/json"
|
|
12598
|
-
}
|
|
12554
|
+
},
|
|
12555
|
+
body: JSON.stringify(deviceName ? {
|
|
12556
|
+
device_name: deviceName
|
|
12557
|
+
} : {})
|
|
12599
12558
|
});
|
|
12600
12559
|
if (response.status >= 500) {
|
|
12601
12560
|
const text = await response.text().catch(() => "");
|
|
@@ -12606,42 +12565,63 @@ async function startValLogin(options = {}) {
|
|
|
12606
12565
|
throw new ValLoginError("unexpected-content-type", "Unexpected failure while trying to login (content type was not JSON).", text ? `Server response: ${text} (status: ${response.status})` : `Status: ${response.status}`);
|
|
12607
12566
|
}
|
|
12608
12567
|
const json = await response.json();
|
|
12609
|
-
const
|
|
12610
|
-
const
|
|
12611
|
-
|
|
12612
|
-
|
|
12568
|
+
const deviceCode = json === null || json === void 0 ? void 0 : json.device_code;
|
|
12569
|
+
const userCode = json === null || json === void 0 ? void 0 : json.user_code;
|
|
12570
|
+
const verificationUri = json === null || json === void 0 ? void 0 : json.verification_uri;
|
|
12571
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string") {
|
|
12572
|
+
throw new ValLoginError("unexpected-response", "Unexpected response from the server. This version of Val may be too old for the login flow on this host — try updating @valbuild/cli.", JSON.stringify(json));
|
|
12613
12573
|
}
|
|
12614
12574
|
return {
|
|
12615
|
-
|
|
12616
|
-
|
|
12575
|
+
deviceCode,
|
|
12576
|
+
userCode,
|
|
12577
|
+
verificationUri,
|
|
12578
|
+
verificationUriComplete: typeof (json === null || json === void 0 ? void 0 : json.verification_uri_complete) === "string" ? json.verification_uri_complete : verificationUri,
|
|
12579
|
+
expiresInSeconds: typeof (json === null || json === void 0 ? void 0 : json.expires_in) === "number" ? json.expires_in : DEFAULT_LOGIN_EXPIRES_IN_SECONDS,
|
|
12580
|
+
intervalSeconds: typeof (json === null || json === void 0 ? void 0 : json.interval) === "number" ? json.interval : DEFAULT_LOGIN_POLL_INTERVAL_SECONDS
|
|
12617
12581
|
};
|
|
12618
12582
|
}
|
|
12619
|
-
|
|
12620
|
-
|
|
12583
|
+
|
|
12584
|
+
/** Fallbacks for a server that omits the optional RFC 8628 timing fields. */
|
|
12585
|
+
const DEFAULT_LOGIN_EXPIRES_IN_SECONDS = 600;
|
|
12586
|
+
const DEFAULT_LOGIN_POLL_INTERVAL_SECONDS = 5;
|
|
12621
12587
|
|
|
12622
12588
|
/**
|
|
12623
|
-
*
|
|
12589
|
+
* How much to add to the poll interval when the server answers `slow_down`.
|
|
12590
|
+
* RFC 8628 section 3.5 specifies 5 seconds.
|
|
12591
|
+
*/
|
|
12592
|
+
const SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
12593
|
+
|
|
12594
|
+
/**
|
|
12595
|
+
* Poll until the user approves the login in their browser.
|
|
12624
12596
|
*
|
|
12625
12597
|
* Accepts an `AbortSignal` so an editor can cancel the flow when the user
|
|
12626
|
-
* dismisses the prompt, instead of leaving a poll loop running
|
|
12598
|
+
* dismisses the prompt, instead of leaving a poll loop running to expiry.
|
|
12627
12599
|
*/
|
|
12628
|
-
async function awaitValLoginConfirmation(
|
|
12600
|
+
async function awaitValLoginConfirmation(authorization, options = {}) {
|
|
12629
12601
|
const host = options.host ?? defaultHost();
|
|
12630
|
-
const maxDuration = options.maxDurationMs ??
|
|
12631
|
-
const pollInterval = options.pollIntervalMs ?? DEFAULT_LOGIN_POLL_INTERVAL;
|
|
12602
|
+
const maxDuration = options.maxDurationMs ?? authorization.expiresInSeconds * 1000;
|
|
12632
12603
|
const now = options.now ?? (() => Date.now());
|
|
12604
|
+
// Mutable: `slow_down` widens it as we go, and never narrows it again.
|
|
12605
|
+
let intervalMs = authorization.intervalSeconds * 1000;
|
|
12633
12606
|
const start = now();
|
|
12634
12607
|
while (now() - start < maxDuration) {
|
|
12635
12608
|
var _options$signal, _options$signal2;
|
|
12636
12609
|
if ((_options$signal = options.signal) !== null && _options$signal !== void 0 && _options$signal.aborted) {
|
|
12637
12610
|
throw new ValLoginError("aborted", "Login was cancelled.");
|
|
12638
12611
|
}
|
|
12639
|
-
|
|
12612
|
+
// Wait first: the user has not had time to approve anything yet.
|
|
12613
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
12640
12614
|
if ((_options$signal2 = options.signal) !== null && _options$signal2 !== void 0 && _options$signal2.aborted) {
|
|
12641
12615
|
throw new ValLoginError("aborted", "Login was cancelled.");
|
|
12642
12616
|
}
|
|
12643
|
-
const response = await fetch(`${host}/api/login
|
|
12644
|
-
method: "POST"
|
|
12617
|
+
const response = await fetch(`${host}/api/login`, {
|
|
12618
|
+
method: "POST",
|
|
12619
|
+
headers: {
|
|
12620
|
+
"Content-Type": "application/json"
|
|
12621
|
+
},
|
|
12622
|
+
body: JSON.stringify({
|
|
12623
|
+
device_code: authorization.deviceCode
|
|
12624
|
+
})
|
|
12645
12625
|
});
|
|
12646
12626
|
if (response.status >= 500) {
|
|
12647
12627
|
throw new ValLoginError("server-error", "An error occurred on the server.", `Status: ${response.status}`);
|
|
@@ -12659,6 +12639,23 @@ async function awaitValLoginConfirmation(nonce, options = {}) {
|
|
|
12659
12639
|
}
|
|
12660
12640
|
throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
|
|
12661
12641
|
}
|
|
12642
|
+
const json = await response.json().catch(() => null);
|
|
12643
|
+
const error = typeof (json === null || json === void 0 ? void 0 : json.error) === "string" ? json.error : null;
|
|
12644
|
+
const description = typeof (json === null || json === void 0 ? void 0 : json.error_description) === "string" ? json.error_description : undefined;
|
|
12645
|
+
if (error === "authorization_pending") {
|
|
12646
|
+
continue;
|
|
12647
|
+
}
|
|
12648
|
+
if (error === "slow_down" || response.status === 429) {
|
|
12649
|
+
intervalMs += SLOW_DOWN_INCREMENT_SECONDS * 1000;
|
|
12650
|
+
continue;
|
|
12651
|
+
}
|
|
12652
|
+
if (error === "access_denied") {
|
|
12653
|
+
throw new ValLoginError("access-denied", "The login was declined in the browser.", description);
|
|
12654
|
+
}
|
|
12655
|
+
if (error === "expired_token") {
|
|
12656
|
+
throw new ValLoginError("expired", "The login code expired before it was approved.", description);
|
|
12657
|
+
}
|
|
12658
|
+
throw new ValLoginError("unexpected-response", "Unexpected response from the server.", JSON.stringify(json));
|
|
12662
12659
|
}
|
|
12663
12660
|
throw new ValLoginError("timeout", "Login confirmation timed out.");
|
|
12664
12661
|
}
|
|
@@ -12964,9 +12961,9 @@ Object.defineProperty(exports, 'hasRemoteFileSchema', {
|
|
|
12964
12961
|
enumerable: true,
|
|
12965
12962
|
get: function () { return core.hasRemoteFileSchema; }
|
|
12966
12963
|
});
|
|
12964
|
+
exports.DEFAULT_LOGIN_EXPIRES_IN_SECONDS = DEFAULT_LOGIN_EXPIRES_IN_SECONDS;
|
|
12967
12965
|
exports.DEFAULT_LOGIN_HOST = DEFAULT_LOGIN_HOST;
|
|
12968
|
-
exports.
|
|
12969
|
-
exports.DEFAULT_LOGIN_POLL_INTERVAL = DEFAULT_LOGIN_POLL_INTERVAL;
|
|
12966
|
+
exports.DEFAULT_LOGIN_POLL_INTERVAL_SECONDS = DEFAULT_LOGIN_POLL_INTERVAL_SECONDS;
|
|
12970
12967
|
exports.Service = Service;
|
|
12971
12968
|
exports.ValFSHost = ValFSHost;
|
|
12972
12969
|
exports.ValLoginError = ValLoginError;
|