From 56393f39b7cc8804af8fb2cd01be44aa565ba509 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 7 Jun 2022 13:57:17 -0700 Subject: [PATCH] fix: Previously provisioned JWT's should be revoked on signout (#3639) * feat: auth.delete endpoint * test --- app/stores/AuthStore.ts | 6 ++++++ server/models/Event.ts | 3 ++- server/routes/api/auth.test.ts | 24 ++++++++++++++++++++++++ server/routes/api/auth.ts | 30 +++++++++++++++++++++++++++++- server/types.ts | 1 + 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/app/stores/AuthStore.ts b/app/stores/AuthStore.ts index a9c7c1441..da47ae944 100644 --- a/app/stores/AuthStore.ts +++ b/app/stores/AuthStore.ts @@ -253,6 +253,12 @@ export default class AuthStore { @action logout = async (savePath = false) => { + if (!this.token) { + return; + } + + client.post(`/auth.delete`); + // remove user and team from localStorage Storage.set(AUTH_STORE, { user: null, diff --git a/server/models/Event.ts b/server/models/Event.ts index 904b6945f..befd7b463 100644 --- a/server/models/Event.ts +++ b/server/models/Event.ts @@ -1,4 +1,4 @@ -import { SaveOptions } from "sequelize"; +import type { SaveOptions } from "sequelize"; import { ForeignKey, AfterSave, @@ -159,6 +159,7 @@ class Event extends IdModel { "users.create", "users.update", "users.signin", + "users.signout", "users.promote", "users.demote", "users.invite", diff --git a/server/routes/api/auth.test.ts b/server/routes/api/auth.test.ts index 19813cf6c..1e2449613 100644 --- a/server/routes/api/auth.test.ts +++ b/server/routes/api/auth.test.ts @@ -48,6 +48,30 @@ describe("#auth.info", () => { }); }); +describe("#auth.delete", () => { + it("should make the access token unusable", async () => { + const user = await buildUser(); + const res = await server.post("/api/auth.delete", { + body: { + token: user.getJwtToken(), + }, + }); + expect(res.status).toEqual(200); + + const res2 = await server.post("/api/auth.info", { + body: { + token: user.getJwtToken(), + }, + }); + expect(res2.status).toEqual(401); + }); + + it("should require authentication", async () => { + const res = await server.post("/api/auth.delete"); + expect(res.status).toEqual(401); + }); +}); + describe("#auth.config", () => { it("should return available SSO providers", async () => { env.DEPLOYMENT = "hosted"; diff --git a/server/routes/api/auth.ts b/server/routes/api/auth.ts index ca0a6ac84..93f0b2237 100644 --- a/server/routes/api/auth.ts +++ b/server/routes/api/auth.ts @@ -2,9 +2,10 @@ import invariant from "invariant"; import Router from "koa-router"; import { find } from "lodash"; import { parseDomain } from "@shared/utils/domains"; +import { sequelize } from "@server/database/sequelize"; import env from "@server/env"; import auth from "@server/middlewares/authentication"; -import { Team, TeamDomain } from "@server/models"; +import { Event, Team, TeamDomain } from "@server/models"; import { presentUser, presentTeam, presentPolicies } from "@server/presenters"; import ValidateSSOAccessTask from "@server/queues/tasks/ValidateSSOAccessTask"; import providers from "../auth/providers"; @@ -125,4 +126,31 @@ router.post("auth.info", auth(), async (ctx) => { }; }); +router.post("auth.delete", auth(), async (ctx) => { + const { user } = ctx.state; + + await sequelize.transaction(async (transaction) => { + await user.rotateJwtSecret({ transaction }); + await Event.create( + { + name: "users.signout", + actorId: user.id, + userId: user.id, + teamId: user.teamId, + data: { + name: user.name, + }, + ip: ctx.request.ip, + }, + { + transaction, + } + ); + }); + + ctx.body = { + success: true, + }; +}); + export default router; diff --git a/server/types.ts b/server/types.ts index 6f24268e5..8c387974e 100644 --- a/server/types.ts +++ b/server/types.ts @@ -13,6 +13,7 @@ export type UserEvent = | { name: "users.create" // eslint-disable-line | "users.signin" + | "users.signout" | "users.update" | "users.suspend" | "users.activate"