summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar piotrruss <mail@pruss.it> 2023-08-21 22:19:54 +0200
committerGravatar piotrruss <mail@pruss.it> 2023-08-21 22:23:57 +0200
commit9fb0815b575cfc23ced6722b1a164328bd3cff1a (patch)
tree971a10bb74824d007cb74082a0a1d07dba3f30e2
parent4d28ac359b25d89d0dbb42dd3a6d32269eebc619 (diff)
downloadauth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.tar.gz
auth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.tar.bz2
auth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.zip
refactor, new routes
-rw-r--r--helpers/createJwt.js22
-rw-r--r--index.js5
-rw-r--r--middleware/auth.js26
-rw-r--r--model/User.js64
-rw-r--r--routes/authRoutes.js33
-rw-r--r--routes/index.js106
-rw-r--r--routes/routes.js71
7 files changed, 201 insertions, 126 deletions
diff --git a/helpers/createJwt.js b/helpers/createJwt.js
new file mode 100644
index 0000000..6c6983b
--- /dev/null
+++ b/helpers/createJwt.js
@@ -0,0 +1,22 @@
+const fs = require('fs')
+const jwt = require('jsonwebtoken')
+const cert = fs.readFileSync(`${process.cwd()}/cert/jwt_256_rsa`)
+
+const createJwt = (user, sessionId) => jwt.sign({
+ email: user.email,
+ verified: !!user.verify,
+ role: user.role,
+ sessionId,
+ },
+ {
+ key: cert,
+ passphrase: process.env.RSA_PASS,
+ },
+ {
+ expiresIn: parseInt(process.env.TOKEN_EXPIRES_IN),
+ issuer: 'pruss.it',
+ algorithm: 'RS256',
+ }
+)
+
+module.exports = createJwt
diff --git a/index.js b/index.js
index cf85259..e23f64d 100644
--- a/index.js
+++ b/index.js
@@ -1,5 +1,7 @@
const express = require('express')
-const routes = require('./routes')
+const routes = require('./routes/routes')
+const authRoutes = require('./routes/authRoutes')
+const auth = require('./middleware/auth');
const cors = require('cors')
require('dotenv-safe').config()
require('./db/mongoose')
@@ -10,6 +12,7 @@ const port = process.env.PORT
app.use(cors());
app.use(express.json())
app.use('/', routes)
+app.use('/', auth, authRoutes)
app.listen(port, () => {
console.log("Auth service is up on port " + port + ".")
diff --git a/middleware/auth.js b/middleware/auth.js
new file mode 100644
index 0000000..a62812e
--- /dev/null
+++ b/middleware/auth.js
@@ -0,0 +1,26 @@
+const fs = require('fs')
+const jwt = require('jsonwebtoken')
+
+const cert = fs.readFileSync(`${process.cwd()}/cert/jwt_256_rsa.pub`, 'utf8')
+
+const auth = async (req, res, next) => {
+ try {
+ const jwtToken = req.body.jwtToken
+
+ if (!jwtToken) throw new Error()
+
+ const user = jwt.verify(jwtToken, cert, { algorithms: ['RS256'], issuer: 'pruss.it' })
+
+ if (!user) throw new Error()
+
+ req.user = user
+
+ return next()
+ } catch (err) {
+ const error = err === 'jwt expired' ? err.message : 'unauthorized'
+
+ res.status(401).send({ error })
+ }
+}
+
+module.exports = auth
diff --git a/model/User.js b/model/User.js
index ce34cbe..8009980 100644
--- a/model/User.js
+++ b/model/User.js
@@ -22,12 +22,6 @@ const userSchema = new mongoose.Schema({
max: 1024,
min: 6,
},
- refresh: {
- type: String,
- required: true,
- max: 1024,
- min: 6,
- },
verify: {
type: String,
default: randomBytes(3).toString('hex'),
@@ -38,25 +32,38 @@ const userSchema = new mongoose.Schema({
min: 3,
default: 'user',
},
- date: {
+ sessions: [{
+ device: { type: String, maxlength: 128, default: "Unknown" },
+ refreshToken: { type: String, required: true, max: 1024, min: 6 },
+ updatedAt: { type: Date, default: Date.now },
+ }],
+ createdAt: {
type: Date,
default: Date.now,
},
})
-userSchema.statics.checkRefreshToken = async (email, refresh) => {
+userSchema.statics.checkRefreshToken = async ({ email, sessionId, refreshToken }) => {
+ if (!email || !sessionId || !refreshToken) throw new Error('Wrong request')
+
const user = await User.findOne({ email })
if (!user) throw new Error('User not found')
- const isMatch = await bcrypt.compare(refresh, user.refresh)
+ console.log(user)
+
+ const session = user.sessions.find(s => s._id.toString() === sessionId)
+
+ if (!session) throw new Error('Session not found')
+
+ const isMatch = await bcrypt.compare(refreshToken, session.refreshToken)
if (!isMatch) throw new Error('Wrong refresh token')
return user
}
-userSchema.statics.findByCredentials = async (email, password) => {
+userSchema.statics.newSession = async ({ email, password, device }) => {
const user = await User.findOne({ email })
if (!user) throw new Error('Unable to login')
@@ -65,22 +72,45 @@ userSchema.statics.findByCredentials = async (email, password) => {
if (!isMatch) throw new Error('Unable to login')
- return user
+ const refreshToken = randomBytes(32).toString('hex')
+
+ user.sessions.push({ device, refreshToken: await bcrypt.hash(refreshToken, 8) })
+
+ const newUser = await user.save()
+
+ return { user: newUser, refreshToken }
}
-userSchema.statics.newRefreshToken = async (user) => {
+userSchema.statics.refreshExistingToken = async ({ user, sessionId }) => {
const newRefreshToken = randomBytes(32).toString('hex')
- user.refresh = newRefreshToken
+ const id = user.sessions.findIndex(s => s._id.toString() === sessionId);
+
+ if (!id) throw new Error('Wrong session id')
+
+ user.sessions[id].refreshToken = await bcrypt.hash(newRefreshToken, 8)
+
await user.save()
return newRefreshToken
}
-userSchema.statics.removeRefreshToken = async (email) => (
- await User.findOneAndUpdate({ email }, { refresh: '' })
+userSchema.statics.removeSession = async ({ email, sessionId }) => (
+ await User.findOneAndUpdate(
+ { email },
+ { $pull: { sessions: { _id: sessionId } } },
+ { safe: true, multi: false }
+ )
)
+userSchema.statics.getSessions = async ({ email }) => {
+ const user = await User.findOne({ email })
+
+ if (!user) throw new Error('User not found')
+
+ return user.sessions
+}
+
userSchema.pre('save', async function(next){
const user = this
@@ -88,10 +118,6 @@ userSchema.pre('save', async function(next){
user.password = await bcrypt.hash(user.password, 8)
}
- if (user.isModified('refresh')) {
- user.refresh = await bcrypt.hash(user.refresh, 8)
- }
-
next()
})
diff --git a/routes/authRoutes.js b/routes/authRoutes.js
new file mode 100644
index 0000000..110e7f4
--- /dev/null
+++ b/routes/authRoutes.js
@@ -0,0 +1,33 @@
+const router = require('express').Router()
+const User = require('../model/User')
+
+// Logout (pass sessionId or will logout one from jwt)
+router.post('/logout', async (req, res) => {
+ try {
+ const { email, sessionId } = req.user
+
+ if (!email || !(req.body.sessionId || sessionId)) throw new Error()
+
+ await User.removeSession({ email, sessionId: req.body.sessionId || sessionId })
+
+ res.status(204).send()
+ } catch (err) {
+ console.log(err)
+ res.status(401).send({ error: 'Could not logout' })
+ }
+})
+
+// User sessions
+router.get("/sessions", async (req, res) => {
+ const { email } = req.user
+
+ try {
+ const sessions = await User.getSessions({ email })
+
+ res.send({ sessions })
+ } catch (err) {
+ res.status(401).send({ error: 'Error getting sessions' })
+ }
+})
+
+module.exports = router
diff --git a/routes/index.js b/routes/index.js
deleted file mode 100644
index 294cb49..0000000
--- a/routes/index.js
+++ /dev/null
@@ -1,106 +0,0 @@
-const fs = require('fs')
-const router = require('express').Router()
-const { randomBytes } = require('crypto')
-const jwt = require('jsonwebtoken')
-const User = require('../model/User')
-
-const cert = fs.readFileSync(`${process.cwd()}/cert/jwt_256_rsa`)
-
-const generateJwtToken = (user) => jwt.sign({
- email: user.email,
- verified: !!user.verify,
- role: user.role,
- },
- {
- key: cert,
- passphrase: process.env.RSA_PASS,
- },
- {
- expiresIn: parseInt(process.env.TOKEN_EXPIRES_IN),
- issuer: 'pruss.it',
- algorithm: 'RS256',
- }
-)
-
-// Alive
-router.get("/", (_, res) => res.send("Auth service is up.") )
-
-// Register
-router.post('/register', async (req, res) => {
- const refresh = randomBytes(32).toString('hex')
- const newUser = new User({
- email: req.body.email,
- password: req.body.password,
- refresh,
- })
-
- try {
- const user = await newUser.save()
- const jwtToken = generateJwtToken(user)
-
- res.status(201).send({ jt: jwtToken, rt: refresh })
- } catch(err) {
- if (err._message) {
- res.status(422).send({ error: err._message })
- } else if (err.code && err.code === 11000) {
- res.status(409).send({ error: 'User with this email already exist' })
- } else {
- res.status(400).send({ error: 'Could not save the user' })
- }
- }
-})
-
-// Login
-router.post('/login', async (req, res) => {
- try {
- const user = await User.findByCredentials(req.body.email, req.body.password)
-
- if (!user) throw new Error()
-
- const refreshToken = await User.newRefreshToken(user)
-
- if (!refreshToken) throw new Error()
-
- const jwtToken = generateJwtToken(user)
-
- res.status(202).send({ jt: jwtToken, rt: refreshToken })
- } catch (err) {
- res.status(401).send({ error: 'Could not log in.' })
- }
-})
-
-// Logout
-router.post('/logout', async (req, res) => {
- try {
- const user = await User.checkRefreshToken(req.body.email, req.body.refresh)
-
- if (!user) throw new Error()
-
- await User.removeRefreshToken(user.email)
-
- res.status(204).send()
- } catch (err) {
- res.status(401).send({ error: 'Could not logout' })
- }
-})
-
-// Refresh token
-router.post('/refresh', async (req, res) => {
- try {
- const user = await User.checkRefreshToken(req.body.email, req.body.refresh)
-
- if (!user) throw new Error()
-
- const newRefreshToken = await User.newRefreshToken(user)
-
- if (!newRefreshToken) throw new Error()
-
- const jwtToken = generateJwtToken(user)
-
- res.status(201).send({ jt: jwtToken, rt: newRefreshToken })
- } catch (err) {
- res.status(401).send({ error: 'User logged out' })
- }
-})
-
-module.exports = router
diff --git a/routes/routes.js b/routes/routes.js
new file mode 100644
index 0000000..b1cfeec
--- /dev/null
+++ b/routes/routes.js
@@ -0,0 +1,71 @@
+const router = require('express').Router()
+const { randomBytes } = require('crypto')
+const createJwt = require('../helpers/createJwt')
+const User = require('../model/User')
+
+// Alive
+router.get("/", (_, res) => res.send("Auth service is up.") )
+
+// Register
+router.post('/register', async (req, res) => {
+ const refreshToken = randomBytes(32).toString('hex')
+ const newUser = new User({
+ email: req.body.email,
+ password: req.body.password,
+ sessions: [{
+ device: req.body.device,
+ refreshToken,
+ }],
+ })
+
+ try {
+ const user = await newUser.save()
+ const sessionId = user.sessions[0]._id
+ const jwtToken = createJwt(user, sessionId)
+
+ res.status(201).send({ sessionId, jwtToken, refreshToken })
+ } catch(err) {
+ if (err._message) {
+ res.status(422).send({ error: err._message })
+ } else if (err.code && err.code === 11000) {
+ res.status(409).send({ error: 'User with this email already exist' })
+ } else {
+ res.status(400).send({ error: 'Could not save the user' })
+ }
+ }
+})
+
+// Login
+router.post('/login', async (req, res) => {
+ try {
+ const { user, refreshToken } = await User.newSession(req.body)
+ const sessionId = user.sessions[user.sessions.length - 1]._id
+ const jwtToken = createJwt(user, sessionId)
+
+ res.status(202).send({ sessionId, jwtToken, refreshToken })
+ } catch (err) {
+ res.status(401).send({ error: 'Could not log in.' })
+ }
+})
+
+// Refresh token
+router.post('/refresh', async (req, res) => {
+ try {
+ const user = await User.checkRefreshToken(req.body)
+
+ if (!user) throw new Error()
+
+ const refreshToken = await User.refreshExistingToken({ user, sessionId: req.body.sessionId })
+
+ if (!refreshToken) throw new Error()
+
+ const jwtToken = createJwt(user)
+
+ res.status(201).send({ sessionId: req.body.sessionId, jwtToken, refreshToken })
+ } catch (err) {
+ console.log(err)
+ res.status(401).send({ error: 'Could not refresh token' })
+ }
+})
+
+module.exports = router