From 8bb35eb59b17a0ecf3d9f1bb2a3080c1c03b0099 Mon Sep 17 00:00:00 2001 From: MuhammadRafay1 Date: Thu, 30 Jul 2026 15:29:21 +0500 Subject: [PATCH 1/2] fix: report a rejected auth key accurately on login `auth login --auth-key ` reported "Unauthorized access." while an unreachable server reported "Invalid API key provided." The two conditions were inverted, and neither message told the user what to do next. - Key the login prompt off `ServiceErrorCode.UnAuthorized` rather than comparing the `NetworkError` singleton by reference, so a rejected key is named as such and connection failures report themselves. Comparing by code also survives factory-built errors, which an identity check silently stops matching. - Build that message from `ServiceError.unauthorizedWithHint` so the login / `--auth-key` guidance is worded identically to every other auth failure. - Reuse the same hint for the logged-out `auth status` message. - Reword the shared 401 fallback to "Authorization has been denied for this request." Co-Authored-By: Claude Opus 5 (1M context) --- src/infrastructure/service-error.ts | 2 +- src/prompts/auth/login.ts | 11 +++++++++-- src/prompts/auth/status.ts | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/service-error.ts b/src/infrastructure/service-error.ts index 32049fd8..2b60e54b 100644 --- a/src/infrastructure/service-error.ts +++ b/src/infrastructure/service-error.ts @@ -32,7 +32,7 @@ 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( + const message = `${apiMessage ?? "Authorization has been denied for this request."} Please run ${f.cmdAlt( "apimatic", "auth", "login" 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) { From 2ad98cd7be0193455bf31d7a9e3a1f470cf63e46 Mon Sep 17 00:00:00 2001 From: MuhammadRafay1 Date: Thu, 30 Jul 2026 18:15:08 +0500 Subject: [PATCH 2/2] fix: point the auth hint at the command that accepts the key The hint told users to pass --auth-key to whichever command hit the 401, but most of them don't declare that flag: `auth status` and `portal toc new` reject it outright. Name `apimatic auth login --auth-key` instead, so the remedy is valid wherever the message surfaces, and spell out that a bare `auth login` authenticates via the browser. Co-Authored-By: Claude Opus 5 (1M context) --- src/infrastructure/service-error.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/infrastructure/service-error.ts b/src/infrastructure/service-error.ts index 2b60e54b..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 ?? "Authorization has been denied for this request."} 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, {}); }