From 37a485b4d1d4b7e60eee2067ffd86d0eea4f03e8 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 18 Mar 2024 08:05:36 -0400 Subject: [PATCH] Create new token when current has expired (#10435) * Create new token when current has expired * Add changeset * Rename renew timer function: * Handle creating a new token on renewal --- .changeset/long-buckets-end.md | 5 +++ packages/db/src/core/tokens.ts | 57 +++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 .changeset/long-buckets-end.md diff --git a/.changeset/long-buckets-end.md b/.changeset/long-buckets-end.md new file mode 100644 index 0000000000..efeee9af90 --- /dev/null +++ b/.changeset/long-buckets-end.md @@ -0,0 +1,5 @@ +--- +"@astrojs/db": patch +--- + +Fetch new app token when previous has expired diff --git a/packages/db/src/core/tokens.ts b/packages/db/src/core/tokens.ts index 379a3f6811..b5c441d420 100644 --- a/packages/db/src/core/tokens.ts +++ b/packages/db/src/core/tokens.ts @@ -31,9 +31,20 @@ class ManagedRemoteAppToken implements ManagedAppToken { session: string; projectId: string; ttl: number; + expires: Date; renewTimer: NodeJS.Timeout | undefined; static async create(sessionToken: string, projectId: string) { + const { token: shortLivedAppToken, ttl } = await this.createToken(sessionToken, projectId); + return new ManagedRemoteAppToken({ + token: shortLivedAppToken, + session: sessionToken, + projectId, + ttl, + }); + } + + static async createToken(sessionToken: string, projectId: string): Promise<{ token: string; ttl: number; }> { const spinner = ora('Connecting to remote database...').start(); const response = await safeFetch( new URL(`${getAstroStudioUrl()}/auth/cli/token-create`), @@ -54,13 +65,8 @@ class ManagedRemoteAppToken implements ManagedAppToken { await new Promise((resolve) => setTimeout(resolve, 2000)); spinner.succeed(green('Connected to remote database.')); - const { token: shortLivedAppToken, ttl } = await response.json(); - return new ManagedRemoteAppToken({ - token: shortLivedAppToken, - session: sessionToken, - projectId, - ttl, - }); + const { token, ttl } = await response.json(); + return { token, ttl }; } constructor(options: { token: string; session: string; projectId: string; ttl: number }) { @@ -69,6 +75,7 @@ class ManagedRemoteAppToken implements ManagedAppToken { this.projectId = options.projectId; this.ttl = options.ttl; this.renewTimer = setTimeout(() => this.renew(), (1000 * 60 * 5) / 2); + this.expires = getExpiresFromTtl(this.ttl); } private async fetch(url: string, body: Record) { @@ -88,24 +95,41 @@ class ManagedRemoteAppToken implements ManagedAppToken { ); } + tokenIsValid() { + return new Date() > this.expires; + } + + createRenewTimer() { + return setTimeout(() => this.renew(), (1000 * 60 * this.ttl) / 2); + } + async renew() { clearTimeout(this.renewTimer); delete this.renewTimer; - try { + + if(this.tokenIsValid()) { const response = await this.fetch('/auth/cli/token-renew', { token: this.token, projectId: this.projectId, }); if (response.status === 200) { - this.renewTimer = setTimeout(() => this.renew(), (1000 * 60 * this.ttl) / 2); + this.expires = getExpiresFromTtl(this.ttl); + this.renewTimer = this.createRenewTimer(); } else { throw new Error(`Unexpected response: ${response.status} ${response.statusText}`); + } + } else { + try { + const { token, ttl } = await ManagedRemoteAppToken.createToken(this.session, this.projectId); + this.token = token; + this.ttl = ttl; + this.expires = getExpiresFromTtl(ttl); + this.renewTimer = this.createRenewTimer(); + } catch { + // If we get here we couldn't create a new token. Since the existing token + // is expired we really can't do anything and should exit. + throw new Error(`Token has expired and attempts to renew it have failed, please try again.`); } - } catch (error: any) { - const retryIn = (60 * this.ttl) / 10; - // eslint-disable-next-line no-console - console.error(`Failed to renew token. Retrying in ${retryIn} seconds.`, error?.message); - this.renewTimer = setTimeout(() => this.renew(), retryIn * 1000); } } @@ -163,3 +187,8 @@ export async function getManagedAppTokenOrExit(token?: string): Promise