diff --git a/src/infrastructure/service-error.ts b/src/infrastructure/service-error.ts index 32049fd8..840b31cd 100644 --- a/src/infrastructure/service-error.ts +++ b/src/infrastructure/service-error.ts @@ -32,11 +32,14 @@ export class ServiceError { return new ServiceError(ServiceErrorCode.NotFound, customMessage, {}); } static unauthorizedWithHint(apiMessage: string | null): ServiceError { - const message = `${apiMessage ?? "You are not authorized to perform this action."} Please run ${f.cmdAlt( - "apimatic", - "auth", - "login" - )} to log in, or provide a valid auth key using the ${f.flag("auth-key")} flag.`; + // Both remedies name the full `auth login` command: the key is supplied to + // that command, not to whichever one hit the 401 — most of them don't accept + // an --auth-key flag at all. + const loginCommand = f.cmdAlt("apimatic", "auth", "login"); + const message = + `${apiMessage ?? "Authorization has been denied for this request."} ` + + `Please run ${loginCommand} to log in via browser, ` + + `or provide a valid auth key using the ${loginCommand} ${f.flag("auth-key")}`; return new ServiceError(ServiceErrorCode.UnAuthorized, message, {}); } diff --git a/src/prompts/auth/login.ts b/src/prompts/auth/login.ts index cb0ff80a..a0aef23c 100644 --- a/src/prompts/auth/login.ts +++ b/src/prompts/auth/login.ts @@ -1,5 +1,5 @@ import { log } from "@clack/prompts"; -import { ServiceError } from "../../infrastructure/service-error.js"; +import { ServiceError, ServiceErrorCode } from "../../infrastructure/service-error.js"; import { SubscriptionInfo } from "../../types/api/account.js"; import { Result } from "neverthrow"; import { withSpinner } from "../prompt.js"; @@ -14,8 +14,15 @@ export class LoginPrompts { } public invalidKeyProvided(serviceError: ServiceError) { + // A rejected key is the failure this reports, so key off the 401 rather than + // the network error it was previously matching. Compared by code, not by + // reference: `unauthorizedWithHint` and friends build fresh instances, so an + // identity check silently stops matching. Anything else (unreachable server, + // server error) already describes itself accurately. const message = - serviceError === ServiceError.NetworkError ? "Invalid API key provided." : serviceError.errorMessage; + serviceError.code === ServiceErrorCode.UnAuthorized + ? ServiceError.unauthorizedWithHint("Invalid API key provided.").errorMessage + : serviceError.errorMessage; log.error(message); } diff --git a/src/prompts/auth/status.ts b/src/prompts/auth/status.ts index 5b3f9786..40a1972e 100644 --- a/src/prompts/auth/status.ts +++ b/src/prompts/auth/status.ts @@ -17,7 +17,9 @@ export class StatusPrompts { } public notLoggedIn() { - log.error(`You are not logged in. Please run ${format.cmdAlt("apimatic", "auth", "login")} to log in.`); + // Same message as every 401 the services surface: the user isn't authorized + // to perform the action, whether that's caught locally or by the API. + log.error(ServiceError.unauthorizedWithHint(null).errorMessage); } public invalidKeyProvided(serviceError: ServiceError) {