mirror of
https://github.com/sasjs/server.git
synced 2026-01-08 07:00:04 +00:00
test(auth): added for authorize + token
This commit is contained in:
22
src/controllers/createClient.ts
Normal file
22
src/controllers/createClient.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import Client from '../model/Client'
|
||||
|
||||
export const createClient = async (data: any) => {
|
||||
const { clientid, clientsecret } = data
|
||||
|
||||
// Checking if client is already in the database
|
||||
const clientExist = await Client.findOne({ clientid })
|
||||
if (clientExist) throw new Error('Client ID already exists.')
|
||||
|
||||
// Create a new client
|
||||
const client = new Client({
|
||||
clientid,
|
||||
clientsecret
|
||||
})
|
||||
|
||||
const savedClient = await client.save()
|
||||
|
||||
return {
|
||||
clientid: savedClient.clientid,
|
||||
clientsecret: savedClient.clientsecret
|
||||
}
|
||||
}
|
||||
32
src/controllers/createUser.ts
Normal file
32
src/controllers/createUser.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import bcrypt from 'bcryptjs'
|
||||
import User from '../model/User'
|
||||
|
||||
export const createUser = async (data: any) => {
|
||||
const { displayname, username, password, isadmin, isactive } = data
|
||||
|
||||
// Checking if user is already in the database
|
||||
const usernameExist = await User.findOne({ username })
|
||||
if (usernameExist) throw new Error('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
|
||||
})
|
||||
|
||||
const savedUser = await user.save()
|
||||
|
||||
return {
|
||||
displayname: savedUser.displayname,
|
||||
username: savedUser.username,
|
||||
isadmin: savedUser.isadmin,
|
||||
isactive: savedUser.isactive
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from 'express'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import mongoose from 'mongoose'
|
||||
import mongoose, { Mongoose } from 'mongoose'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import Client from '../../model/Client'
|
||||
@@ -14,18 +14,32 @@ 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!')
|
||||
|
||||
export const populateClients = async () => {
|
||||
const result = await Client.find()
|
||||
clientIDs.clear()
|
||||
result.forEach((r) => {
|
||||
clients[r.clientid] = r.clientsecret
|
||||
clientIDs.add(r.clientid)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const connectDB = () => {
|
||||
// NOTE: when exporting app.js as agent for supertest
|
||||
// we should exlcude connecting to the real database
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
mongoose.connect(process.env.DB_CONNECT as string, async (err) => {
|
||||
if (err) throw err
|
||||
|
||||
console.log('Connected to db!')
|
||||
|
||||
await populateClients()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const saveCode = (client_id: string, code: string) =>
|
||||
(authCodes[client_id] = code)
|
||||
export const deleteCode = (client_id: string) => delete authCodes[client_id]
|
||||
|
||||
const refreshTokens: string[] = []
|
||||
|
||||
@@ -37,14 +51,14 @@ authRouter.post('/authorize', async (req, res) => {
|
||||
|
||||
// Authenticate User
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) return res.status(400).send('Username is not found.')
|
||||
if (!user) return res.status(403).send('Username is not found.')
|
||||
|
||||
const validPass = await bcrypt.compare(password, user.password)
|
||||
if (!validPass) return res.status(400).send('Invalid password.')
|
||||
if (!validPass) return res.status(403).send('Invalid password.')
|
||||
|
||||
// Verify client ID
|
||||
if (!clientIDs.has(client_id)) {
|
||||
return res.sendStatus(403)
|
||||
return res.status(403).send('Invalid client_id.')
|
||||
}
|
||||
|
||||
// generate authorization code against client_id
|
||||
@@ -54,9 +68,10 @@ authRouter.post('/authorize', async (req, res) => {
|
||||
isadmin: user.isadmin,
|
||||
isactive: user.isactive
|
||||
}
|
||||
authCodes[client_id] = generateAuthCode(userInfo)
|
||||
|
||||
res.json({ code: authCodes[client_id] })
|
||||
const code = saveCode(client_id, generateAuthCode(userInfo))
|
||||
|
||||
res.json({ code })
|
||||
})
|
||||
|
||||
authRouter.post('/token', async (req, res) => {
|
||||
@@ -66,20 +81,21 @@ authRouter.post('/token', async (req, res) => {
|
||||
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)
|
||||
if (!userInfo) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const accessToken = generateAccessToken(userInfo)
|
||||
const refreshToken = jwt.sign(
|
||||
userInfo,
|
||||
process.env.REFRESH_TOKEN_SECRET as string
|
||||
)
|
||||
refreshTokens.push(refreshToken)
|
||||
|
||||
deleteCode(client_id)
|
||||
|
||||
res.json({ accessToken: accessToken, refreshToken: refreshToken })
|
||||
})
|
||||
|
||||
// authRouter.post('/refresh', (req, res) => {
|
||||
@@ -109,7 +125,7 @@ const generateAccessToken = (data: InfoJWT) =>
|
||||
expiresIn: '1day'
|
||||
})
|
||||
|
||||
const generateAuthCode = (data: InfoJWT) =>
|
||||
export const generateAuthCode = (data: InfoJWT) =>
|
||||
jwt.sign(data, process.env.AUTH_CODE_SECRET as string, {
|
||||
expiresIn: '30s'
|
||||
})
|
||||
|
||||
@@ -6,9 +6,10 @@ import { InfoJWT } from '../../types'
|
||||
import driveRouter from './drive'
|
||||
import stpRouter from './stp'
|
||||
import userRouter from './user'
|
||||
import authRouter, { connectDB } from './auth'
|
||||
|
||||
dotenv.config()
|
||||
import authRouter from './auth'
|
||||
connectDB()
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
|
||||
276
src/routes/api/spec/auth.spec.ts
Normal file
276
src/routes/api/spec/auth.spec.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import app from '../../../app'
|
||||
import { createUser } from '../../../controllers/createUser'
|
||||
import { createClient } from '../../../controllers/createClient'
|
||||
import { generateAuthCode, populateClients, saveCode } from '../auth'
|
||||
import { InfoJWT } from '../../../types'
|
||||
|
||||
const client = {
|
||||
clientid: 'someclientID',
|
||||
clientsecret: 'someclientSecret'
|
||||
}
|
||||
// const adminUser = {
|
||||
// displayname: 'Test Admin',
|
||||
// username: 'testAdminUsername',
|
||||
// password: '12345678',
|
||||
// isadmin: true,
|
||||
// isactive: true
|
||||
// }
|
||||
const user = {
|
||||
displayname: 'Test User',
|
||||
username: 'testUsername',
|
||||
password: '87654321',
|
||||
isadmin: false,
|
||||
isactive: true
|
||||
}
|
||||
|
||||
describe('auth', () => {
|
||||
let con: Mongoose
|
||||
let mongoServer: MongoMemoryServer
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create()
|
||||
con = await mongoose.connect(mongoServer.getUri())
|
||||
await createClient(client)
|
||||
await populateClients()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (con) {
|
||||
await con.connection.dropDatabase()
|
||||
await con.connection.close()
|
||||
}
|
||||
if (mongoServer) {
|
||||
await mongoServer.stop()
|
||||
}
|
||||
})
|
||||
|
||||
describe('authorize', () => {
|
||||
afterEach(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
const collection = collections['users']
|
||||
await collection.deleteMany({})
|
||||
})
|
||||
|
||||
it('should respond with authorization code', async () => {
|
||||
await createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
client_id: client.clientid
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toHaveProperty('code')
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if username is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
password: user.password,
|
||||
client_id: client.clientid
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"username" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if password is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
client_id: client.clientid
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"password" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if client_id is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"client_id" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if username is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
client_id: client.clientid
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Username is not found.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if password is incorrect', async () => {
|
||||
await createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: 'WrongPassword',
|
||||
client_id: client.clientid
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Invalid password.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if client_id is incorrect', async () => {
|
||||
await createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/authorize')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
client_id: 'WrongClientID'
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.text).toEqual('Invalid client_id.')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('token', () => {
|
||||
const userInfo: InfoJWT = {
|
||||
client_id: client.clientid,
|
||||
username: user.username,
|
||||
isadmin: user.isadmin,
|
||||
isactive: user.isactive
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await createUser(user)
|
||||
})
|
||||
afterAll(async () => {
|
||||
const collections = mongoose.connection.collections
|
||||
const collection = collections['users']
|
||||
await collection.deleteMany({})
|
||||
})
|
||||
|
||||
it('should respond with access and refresh tokens', async () => {
|
||||
const code = saveCode(userInfo.client_id, generateAuthCode(userInfo))
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: client.clientid,
|
||||
client_secret: client.clientsecret,
|
||||
code
|
||||
})
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toHaveProperty('accessToken')
|
||||
expect(res.body).toHaveProperty('refreshToken')
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if code is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: client.clientid,
|
||||
client_secret: client.clientsecret
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"code" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if client_id is missing', async () => {
|
||||
const code = saveCode(userInfo.client_id, generateAuthCode(userInfo))
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_secret: client.clientsecret,
|
||||
code
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"client_id" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if client_secret is missing', async () => {
|
||||
const code = saveCode(userInfo.client_id, generateAuthCode(userInfo))
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: client.clientid,
|
||||
code
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual(`"client_secret" is required`)
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if code is invalid', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: client.clientid,
|
||||
client_secret: client.clientsecret,
|
||||
code: 'InvalidCode'
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if client_id is invalid', async () => {
|
||||
const code = saveCode(userInfo.client_id, generateAuthCode(userInfo))
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: 'WrongClientID',
|
||||
client_secret: client.clientsecret,
|
||||
code
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Forbidden if client_secret is invalid', async () => {
|
||||
const code = saveCode(userInfo.client_id, generateAuthCode(userInfo))
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASjsApi/auth/token')
|
||||
.send({
|
||||
client_id: client.clientid,
|
||||
client_secret: 'WrongClientSecret',
|
||||
code
|
||||
})
|
||||
.expect(403)
|
||||
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,35 +1,15 @@
|
||||
import express from 'express'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import User from '../../model/User'
|
||||
import { createUser } from '../../controllers/createUser'
|
||||
import { registerValidation } from '../../utils'
|
||||
|
||||
const userRouter = express.Router()
|
||||
|
||||
userRouter.post('/', async (req, res) => {
|
||||
const { error, value } = registerValidation(req.body)
|
||||
const { error, value: data } = 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()
|
||||
const savedUser = await createUser(data)
|
||||
res.send({
|
||||
displayname: savedUser.displayname,
|
||||
username: savedUser.username,
|
||||
|
||||
Reference in New Issue
Block a user