Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rest-express-auth example #2869

Draft
wants to merge 22 commits into
base: latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dc060df
chore: initial rest-express-auth creation
gautvm May 18, 2021
6b9a670
refactor: create data models
gautvm May 18, 2021
fb38fef
refactor: set up api structure
gautvm May 18, 2021
f14f40c
refactor: start some work on signup
gautvm May 18, 2021
e151438
feat(rest-express-auth) add signup route
gautvm May 18, 2021
67a3158
feat(rest-express-auth) add login route
gautvm May 18, 2021
df0b5d8
feat(rest-express-auth) added logout route
gautvm May 18, 2021
35faffb
refactor(rest-express-auth) refactor isAuth middleware
gautvm May 18, 2021
d0324e5
feat(rest-express-auth) add account delete route
gautvm May 19, 2021
c10c571
feat(rest-express-auth): posts get route
gautvm Jun 4, 2021
6bc4221
added readmes to rest-express-auth
ruheni Jun 21, 2021
cf802ea
Merge branch 'prisma:latest' into rest-express-auth
Mar 7, 2023
e5bd3a2
fix: implemented fixes from review
gautvm Mar 7, 2023
ea1dbd3
refactor updated rest-express-auth example to follow the format of ot…
gautvm Mar 7, 2023
18e37d1
refactor: added database seeding
gautvm Mar 7, 2023
e48bfc5
fix: corrected readme
Mar 7, 2023
9e56be0
fix: added isAuthenticated middleware to routes
Mar 7, 2023
ad62f59
fix: rest-express-auth: fixed readmes
gautvm Mar 20, 2023
78494c3
fix: rest-express-auth: correct protected routes
gautvm Mar 20, 2023
9d82677
refactor: rest-express-auth: add test
gautvm Mar 20, 2023
6df2873
fix: rest-express-auth: add hashing for passwords in seeding data
gautvm Mar 20, 2023
90e30d7
docs: rest-express-auth: add docs for isAuthenticated middleware
gautvm May 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions typescript/rest-express-auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
*.env*
gautvm marked this conversation as resolved.
Show resolved Hide resolved
23 changes: 23 additions & 0 deletions typescript/rest-express-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "rest-express-auth",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"dev": "ts-node src/index.ts"
},
"keywords": [],
"author": "",
"dependencies": {
"@prisma/client": "^2.22.1",
"argon2": "^0.27.2",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1"
},
"devDependencies": {
gautvm marked this conversation as resolved.
Show resolved Hide resolved
"@types/express": "^4.17.11",
"@types/jsonwebtoken": "^8.5.1",
"@types/node": "^15.3.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
}
}
29 changes: 29 additions & 0 deletions typescript/rest-express-auth/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}

generator client {
provider = "prisma-client-js"
}

model User {
id Int @id @default(autoincrement())
email String @unique
password String
username String
posts Post[]
sessionID String?
}

model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
22 changes: 22 additions & 0 deletions typescript/rest-express-auth/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { auth as AuthRoutes } from './routes/Auth'
import { account as AccountRoutes } from './routes/Account'
import { post as PostRoutes } from './routes/Post'
import express from 'express'

declare global {
namespace Express {
interface Request {
user?: import('@prisma/client').User
}
}
}

const app = express()

app.use(express.json())

app.use('/auth', AuthRoutes)
app.use('/account', AccountRoutes)
app.use('/posts', PostRoutes)

app.listen(3000, () => console.log(`🚀 Server ready at: http://localhost:3000`))
3 changes: 3 additions & 0 deletions typescript/rest-express-auth/src/lib/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()
55 changes: 55 additions & 0 deletions typescript/rest-express-auth/src/middlewares/isAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { PrismaClient, User } from '@prisma/client'
import { NextFunction, Request, Response } from 'express'
import jwt from 'jsonwebtoken'

const prisma = new PrismaClient()

