Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 57 additions & 27 deletions src/create-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,30 @@ describe("create-client", () => {
});

describe("getAccessToken", () => {
const clientWithExpiredAccessToken = async () => {
const now = Date.now();
const { scope: initialRefreshScope } = nockRefresh({
accessTokenClaims: {
iat: now,
// deliberately issuing a JWT that expires at the same time
// as iat (we don't trust the client's clock, so back-dating
// the timestamps doesn't do anything)
exp: now,
jti: "initial-access-token",
},
});

client = await createClient("client_123abc", {
redirectUri: "https://example.com/",
// turning off auto refresh so we can ensure that we're
// hitting the expired-access token case
onBeforeAutoRefresh: () => false,
});
initialRefreshScope.done();

return client;
};

describe("when the current session is authenticated", () => {
it("returns the access token", async () => {
const { scope } = nockRefresh();
Expand All @@ -388,16 +412,18 @@ describe("create-client", () => {

describe("when the current session is not authenticated", () => {
it("throws a `LoginRequiredError`", async () => {
const consoleDebugSpy = jest
.spyOn(console, "debug")
.mockImplementation();
client = await clientWithExpiredAccessToken();

const scope = nock("https://api.workos.com")
.post("/user_management/authenticate", {
client_id: "client_123abc",
grant_type: "refresh_token",
})
.reply(401, {});

client = await createClient("client_123abc", {
redirectUri: "https://example.com/",
});
await expect(client.getAccessToken()).rejects.toThrow(
LoginRequiredError,
);
Expand All @@ -406,30 +432,6 @@ describe("create-client", () => {
});

describe("when the current access token has expired", () => {
const clientWithExpiredAccessToken = async () => {
const now = Date.now();
const { scope: initialRefreshScope } = nockRefresh({
accessTokenClaims: {
iat: now,
// deliberately issuing a JWT that expires at the same time
// as iat (we don't trust the client's clock, so back-dating
// the timestamps doesn't do anything)
exp: now,
jti: "initial-access-token",
},
});

client = await createClient("client_123abc", {
redirectUri: "https://example.com/",
// turning off auto refresh so we can ensure that we're
// hitting the expired-access token case
onBeforeAutoRefresh: () => false,
});
initialRefreshScope.done();

return client;
};

it("gets a new access token and returns it", async () => {
const client = await clientWithExpiredAccessToken();

Expand Down Expand Up @@ -490,6 +492,34 @@ describe("create-client", () => {

scope.done();
});

it("throws an error if the fetch fails", async () => {
const consoleDebugSpy = jest
.spyOn(console, "debug")
.mockImplementation();
const client = await clientWithExpiredAccessToken();

const errorScope = nock("https://api.workos.com")
.post("/user_management/authenticate", {
client_id: "client_123abc",
grant_type: "refresh_token",
})
.times(2)
.replyWithError(new TypeError("Network Error"));
Comment on lines +502 to +508
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯


await expect(client.getAccessToken()).rejects.toThrow(TypeError);
await expect(client.getAccessToken()).rejects.toThrow(TypeError);
expect(consoleDebugSpy.mock.calls).toEqual([
[expect.any(TypeError)],
[expect.any(TypeError)],
]);
errorScope.done();

const { scope: successScope } = nockRefresh();
const accessToken = await client.getAccessToken();
expect(accessToken).toMatch(/^.eyJ/);
successScope.done();
});
});
});

Expand Down
10 changes: 9 additions & 1 deletion src/create-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,16 @@ An authorization_code was supplied for a login which did not originate at the ap
beginningState.tag !== "INITIAL" &&
this.#onRefreshFailure &&
this.#onRefreshFailure({ signIn: this.signIn.bind(this) });

this.#state = { tag: "ERROR" };
} else {
// transitioning into the AUTHENTICATED state ensures that we will
// attempt to refresh the token on future getAccessToken calls()
//
// this could maybe be a new state for clarity? TEMPORARY_ERROR?
this.#state = { tag: "AUTHENTICATED" };
}
this.#state = { tag: "ERROR" };

throw error;
}
}
Expand Down
Loading