1
0
mirror of https://github.com/sasjs/server.git synced 2026-01-09 23:40:06 +00:00

feat: authentication with jwt

This commit is contained in:
Saad Jutt
2021-11-02 03:13:16 +05:00
parent f1e464d4a4
commit 22dfcfddb9
12 changed files with 13816 additions and 53 deletions

144
src/routes/api/auth.ts Normal file
View File

@@ -0,0 +1,144 @@
import express from 'express'
import bcrypt from 'bcryptjs'
import mongoose from 'mongoose'
import jwt from 'jsonwebtoken'
import Client from '../../model/Client'
import User from '../../model/User'
import { authorizeValidation, tokenValidation } from '../../utils'
import { InfoJWT } from '../../types'
const authRouter = express.Router()
const clients: { [key: string]: string } = {}
const clientIDs = new Set()
const authCodes: { [key: string]: string } = {}
// connect to DB
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
if (err) throw err
console.log('Connected to db!')
const result = await Client.find()
result.forEach((r) => {
clients[r.clientid] = r.clientsecret
clientIDs.add(r.clientid)
})
})
const refreshTokens: string[] = []
authRouter.post('/authorize', async (req, res) => {
const { error, value } = authorizeValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
const { username, password, client_id } = value
// Authenticate User
const user = await User.findOne({ username })
if (!user) return res.status(400).send('Username is not found.')
const validPass = await bcrypt.compare(password, user.password)
if (!validPass) return res.status(400).send('Invalid password.')
// Verify client ID
if (!clientIDs.has(client_id)) {
return res.sendStatus(403)
}
// generate authorization code against client_id
const userInfo: InfoJWT = {
client_id,
username,
isadmin: user.isadmin,
isactive: user.isactive
}
authCodes[client_id] = generateAuthCode(userInfo)
res.json({ code: authCodes[client_id] })
})
authRouter.post('/token', async (req, res) => {
const { error, value } = tokenValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
const { client_id, client_secret, code } = value
const userInfo = await verifyAuthCode(client_id, client_secret, code)
if (userInfo) {
const accessToken = generateAccessToken(userInfo)
const refreshToken = jwt.sign(
userInfo,
process.env.REFRESH_TOKEN_SECRET as string
)
refreshTokens.push(refreshToken)
delete authCodes[client_id]
res.json({ accessToken: accessToken, refreshToken: refreshToken })
} else {
res.sendStatus(403)
}
})
// authRouter.post('/refresh', (req, res) => {
// const refreshToken = req.body.token
// if (refreshToken == null) return res.sendStatus(401)
// if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403)
// jwt.verify(
// refreshToken,
// process.env.REFRESH_TOKEN_SECRET as string,
// (err: any, user: any) => {
// if (err) return res.sendStatus(403)
// const accessToken = generateAccessToken({ name: user.name })
// res.json({ accessToken: accessToken })
// }
// )
// })
authRouter.delete('/logout', (req, res) => {
const index = refreshTokens.findIndex(req.body.token)
if (index > -1) refreshTokens.splice(index, 1)
res.sendStatus(204)
})
const generateAccessToken = (data: InfoJWT) =>
jwt.sign(data, process.env.ACCESS_TOKEN_SECRET as string, {
expiresIn: '1day'
})
const generateAuthCode = (data: InfoJWT) =>
jwt.sign(data, process.env.AUTH_CODE_SECRET as string, {
expiresIn: '30s'
})
const verifyAuthCode = async (
client_id: string,
client_secret: string,
code: string
): Promise<InfoJWT | undefined> => {
return new Promise((resolve, reject) => {
jwt.verify(code, process.env.AUTH_CODE_SECRET as string, (err, data) => {
if (err) return resolve(undefined)
const clientInfo: InfoJWT = {
client_id: data?.client_id,
username: data?.username,
isadmin: data?.isadmin,
isactive: data?.isactive
}
if (
clientInfo.client_id === client_id &&
clients[client_id] === client_secret &&
authCodes[client_id] === code
) {
return resolve(clientInfo)
}
return resolve(undefined)
})
})
}
export default authRouter

View File

@@ -1,10 +1,49 @@
import express from 'express'
import jwt from 'jsonwebtoken'
import dotenv from 'dotenv'
import { InfoJWT } from '../../types'
import driveRouter from './drive'
import stpRouter from './stp'
import userRouter from './user'
dotenv.config()
import authRouter from './auth'
const router = express.Router()
router.use('/drive', driveRouter)
router.use('/stp', stpRouter)
router.use('/drive', authenticateToken, driveRouter)
router.use('/stp', authenticateToken, stpRouter)
router.use('/user', authenticateToken, verifyAdmin, userRouter)
router.use('/auth', authRouter)
function authenticateToken(req: any, res: any, next: any) {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (token == null) return res.sendStatus(401)
jwt.verify(
token,
process.env.ACCESS_TOKEN_SECRET as string,
(err: any, data: any) => {
if (err) return res.sendStatus(403)
const user: InfoJWT = {
client_id: data?.client_id,
username: data?.username,
isadmin: data?.isadmin,
isactive: data?.isactive
}
req.user = user
next()
}
)
}
function verifyAdmin(req: any, res: any, next: any) {
const { user } = req
if (!user.isadmin) return res.status(403).send('Admin account required')
next()
}
export default router

44
src/routes/api/user.ts Normal file
View File

@@ -0,0 +1,44 @@
import express from 'express'
import bcrypt from 'bcryptjs'
import User from '../../model/User'
import { registerValidation } from '../../utils'
const userRouter = express.Router()
userRouter.post('/', async (req, res) => {
const { error, value } = registerValidation(req.body)
if (error) return res.status(400).send(error.details[0].message)
const { displayname, username, password, isadmin, isactive } = value
// Checking if user is already in the database
const usernameExist = await User.findOne({ username })
if (usernameExist) return res.status(400).send('Username already exists.')
// Hash passwords
const salt = await bcrypt.genSalt(10)
const hashPassword = await bcrypt.hash(password, salt)
// Create a new user
const user = new User({
displayname,
username,
password: hashPassword,
isadmin,
isactive
})
try {
const savedUser = await user.save()
res.send({
displayname: savedUser.displayname,
username: savedUser.username,
isadmin: savedUser.isadmin,
isactive: savedUser.isactive
})
} catch (err) {
res.status(400).send(err)
}
})
export default userRouter