diff --git a/server/src/lib/get-html-from-drift-file.ts b/server/src/lib/get-html-from-drift-file.ts
index bbee1ea0..dcb22dd8 100644
--- a/server/src/lib/get-html-from-drift-file.ts
+++ b/server/src/lib/get-html-from-drift-file.ts
@@ -32,6 +32,7 @@ ${content}
} else {
contentToRender = "\n" + content
}
+
const html = markdown(contentToRender)
return html
}
diff --git a/server/src/lib/models/JWTDenyList.ts b/server/src/lib/models/JWTDenyList.ts
new file mode 100644
index 00000000..fce57cec
--- /dev/null
+++ b/server/src/lib/models/JWTDenyList.ts
@@ -0,0 +1,42 @@
+import {
+ Model,
+ Column,
+ Table,
+ IsUUID,
+ PrimaryKey,
+ DataType,
+ CreatedAt,
+ UpdatedAt,
+ DeletedAt,
+ Unique
+} from "sequelize-typescript"
+
+@Table
+export class JWTDenyList extends Model {
+ @IsUUID(4)
+ @PrimaryKey
+ @Unique
+ @Column({
+ type: DataType.UUID,
+ defaultValue: DataType.UUIDV4
+ })
+ id!: string
+
+ @Column
+ token!: string
+
+ @Column
+ reason!: string
+
+ @CreatedAt
+ @Column
+ createdAt!: Date
+
+ @UpdatedAt
+ @Column
+ updatedAt!: Date
+
+ @DeletedAt
+ @Column
+ deletedAt?: Date
+}
diff --git a/server/src/migrations/07_denylist-table.ts b/server/src/migrations/07_denylist-table.ts
new file mode 100644
index 00000000..2ba01062
--- /dev/null
+++ b/server/src/migrations/07_denylist-table.ts
@@ -0,0 +1,31 @@
+"use strict"
+import { DataTypes } from "sequelize"
+import type { Migration } from "../database"
+
+export const up: Migration = async ({ context: queryInterface }) =>
+ queryInterface.createTable("JWTDenyLists", {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ unique: true
+ },
+ token: {
+ type: DataTypes.STRING
+ },
+ reason: {
+ type: DataTypes.STRING
+ },
+ createdAt: {
+ type: DataTypes.DATE
+ },
+ updatedAt: {
+ type: DataTypes.DATE
+ },
+ deletedAt: {
+ type: DataTypes.DATE
+ }
+ })
+
+export const down: Migration = async ({ context: queryInterface }) =>
+ queryInterface.dropTable("JWTDenyLists")
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts
index 1ac96668..2b485432 100644
--- a/server/src/routes/auth.ts
+++ b/server/src/routes/auth.ts
@@ -1,7 +1,8 @@
import { Router } from "express"
import { genSalt, hash, compare } from "bcryptjs"
import { User } from "@lib/models/User"
-import { sign } from "jsonwebtoken"
+import { JWTDenyList } from "@lib/models/JWTDenyList"
+import { sign, verify } from "jsonwebtoken"
import config from "@lib/config"
import jwt from "@lib/middleware/jwt"
import { celebrate, Joi } from "celebrate"
@@ -144,3 +145,31 @@ auth.get("/verify-token", jwt, async (req, res, next) => {
next(e)
}
})
+
+auth.post("/signout", jwt, async (req, res, next) => {
+ try {
+ const authHeader = req.headers["authorization"]
+ const token = authHeader?.split(" ")[1]
+ let reason = ""
+ if (token == null) return res.sendStatus(401)
+
+ verify(token, config.jwt_secret, (err: any, user: any) => {
+ if (err) return res.sendStatus(403)
+ if (user) {
+ reason = "Manually revoked"
+ } else {
+ reason = "Token expired"
+ }
+ })
+ const denylist = await new JWTDenyList({ token, reason })
+ await denylist.save()
+ req.headers["authorization"] = ""
+ res.status(201).json({
+ message: "You are now logged out",
+ token,
+ reason
+ })
+ } catch (e) {
+ next(e)
+ }
+})