diff --git a/core/src/commands/login.ts b/core/src/commands/login.ts index 7811e995ce..da659a8e59 100644 --- a/core/src/commands/login.ts +++ b/core/src/commands/login.ts @@ -39,11 +39,23 @@ export class LoginCommand extends Command { // The Enterprise API is missing from the Garden class for commands with noProject // so we initialize it here. - const enterpriseApi = await EnterpriseApi.factory({ log, currentDirectory, skipLogging: true }) - if (enterpriseApi) { - log.info({ msg: `You're already logged in to Garden Enteprise.` }) - enterpriseApi.close() - return {} + try { + const enterpriseApi = await EnterpriseApi.factory({ log, currentDirectory, skipLogging: true }) + if (enterpriseApi) { + log.info({ msg: `You're already logged in to Garden Enteprise.` }) + enterpriseApi.close() + return {} + } + } catch (err) { + if (err?.detail?.statusCode === 401) { + const msg = dedent` + Looks like your session token is invalid. If you were previously logged into a different instance + of Garden Enterprise, log out first before logging in. + ` + log.warn({ msg, symbol: "warning" }) + log.info("") + } + throw err } const config = await getEnterpriseConfig(currentDirectory) diff --git a/core/src/enterprise/api.ts b/core/src/enterprise/api.ts index 419a107f07..deb608845a 100644 --- a/core/src/enterprise/api.ts +++ b/core/src/enterprise/api.ts @@ -10,7 +10,7 @@ import { IncomingHttpHeaders } from "http" import { got, GotHeaders, GotHttpError } from "../util/http" import { findProjectConfig } from "../config/base" -import { CommandError, EnterpriseApiError, RuntimeError } from "../exceptions" +import { CommandError, EnterpriseApiError } from "../exceptions" import { LogEntry } from "../logger/log-entry" import { gardenEnv } from "../constants" import { ClientAuthToken } from "../db/entities/client-auth-token" @@ -268,7 +268,8 @@ export class EnterpriseApi { private async refreshToken(token: ClientAuthToken) { try { - const res = await this.get("token/refresh", { + let res: any + res = await this.get("token/refresh", { Cookie: `rt=${token?.refreshToken}`, }) @@ -290,7 +291,11 @@ export class EnterpriseApi { await EnterpriseApi.saveAuthToken(this.log, tokenObj) } catch (err) { this.log.debug({ msg: `Failed to refresh the token.` }) - throw new RuntimeError(`An error occurred while verifying client auth token with platform: ${err.message}`, {}) + const detail = is401Error(err) ? { statusCode: err.response.statusCode } : {} + throw new EnterpriseApiError( + `An error occurred while verifying client auth token with Garden Enterprise: ${err.message}`, + detail + ) } } @@ -355,15 +360,18 @@ export class EnterpriseApi { async checkClientAuthToken(): Promise { let valid = false try { - this.log.debug(`Checking client auth token with platform: ${this.domain}/token/verify`) + this.log.debug(`Checking client auth token with Garden Enterprise: ${this.domain}/token/verify`) await this.get("token/verify") valid = true } catch (err) { if (!is401Error(err)) { - throw new RuntimeError(`An error occurred while verifying client auth token with platform: ${err.message}`, {}) + throw new EnterpriseApiError( + `An error occurred while verifying client auth token with Garden Enterprise: ${err.message}`, + {} + ) } } - this.log.debug(`Checked client auth token with platform - valid: ${valid}`) + this.log.debug(`Checked client auth token with Garden Enterprise - valid: ${valid}`) return valid } } diff --git a/core/test/unit/src/commands/login.ts b/core/test/unit/src/commands/login.ts index d8f658bf7f..3cb2c1018b 100644 --- a/core/test/unit/src/commands/login.ts +++ b/core/test/unit/src/commands/login.ts @@ -16,10 +16,11 @@ import stripAnsi from "strip-ansi" import { makeDummyGarden } from "../../../../src/cli/cli" import { Garden } from "../../../../src" import { ClientAuthToken } from "../../../../src/db/entities/client-auth-token" -import { randomString } from "../../../../src/util/string" +import { dedent, randomString } from "../../../../src/util/string" import { EnterpriseApi } from "../../../../src/enterprise/api" import { LogLevel } from "../../../../src/logger/log-node" import { gardenEnv } from "../../../../src/constants" +import { EnterpriseApiError } from "../../../../src/exceptions" function makeCommandParams(garden: Garden) { const log = garden.log @@ -168,6 +169,44 @@ describe("LoginCommand", () => { ) }) + it("should throw and print a helpful message on 401 errors", async () => { + const postfix = randomString() + const testToken = { + token: `dummy-token-${postfix}`, + refreshToken: `dummy-refresh-token-${postfix}`, + tokenValidity: 60, + } + + const command = new LoginCommand() + const garden = await makeDummyGarden(getDataDir("test-projects", "login", "has-domain-and-id"), { + noEnterprise: false, + commandInfo: { name: "foo", args: {}, opts: {} }, + }) + + await EnterpriseApi.saveAuthToken(garden.log, testToken) + td.replace(EnterpriseApi.prototype, "checkClientAuthToken", async () => false) + td.replace(EnterpriseApi.prototype, "refreshToken", async () => { + throw new EnterpriseApiError("bummer", { statusCode: 401 }) + }) + + const savedToken = await ClientAuthToken.findOne() + expect(savedToken).to.exist + expect(savedToken!.token).to.eql(testToken.token) + expect(savedToken!.refreshToken).to.eql(testToken.refreshToken) + + await expectError( + () => command.action(makeCommandParams(garden)), + (err) => expect(stripAnsi(err.message)).to.match(/bummer/) + ) + + const logOutput = getLogMessages(garden.log, (entry) => entry.level <= LogLevel.info).join("\n") + + expect(logOutput).to.include(dedent` + Looks like your session token is invalid. If you were previously logged into a different instance + of Garden Enterprise, log out first before logging in. + `) + }) + context("GARDEN_AUTH_TOKEN set in env", () => { const saveEnv = gardenEnv.GARDEN_AUTH_TOKEN before(() => {