small refactorings

This commit is contained in:
lukeIam 2023-09-20 18:37:55 +01:00
parent 51b0750a3f
commit 2c90bba774
3 changed files with 51 additions and 42 deletions

View File

@ -17,10 +17,10 @@ class Auth {
} }
/** /**
* Inializes all passportjs stragegies and other passportjs ralated initialization. * Inializes all passportjs strategies and other passportjs ralated initialization.
*/ */
async initPassportJs() { async initPassportJs() {
// Check if we should load the local strategy // Check if we should load the local strategy (username + password login)
if (global.ServerSettings.authActiveAuthMethods.includes("local")) { if (global.ServerSettings.authActiveAuthMethods.includes("local")) {
passport.use(new LocalStrategy(this.localAuthCheckUserPw.bind(this))) passport.use(new LocalStrategy(this.localAuthCheckUserPw.bind(this)))
} }
@ -33,13 +33,17 @@ class Auth {
callbackURL: global.ServerSettings.authGoogleOauth20CallbackURL callbackURL: global.ServerSettings.authGoogleOauth20CallbackURL
}, (async function (accessToken, refreshToken, profile, done) { }, (async function (accessToken, refreshToken, profile, done) {
// TODO: do we want to create the users which does not exist? // TODO: do we want to create the users which does not exist?
// get user by email
const user = await Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase()) const user = await Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase())
if (!user || !user.isActive) { if (!user || !user.isActive) {
// deny login
done(null, null) done(null, null)
return return
} }
// permit login
return done(null, user) return done(null, user)
}).bind(this))) }).bind(this)))
} }
@ -59,17 +63,23 @@ class Auth {
}, },
(function (issuer, profile, done) { (function (issuer, profile, done) {
// TODO: do we want to create the users which does not exist? // TODO: do we want to create the users which does not exist?
// get user by email
var user = Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase()) var user = Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase())
if (!user || !user.isActive) { if (!user || !user.isActive) {
// deny login
done(null, null) done(null, null)
return return
} }
// permit login
return done(null, user) return done(null, user)
}).bind(this))) }).bind(this)))
} }
// should be already initialied here - but ci had some problems so check again
// token is required to encrypt/protect the info in jwts
if (!global.ServerSettings.tokenSecret) { if (!global.ServerSettings.tokenSecret) {
await this.initTokenSecret() await this.initTokenSecret()
} }
@ -83,22 +93,19 @@ class Auth {
// define how to seralize a user (to be put into the session) // define how to seralize a user (to be put into the session)
passport.serializeUser(function (user, cb) { passport.serializeUser(function (user, cb) {
process.nextTick(function () { process.nextTick(function () {
// only store username and id to session // only store id to session
// TODO: do we want to store more info in the session?
return cb(null, JSON.stringify({ return cb(null, JSON.stringify({
"username": user.username,
"id": user.id, "id": user.id,
"email": user.email,
})) }))
}) })
}) })
// define how to deseralize a user (use the username to get it from the database) // define how to deseralize a user (use the ID to get it from the database)
passport.deserializeUser((function (user, cb) { passport.deserializeUser((function (user, cb) {
process.nextTick((async function () { process.nextTick((async function () {
const parsedUserInfo = JSON.parse(user) const parsedUserInfo = JSON.parse(user)
// TODO: do the matching on username or better on id? // load the user by ID that is stored in the session
const dbUser = await Database.userModel.getUserByUsername(parsedUserInfo.username.toLowerCase()) const dbUser = await Database.userModel.getUserById(parsedUserInfo.id)
return cb(null, dbUser) return cb(null, dbUser)
}).bind(this)) }).bind(this))
}).bind(this)) }).bind(this))
@ -110,23 +117,28 @@ class Auth {
* @param {*} res Response object. * @param {*} res Response object.
*/ */
paramsToCookies(req, res) { paramsToCookies(req, res) {
if (req.query.isRest && (req.query.isRest.toLowerCase() == "true" || req.query.isRest.toLowerCase() == "false")) { if (req.query.isRest && req.query.isRest.toLowerCase() == "true") {
// store the isRest flag to the is_rest cookie
res.cookie('is_rest', req.query.isRest.toLowerCase(), { res.cookie('is_rest', req.query.isRest.toLowerCase(), {
maxAge: 120000 * 120, // Hack - this semms to be in UTC?? maxAge: 120000 * 120, // Hack - this semms to be in UTC??
httpOnly: true httpOnly: true
}) })
} }
else { else {
// no isRest-flag set -> set is_rest cookie to false
res.cookie('is_rest', "false", { res.cookie('is_rest', "false", {
maxAge: 120000 * 120, // Hack - this semms to be in UTC?? maxAge: 120000 * 120, // Hack - this semms to be in UTC??
httpOnly: true httpOnly: true
}) })
// check if we are missing a callback parameter - we need one if isRest=false
if (!req.query.callback || req.query.callback === "") { if (!req.query.callback || req.query.callback === "") {
res.status(400).send({ res.status(400).send({
message: 'No callback parameter' message: 'No callback parameter'
}) })
return return
} }
// store the callback url to the auth_cb cookie
res.cookie('auth_cb', req.query.callback, { res.cookie('auth_cb', req.query.callback, {
maxAge: 120000 * 120, // Hack - this semms to be in UTC?? maxAge: 120000 * 120, // Hack - this semms to be in UTC??
httpOnly: true httpOnly: true
@ -142,6 +154,7 @@ class Auth {
* @param {*} res Response object. * @param {*} res Response object.
*/ */
async handleLoginSuccessBasedOnCookie(req, res) { async handleLoginSuccessBasedOnCookie(req, res) {
// get userLogin json (information about the user, server and the session)
const data_json = await this.getUserLoginResponsePayload(req.user) const data_json = await this.getUserLoginResponsePayload(req.user)
if (req.cookies.is_rest && req.cookies.is_rest === "true") { if (req.cookies.is_rest && req.cookies.is_rest === "true") {
@ -152,7 +165,7 @@ class Auth {
// UI request -> check if we have a callback url // UI request -> check if we have a callback url
// TODO: do we want to somehow limit the values for auth_cb? // TODO: do we want to somehow limit the values for auth_cb?
if (req.cookies.auth_cb && req.cookies.auth_cb.startsWith("http")) { if (req.cookies.auth_cb && req.cookies.auth_cb.startsWith("http")) {
// UI request -> redirect // UI request -> redirect to auth_cb url and send the jwt token as parameter
res.redirect(302, `${req.cookies.auth_cb}?setToken=${data_json.user.token}`) res.redirect(302, `${req.cookies.auth_cb}?setToken=${data_json.user.token}`)
} }
else { else {
@ -165,7 +178,7 @@ class Auth {
* Creates all (express) routes required for authentication. * Creates all (express) routes required for authentication.
* @param {express.Router} router * @param {express.Router} router
*/ */
initAuthRoutes(router) { async initAuthRoutes(router) {
// Local strategy login route (takes username and password) // Local strategy login route (takes username and password)
router.post('/login', passport.authenticate('local'), router.post('/login', passport.authenticate('local'),
(async function (req, res) { (async function (req, res) {
@ -177,6 +190,7 @@ class Auth {
// google-oauth20 strategy login route (this redirects to the google login) // google-oauth20 strategy login route (this redirects to the google login)
router.get('/auth/google', (req, res, next) => { router.get('/auth/google', (req, res, next) => {
const auth_func = passport.authenticate('google', { scope: ['email'] }) const auth_func = passport.authenticate('google', { scope: ['email'] })
// params (isRest, callback) to a cookie that will be send to the client
this.paramsToCookies(req, res) this.paramsToCookies(req, res)
auth_func(req, res, next) auth_func(req, res, next)
}) })
@ -184,12 +198,14 @@ class Auth {
// google-oauth20 strategy callback route (this receives the token from google) // google-oauth20 strategy callback route (this receives the token from google)
router.get('/auth/google/callback', router.get('/auth/google/callback',
passport.authenticate('google'), passport.authenticate('google'),
// on a successfull login: read the cookies and react like the client requested (callback or json)
this.handleLoginSuccessBasedOnCookie.bind(this) this.handleLoginSuccessBasedOnCookie.bind(this)
) )
// openid strategy login route (this redirects to the configured openid login provider) // openid strategy login route (this redirects to the configured openid login provider)
router.get('/auth/openid', (req, res, next) => { router.get('/auth/openid', (req, res, next) => {
const auth_func = passport.authenticate('openidconnect') const auth_func = passport.authenticate('openidconnect')
// params (isRest, callback) to a cookie that will be send to the client
this.paramsToCookies(req, res) this.paramsToCookies(req, res)
auth_func(req, res, next) auth_func(req, res, next)
}) })
@ -197,6 +213,7 @@ class Auth {
// openid strategy callback route (this receives the token from the configured openid login provider) // openid strategy callback route (this receives the token from the configured openid login provider)
router.get('/auth/openid/callback', router.get('/auth/openid/callback',
passport.authenticate('openidconnect'), passport.authenticate('openidconnect'),
// on a successfull login: read the cookies and react like the client requested (callback or json)
this.handleLoginSuccessBasedOnCookie.bind(this) this.handleLoginSuccessBasedOnCookie.bind(this)
) )
@ -239,7 +256,7 @@ class Auth {
} }
/** /**
* Function to generate a jwt token for a given user. * Function to validate a jwt token for a given user.
* @param {string} token * @param {string} token
* @returns the tokens data. * @returns the tokens data.
*/ */
@ -253,7 +270,7 @@ class Auth {
} }
/** /**
* Generate a token for each user. * Generate a token which is used to encrpt/protect the jwts.
*/ */
async initTokenSecret() { async initTokenSecret() {
if (process.env.TOKEN_SECRET) { // User can supply their own token secret if (process.env.TOKEN_SECRET) { // User can supply their own token secret
@ -279,12 +296,15 @@ class Auth {
* @param {function} done * @param {function} done
*/ */
jwtAuthCheck(jwt_payload, done) { jwtAuthCheck(jwt_payload, done) {
const user = Database.userModel.getUserByUsername(jwt_payload.username.toLowerCase()) // load user by id from the jwt token
const user = Database.userModel.getUserById(jwt_payload.id)
if (!user || !user.isActive) { if (!user || !user.isActive) {
// deny login
done(null, null) done(null, null)
return return
} }
// approve login
done(null, user) done(null, user)
return return
} }
@ -296,6 +316,7 @@ class Auth {
* @param {function} done * @param {function} done
*/ */
async localAuthCheckUserPw(username, password, done) { async localAuthCheckUserPw(username, password, done) {
// Load the user given it's username
const user = await Database.userModel.getUserByUsername(username.toLowerCase()) const user = await Database.userModel.getUserByUsername(username.toLowerCase())
if (!user || !user.isActive) { if (!user || !user.isActive) {
@ -306,9 +327,11 @@ class Auth {
// Check passwordless root user // Check passwordless root user
if (user.id === 'root' && (!user.pash || user.pash === '')) { if (user.id === 'root' && (!user.pash || user.pash === '')) {
if (password) { if (password) {
// deny login
done(null, null) done(null, null)
return return
} }
// approve login
done(null, user) done(null, user)
return return
} }
@ -316,9 +339,11 @@ class Auth {
// Check password match // Check password match
const compare = await bcrypt.compare(password, user.pash) const compare = await bcrypt.compare(password, user.pash)
if (compare) { if (compare) {
// approve login
done(null, user) done(null, user)
return return
} }
// deny login
done(null, null) done(null, null)
return return
} }
@ -343,7 +368,7 @@ class Auth {
/** /**
* Return the login info payload for a user. * Return the login info payload for a user.
* @param {string} username * @param {string} username
* @returns {string} jsonPayload * @returns {Promise<string>} jsonPayload
*/ */
async getUserLoginResponsePayload(user) { async getUserLoginResponsePayload(user) {
const libraryIds = await Database.libraryModel.getAllLibraryIds() const libraryIds = await Database.libraryModel.getAllLibraryIds()

View File

@ -87,6 +87,7 @@ class Server {
} }
authMiddleware(req, res, next) { authMiddleware(req, res, next) {
// ask passportjs if the current request is authenticated
this.auth.isAuthenticated(req, res, next) this.auth.isAuthenticated(req, res, next)
} }
@ -145,7 +146,7 @@ class Server {
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
cookie: { cookie: {
// also send the cookie if were hare not on https // also send the cookie if were are not on https (not every use has https)
secure: false secure: false
}, },
})) }))
@ -155,8 +156,6 @@ class Server {
app.use(passport.session()) app.use(passport.session())
// config passport.js // config passport.js
await this.auth.initPassportJs() await this.auth.initPassportJs()
// use auth on all routes - not used now
// app.use(passport.authenticate('session'))
const router = express.Router() const router = express.Router()
app.use(global.RouterBasePath, router) app.use(global.RouterBasePath, router)
@ -200,7 +199,7 @@ class Server {
}) })
// Auth routes // Auth routes
this.auth.initAuthRoutes(router) await this.auth.initAuthRoutes(router)
// Client dynamic routes // Client dynamic routes
const dyanimicRoutes = [ const dyanimicRoutes = [

View File

@ -2,8 +2,6 @@ const SocketIO = require('socket.io')
const Logger = require('./Logger') const Logger = require('./Logger')
const Database = require('./Database') const Database = require('./Database')
const Auth = require('./Auth') const Auth = require('./Auth')
const passport = require('passport')
const expressSession = require('express-session')
class SocketAuthority { class SocketAuthority {
constructor() { constructor() {
@ -85,23 +83,6 @@ class SocketAuthority {
} }
}) })
/*
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
io.use(wrap(expressSession({
secret: global.ServerSettings.tokenSecret,
resave: false,
saveUninitialized: false,
cookie: {
// also send the cookie if were hare not on https
secure: false
},
})));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
*/
this.io.on('connection', (socket) => { this.io.on('connection', (socket) => {
this.clients[socket.id] = { this.clients[socket.id] = {
id: socket.id, id: socket.id,
@ -168,14 +149,18 @@ class SocketAuthority {
// When setting up a socket connection the user needs to be associated with a socket id // When setting up a socket connection the user needs to be associated with a socket id
// for this the client will send a 'auth' event that includes the users API token // for this the client will send a 'auth' event that includes the users API token
async authenticateSocket(socket, token) { async authenticateSocket(socket, token) {
// TODO // we don't use passport to authenticate the jwt we get over the socket connection.
// it's easier to directly verify/decode it.
const token_data = Auth.validateAccessToken(token) const token_data = Auth.validateAccessToken(token)
if (!token_data || !token_data.username) { if (!token_data || !token_data.id) {
// Token invalid
Logger.error('Cannot validate socket - invalid token') Logger.error('Cannot validate socket - invalid token')
return socket.emit('invalid_token') return socket.emit('invalid_token')
} }
const user = await Database.userModel.getUserByUsername(token_data.username) // get the user via the id from the decoded jwt.
const user = await Database.userModel.getUserById(token_data.id)
if (!user) { if (!user) {
// user not found
Logger.error('Cannot validate socket - invalid token') Logger.error('Cannot validate socket - invalid token')
return socket.emit('invalid_token') return socket.emit('invalid_token')
} }