diff options
author | 2023-08-21 22:19:54 +0200 | |
---|---|---|
committer | 2023-08-21 22:23:57 +0200 | |
commit | 9fb0815b575cfc23ced6722b1a164328bd3cff1a (patch) | |
tree | 971a10bb74824d007cb74082a0a1d07dba3f30e2 | |
parent | 4d28ac359b25d89d0dbb42dd3a6d32269eebc619 (diff) | |
download | auth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.tar.gz auth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.tar.bz2 auth-service-9fb0815b575cfc23ced6722b1a164328bd3cff1a.zip |
refactor, new routes
-rw-r--r-- | helpers/createJwt.js | 22 | ||||
-rw-r--r-- | index.js | 5 | ||||
-rw-r--r-- | middleware/auth.js | 26 | ||||
-rw-r--r-- | model/User.js | 64 | ||||
-rw-r--r-- | routes/authRoutes.js | 33 | ||||
-rw-r--r-- | routes/index.js | 106 | ||||
-rw-r--r-- | routes/routes.js | 71 |
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 @@ -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 |