Skip to content

Commit

Permalink
Create new token when current has expired (#10435)
Browse files Browse the repository at this point in the history
* Create new token when current has expired

* Add changeset

* Rename renew timer function:

* Handle creating a new token on renewal
  • Loading branch information
matthewp authored Mar 18, 2024
1 parent 156c094 commit 37a485b
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-buckets-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/db": patch
---

Fetch new app token when previous has expired
57 changes: 43 additions & 14 deletions packages/db/src/core/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`),
Expand All @@ -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 }) {
Expand All @@ -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<string, unknown>) {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -163,3 +187,8 @@ export async function getManagedAppTokenOrExit(token?: string): Promise<ManagedA
}
return ManagedRemoteAppToken.create(sessionToken, projectId);
}

function getExpiresFromTtl(ttl: number): Date {
// ttl is in minutes
return new Date(Date.now() + ttl * 60 * 1000);
}

0 comments on commit 37a485b

Please sign in to comment.