export const isAuth = async (
req: Request,
res: Response,
next: NextFunction,
) => {
if (!req.headers['authorization'])
return res
.status(400)
.json({ success: false, error: 'missing auth header' })

const authHeader = req.headers['authorization']
const authMethod = authHeader.split(' ')[0]
const accessToken = authHeader.split(' ')[1]

if (!authMethod || !accessToken) {
return res.json({ success: false, error: 'invalid auth header' })
} else if (authMethod !== 'Bearer') {
return res.json({ success: false, error: 'invalid auth method' })
}

let tokenBody: any

try {
tokenBody = jwt.verify(accessToken, 'secret')
} catch {
return res.json({
success: false,
error: 'invalid token',
})
}

if (!tokenBody.userID) {
return res.json({ success: false, error: 'invalid token' })
}

const user = await prisma.user.findUnique({
where: {
id: tokenBody.userID,
},
})

if (!user) {
return res.json({ success: false, error: 'User does not exist' })
}

req.user = user

next()
}
17 changes: 17 additions & 0 deletions typescript/rest-express-auth/src/routes/Account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { prisma } from '../lib/prisma'
import { isAuth } from '../middlewares/isAuth'
import { Router } from 'express'

export const account = Router()

account.put('/update', (req, res) => {})

account.delete('/delete', isAuth, async (req, res) => {
await prisma.user.delete({
where: {
id: req.user?.id,
},
})

return res.json({ success: true })
})
94 changes: 94 additions & 0 deletions typescript/rest-express-auth/src/routes/Auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { prisma } from '../lib/prisma'
import { isAuth } from '../middlewares/isAuth'
import { generateID } from '../utils/generateID'
import { Router } from 'express'
import jwt from 'jsonwebtoken'
import argon2 from 'argon2'

export const auth = Router()

auth.post('/signup', async (req, res) => {
const { email, password, username } = req.body

if (!email || !password || !username) {
return res.json({
success: false,
error: 'Missing request body properties',
})
}

const result = await prisma.user.findFirst({
where: {
username,
},
})

if (result) return res.json({ success: false, error: 'User already exists' })

const hashedPassword = await argon2.hash(password)

const user = await prisma.user.create({
data: {
email: email,
password: hashedPassword,
username: username,
},
})

const accessToken = jwt.sign({ userID: user.id }, 'secret')

return res.json({ success: true, accessToken: accessToken })
})

auth.post('/login', async (req, res) => {
const { username, password } = req.body

if (!username || !password) {
return res.json({
success: false,
error: 'Missing request body properties',
})
}

const user = await prisma.user.findFirst({
where: {
username,
},
})

if (!user) {
return res.json({ success: false, error: 'User not found' })
}

if (await argon2.verify(user.password, password)) {
const sessionID = await generateID(30)

await prisma.user.update({
where: {
id: user.id,
},
data: {
sessionID: sessionID,
},
})

const accessToken = jwt.sign({ userID: user.id, sessionID }, 'secret')

return res.json({ success: true, accessToken: accessToken })
} else {
return res.json({ success: false, error: 'Invalid password' })
}
})

auth.post('/logout', isAuth, async (req, res) => {
await prisma.user.update({
where: {
id: req.user?.id,
},
data: {
sessionID: '',
gautvm marked this conversation as resolved.
Show resolved Hide resolved
},
})

res.json({ success: true })
})
10 changes: 10 additions & 0 deletions typescript/rest-express-auth/src/routes/Post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { prisma } from '../lib/prisma'
import { Router } from 'express'

export const post = Router()

post.get('/get', async (req, res) => {
const result = await prisma.post.findMany({})

return res.json(result)
})
5 changes: 5 additions & 0 deletions typescript/rest-express-auth/src/utils/generateID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import crypto from 'crypto'

export const generateID = async (length: number) => {
return crypto.randomBytes(length).toString('base64')
}
9 changes: 9 additions & 0 deletions typescript/rest-express-auth/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": ["esnext"],
"esModuleInterop": true
}
}