Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Check if JWT is revoked before allowing access #4353

Merged
merged 6 commits into from
Feb 21, 2025

Conversation

DafyddLlyr
Copy link
Contributor

@DafyddLlyr DafyddLlyr commented Feb 19, 2025

What does this PR do?

  • Adds a check of the revoked_tokens table by using the isRevoked callback from express-jwt
  • This ensures that any tokens which are revoked, fail validation
  • Updates API tests to assume all tokens are not revoked by default - doing this once upstream where queryMock is instantiated means we don't have to add this in multiple places.
  • API tests -
    • adds a mock so that all JWTs are non-revoked by default
    • updates mocks and describe() blocks - please see comment on PR below

Not yet covered

Requests made directly to Hasura with an existing token (not proxied via API).

To test...

  • Log in, make a copy of the token
  • Log out, check a record has been added to the revoked_tokens table
  • Try to re-use token (e.g. via API docs) - access is not allowed!
Screen.Recording.2025-02-21.at.13.16.13.mov

@DafyddLlyr DafyddLlyr changed the base branch from main to dp/api-revoke-on-logout February 19, 2025 13:57
@DafyddLlyr DafyddLlyr changed the title dp/isRevoked callback feat(api): Check if JWT is revoked before allowing access Feb 19, 2025
@DafyddLlyr DafyddLlyr force-pushed the dp/isRevoked-callback branch from 70614c7 to 3666c12 Compare February 19, 2025 13:59
Copy link

github-actions bot commented Feb 19, 2025

Removed vultr server and associated DNS entries

@DafyddLlyr DafyddLlyr force-pushed the dp/isRevoked-callback branch 3 times, most recently from 91bf522 to 5c58be0 Compare February 20, 2025 13:54
@DafyddLlyr
Copy link
Contributor Author

Invalidate JWT on logout

Base automatically changed from dp/api-revoke-on-logout to main February 21, 2025 09:49
@DafyddLlyr DafyddLlyr force-pushed the dp/isRevoked-callback branch from 5c58be0 to 56afe71 Compare February 21, 2025 09:55
This is required as there's now an addtional query via .client.request which wasn't previously mocked
@DafyddLlyr DafyddLlyr force-pushed the dp/isRevoked-callback branch from 56afe71 to b9151e0 Compare February 21, 2025 12:21
@@ -12,13 +13,18 @@ const mockGenerateCSVData = vi.fn().mockResolvedValue([
metadata: {},
},
]);
vi.mock("@opensystemslab/planx-core", () => {

vi.mock("@opensystemslab/planx-core", async (importOriginal) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This pattern is repeated a few times here, grouped in b1d6037

Now that JWT verification needs to make a round trip to the DB, this test needs to access a GraphQL client to do this ($api.client.request) which is not mocked. Using importOriginal() we can only mock the function we want to mock in this test suite and keep the rest of the original behaviour. In this case it's this.export.csvData.

We have an open issue to consolidate these mocks and to make them easier and simpler to manage - the pattern here could be DRY'ed up a but into a setupPlanXCoreMock() function called by vitest setup. I've not tackled this here in order to make process and keep things separate. The issue is here - #2404

.then((res) => {
expect(res.body).toEqual({
error: "No authorization token was found",
describe("authentication and error handling", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another common pattern here is just tighter closures for mocks - a lot were failing because we were relying on queryMock.reset() to manage scope instead of describe() blocks - this meant that the mock query for a non-revoked JWT was then being cleared, and the tests were failing.

@DafyddLlyr DafyddLlyr requested a review from a team February 21, 2025 13:19
@DafyddLlyr DafyddLlyr marked this pull request as ready for review February 21, 2025 13:19
Copy link
Member

@jessicamcinchak jessicamcinchak left a comment

Choose a reason for hiding this comment

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

Thanks for super clear testing instructions here, all works as expected for me repeated over a couple times 🙌


beforeEach(() => {
queryMock.setup(process.env.HASURA_GRAPHQL_URL!);
queryMock.mockQuery(mockIsTokenRevokedQuery);
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@freemvmt freemvmt left a comment

Choose a reason for hiding this comment

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

beautiful! such thorough work on the tests.

also I tested as you suggested and it works like a dream! ✨

@DafyddLlyr DafyddLlyr merged commit 41eaa8e into main Feb 21, 2025
13 checks passed
@DafyddLlyr DafyddLlyr deleted the dp/isRevoked-callback branch February 21, 2025 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants