Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,21 @@ private static JsonNode browserLogin(Arguments args, ApiClient api, CredentialSt
// the blank check below is the wait. Denied, expired, and unknown all arrive as
// failures and propagate: none of them will ever turn into an approval, and polling on
// would just burn the deadline.
JsonNode response = api.post("/api/v1/auth/device/token", poll, null);
JsonNode response;
try {
response = api.post("/api/v1/auth/device/token", poll, null);
} catch (CliFailure failure) {
// Nobody approved in time. Reporting the raw 410 leaves a person reading
// "REQUEST_REJECTED ... status 410" and looking for a fault; the request did
// exactly what it promised, and the answer is to run login again.
if (failure.status() != null && failure.status() == 410) {
throw new CliFailure(
5,
"DEVICE_AUTHORIZATION_EXPIRED",
"The approval window closed before the code was confirmed. Run login again.");
}
throw failure;
}
String token = response.path("accessToken").asText();
if (token.isBlank()) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ void stopServer() {
server.stop(0);
}

/**
* A device request that nobody confirmed in time is not a fault, and saying "REQUEST_REJECTED
* ... status 410" sends the reader looking for one. The answer is to run login again, so the
* CLI has to say that.
*/
@Test
void browserLoginExplainsAnApprovalWindowThatClosed() throws Exception {
server.removeContext("/");
server.createContext("/api/v1/auth/device/authorize", exchange -> respond(exchange, 201,
"{\"deviceCode\":\"device-1\",\"userCode\":\"ABCD-EFGH\","
+ "\"verificationUriComplete\":\"https://example.test/cli-auth?code=ABCD-EFGH\","
+ "\"intervalSeconds\":1}"));
server.createContext("/api/v1/auth/device/token", exchange -> respond(exchange, 410,
"{\"error\":\"expired_token\"}"));

Result result = run("", "--base-url", baseUrl, "--config-dir", tempDir.toString(),
"login", "--browser", "--no-open");

assertThat(result.exitCode()).isEqualTo(5);
assertThat(JSON.readTree(result.stderr()).path("error").path("code").asText())
.isEqualTo("DEVICE_AUTHORIZATION_EXPIRED");
assertThat(JSON.readTree(result.stderr()).path("error").path("message").asText())
.contains("Run login again");
}

@Test
void loginReadsPasswordFromStandardInputAndStoresTokenWithoutEchoingSecrets() throws Exception {
server.removeContext("/");
Expand Down