server: add JWTDenyList table and signout route (#52)
* add models and signout route * add migration file for JWTDenylist Closes #25 Co-authored-by: Max Leiter <maxwell.leiter@gmail.com>
This commit is contained in:
parent
9cbcfd3397
commit
b6439858df
4 changed files with 104 additions and 1 deletions
|
@ -32,6 +32,7 @@ ${content}
|
||||||
} else {
|
} else {
|
||||||
contentToRender = "\n" + content
|
contentToRender = "\n" + content
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = markdown(contentToRender)
|
const html = markdown(contentToRender)
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
42
server/src/lib/models/JWTDenyList.ts
Normal file
42
server/src/lib/models/JWTDenyList.ts
Normal file
|
@ -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
|
||||||
|
}
|
31
server/src/migrations/07_denylist-table.ts
Normal file
31
server/src/migrations/07_denylist-table.ts
Normal file
|
@ -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")
|
|
@ -1,7 +1,8 @@
|
||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import { genSalt, hash, compare } from "bcryptjs"
|
import { genSalt, hash, compare } from "bcryptjs"
|
||||||
import { User } from "@lib/models/User"
|
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 config from "@lib/config"
|
||||||
import jwt from "@lib/middleware/jwt"
|
import jwt from "@lib/middleware/jwt"
|
||||||
import { celebrate, Joi } from "celebrate"
|
import { celebrate, Joi } from "celebrate"
|
||||||
|
@ -144,3 +145,31 @@ auth.get("/verify-token", jwt, async (req, res, next) => {
|
||||||
next(e)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in a new issue