Skip to content

Commit

Permalink
authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
trdecker committed Nov 20, 2023
1 parent eb43763 commit bee01af
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 22 deletions.
7 changes: 3 additions & 4 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import dotenv from 'dotenv'
import notesRouter from './src/routes/notesRouter.js'
import usersRouter from './src/routes/usersRouter.js'
import cors from 'cors'
import config from './src/config/config.js'

dotenv.config()
const port = process.env.PORT ?? 80

// configureAzure(config.database)
const port = config.port ?? 80 // Default to 80

const app = express()

const corsOptions = {
origin: [process.env.DEV_URL],
origin: [config.devUrl],
methods: 'GET, PUT, POST, DELETE, HEAD, PATCH, OPTIONS',
credentials: true,
allowedHeaders: 'Content-Type, Authorization'
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"dependencies": {
"@azure/cosmos": "^4.0.0",
"@azure/identity": "^3.3.2",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2"
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2"
}
}
4 changes: 3 additions & 1 deletion src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ dotenv.config()

export default {
endpoint: process.env.COSMOS_ENDPOINT,
port: process.env.PORT,
key: process.env.PRIMARY_KEY,
databaseName: process.env.DATABASE_NAME,
notesContainerName: process.env.NOTES_CONTAINER_NAME,
usersContainerName: process.env.USERS_CONTAINER_NAME
usersContainerName: process.env.USERS_CONTAINER_NAME,
devUrl: process.env.DEV_URL
}
20 changes: 20 additions & 0 deletions src/controllers/noteController.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
import Note from '../models/noteModel.js'
import { apiBadRequestError, apiInternalError, apiNotFoundError } from '../utils/apiUtils.js'


/**
* @function getNotes
* @function createNote
* @function editNote
* @function deleteNote
*/
const notesController = {

/**
Expand Down Expand Up @@ -49,6 +56,19 @@ const notesController = {
return
}

if (!newNote.title) {
apiBadRequestError(res, 'title is a required field in the body')
return
}

if (!newNote.body) {
apiBadRequestError(res, 'body is a required field in the body')
return
}

// Check authorization


const createdItem = await Note.createNote(userId, newNote)
res.json(createdItem)
} catch (e) {
Expand Down
45 changes: 37 additions & 8 deletions src/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,58 @@
*/

import userModel from "../models/userModel.js"
import { apiBadRequestError } from "../utils/apiUtils.js"
import { apiBadRequestError, apiForbiddenError } from "../utils/apiUtils.js"

const userController = {
signup(req, res) {
body = req.body
async signup(req, res) {
const { body } = req
if (!body) {
apiBadRequestError(res, 'Body is required')
return
}
const username = body.username
const password = body.password

const { username, password } = body

if (!username || !password) {
apiBadRequestError(res, 'Missing username or password')
return
}

userModel.signup(username, password)
// If a username already exists, return a 403
const users = await userModel.getUser(username)
console.log(users)

if (users.length > 0) {
apiForbiddenError(res, 'Username already exists.')
return
}

await userModel.signup(username, password)
res.send('User signed up!')
},

login(req, res) {
res.send('This will login the user.')
async login(req, res) {
const { body } = req
if (!body) {
apiBadRequestError(res, 'Body is required')
return
}

const { username, password } = req.body

if (!username || !password) {
apiBadRequestError(res, 'Missing username or password')
return
}

const authToken = await userModel.login(username, password)

if (authToken) {
res.json({ authToken })
}
else {
apiForbiddenError(res, 'Invalid credentials')
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/middlewares/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import jwt from "jsonwebtoken"
import config from "../config/config.js"

export function requireAuth (req, res, next) {
const token = req.headers.authorization

// 401 if no authtoken
if (!token) {
return res.status(401).json({ error: 'Unauthorized' })
}

try {
const decoded = jwt.verify(token.replace('Bearer ', ''), config.key)
req.user = decoded
next()
} catch (error) {
// Invalid token, respond with an error
res.status(401).json({ error: 'Unauthorized' })
}
}
57 changes: 53 additions & 4 deletions src/models/userModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import { generateId } from '../utils/commonUtils.js'
import { CosmosClient } from '@azure/cosmos'
import bcrypt from 'bcrypt'
import jwt from 'jsonwebtoken'
import config from '../config/config.js'

const cosmosClient = new CosmosClient({
Expand All @@ -19,23 +21,70 @@ const cosmosClient = new CosmosClient({
const database = cosmosClient.database(config.databaseName)
const container = database.container(config.usersContainerName)

export default {
const saltRounds = 10

async getUser(userName) {
/**
* @function getUser
* @function signup
* @function login
*/
export default {

/**
* @description Get a user based on their username
* @param {String} userName
* @returns username
*/
async getUser(username) {
const querySpec = {
query: 'select * from c where c.username = @username',
parameters: [
{
name: '@username',
value: username
}
]
}
const { resources: items } = await container.items.query(querySpec).fetchAll()
return items
},

/**
* @param {String} username
* @returns created user (and it should return an authtoken, too)
*/
async signup(username, password) {
const userId = generateId()
const user = { userId, username, password }
const hashedPassword = await bcrypt.hash(password, saltRounds)
const user = { userId, username, password: hashedPassword }
const { resource: createdUser } = await container.items.create(user)
return createdUser
},

login(username, password) {
/**
* Check a username and password. If it matches, returns an authtoken.
* @param {String} username
* @param {String} password
* @returns Authtoken
*/
async login(username, password) {
const users = await this.getUser(username)
if (users.length == 0) return null // Bad username

const user = users.at(0)

const match = await bcrypt.compare(password, user.password)

// If the password matches, return the authtoken
if (match) {
const token = jwt.sign(
{ userId: user.userId, username: user.username },
config.key,
{ expiresIn: '1h' }
)
return token
}

return null // Invalid credentials
}
}
9 changes: 5 additions & 4 deletions src/routes/notesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

import express from 'express'
import notesController from '../controllers/noteController.js'
import { requireAuth } from '../middlewares/authMiddleware.js'

const router = express.Router()

router.get('/', notesController.getNotes)
router.post('/', notesController.createNote)
router.put('/', notesController.editNote)
router.delete('/', notesController.deleteNote)
router.get('/', requireAuth, notesController.getNotes)
router.post('/', requireAuth, notesController.createNote)
router.put('/', requireAuth, notesController.editNote)
router.delete('/', requireAuth, notesController.deleteNote)

export default router
4 changes: 4 additions & 0 deletions src/utils/apiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export function apiBadRequestError(res, errorMessage) {
res.status(400).send(errorMessage)
}

export function apiForbiddenError(res, errorMessage) {
res.status(403).send(errorMessage)
}

/**
* Send a 404 error, along with an error message.
* @param {Object} res
Expand Down

0 comments on commit bee01af

Please sign in to comment.