mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-17 00:08:55 +01:00
Add new API endpoint for updating auth-settings and update passport auth strategies
This commit is contained in:
parent
078cb0855f
commit
237fe84c54
@ -199,13 +199,19 @@ export default {
|
|||||||
if (this.enableOpenIDAuth) this.newAuthSettings.authActiveAuthMethods.push('openid')
|
if (this.enableOpenIDAuth) this.newAuthSettings.authActiveAuthMethods.push('openid')
|
||||||
|
|
||||||
this.savingSettings = true
|
this.savingSettings = true
|
||||||
const success = await this.$store.dispatch('updateServerSettings', this.newAuthSettings)
|
this.$axios
|
||||||
this.savingSettings = false
|
.$patch('/api/auth-settings', this.newAuthSettings)
|
||||||
if (success) {
|
.then((data) => {
|
||||||
|
this.$store.commit('setServerSettings', data.serverSettings)
|
||||||
this.$toast.success('Server settings updated')
|
this.$toast.success('Server settings updated')
|
||||||
} else {
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update server settings', error)
|
||||||
this.$toast.error('Failed to update server settings')
|
this.$toast.error('Failed to update server settings')
|
||||||
}
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.savingSettings = false
|
||||||
|
})
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.newAuthSettings = {
|
this.newAuthSettings = {
|
||||||
|
@ -36,7 +36,12 @@ class Auth {
|
|||||||
async initPassportJs() {
|
async initPassportJs() {
|
||||||
// Check if we should load the local strategy (username + password login)
|
// 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)))
|
this.initAuthStrategyPassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should load the openid strategy
|
||||||
|
if (global.ServerSettings.authActiveAuthMethods.includes("openid")) {
|
||||||
|
this.initAuthStrategyOpenID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we should load the google-oauth20 strategy
|
// Check if we should load the google-oauth20 strategy
|
||||||
@ -62,8 +67,44 @@ class Auth {
|
|||||||
}).bind(this)))
|
}).bind(this)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we should load the openid strategy
|
// Load the JwtStrategy (always) -> for bearer token auth
|
||||||
if (global.ServerSettings.authActiveAuthMethods.includes("openid")) {
|
passport.use(new JwtStrategy({
|
||||||
|
jwtFromRequest: ExtractJwt.fromExtractors([ExtractJwt.fromAuthHeaderAsBearerToken(), ExtractJwt.fromUrlQueryParameter('token')]),
|
||||||
|
secretOrKey: Database.serverSettings.tokenSecret
|
||||||
|
}, this.jwtAuthCheck.bind(this)))
|
||||||
|
|
||||||
|
// define how to seralize a user (to be put into the session)
|
||||||
|
passport.serializeUser(function (user, cb) {
|
||||||
|
process.nextTick(function () {
|
||||||
|
// only store id to session
|
||||||
|
return cb(null, JSON.stringify({
|
||||||
|
id: user.id,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// define how to deseralize a user (use the ID to get it from the database)
|
||||||
|
passport.deserializeUser((function (user, cb) {
|
||||||
|
process.nextTick((async function () {
|
||||||
|
const parsedUserInfo = JSON.parse(user)
|
||||||
|
// load the user by ID that is stored in the session
|
||||||
|
const dbUser = await Database.userModel.getUserById(parsedUserInfo.id)
|
||||||
|
return cb(null, dbUser)
|
||||||
|
}).bind(this))
|
||||||
|
}).bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passport use LocalStrategy
|
||||||
|
*/
|
||||||
|
initAuthStrategyPassword() {
|
||||||
|
passport.use(new LocalStrategy(this.localAuthCheckUserPw.bind(this)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passport use OpenIDClient.Strategy
|
||||||
|
*/
|
||||||
|
initAuthStrategyOpenID() {
|
||||||
const openIdIssuerClient = new OpenIDClient.Issuer({
|
const openIdIssuerClient = new OpenIDClient.Issuer({
|
||||||
issuer: global.ServerSettings.authOpenIDIssuerURL,
|
issuer: global.ServerSettings.authOpenIDIssuerURL,
|
||||||
authorization_endpoint: global.ServerSettings.authOpenIDAuthorizationURL,
|
authorization_endpoint: global.ServerSettings.authOpenIDAuthorizationURL,
|
||||||
@ -140,31 +181,28 @@ class Auth {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the JwtStrategy (always) -> for bearer token auth
|
/**
|
||||||
passport.use(new JwtStrategy({
|
* Unuse strategy
|
||||||
jwtFromRequest: ExtractJwt.fromExtractors([ExtractJwt.fromAuthHeaderAsBearerToken(), ExtractJwt.fromUrlQueryParameter('token')]),
|
*
|
||||||
secretOrKey: Database.serverSettings.tokenSecret
|
* @param {string} name
|
||||||
}, this.jwtAuthCheck.bind(this)))
|
*/
|
||||||
|
unuseAuthStrategy(name) {
|
||||||
|
passport.unuse(name)
|
||||||
|
}
|
||||||
|
|
||||||
// define how to seralize a user (to be put into the session)
|
/**
|
||||||
passport.serializeUser(function (user, cb) {
|
* Use strategy
|
||||||
process.nextTick(function () {
|
*
|
||||||
// only store id to session
|
* @param {string} name
|
||||||
return cb(null, JSON.stringify({
|
*/
|
||||||
id: user.id,
|
useAuthStrategy(name) {
|
||||||
}))
|
if (name === 'openid') {
|
||||||
})
|
this.initAuthStrategyOpenID()
|
||||||
})
|
} else if (name === 'local') {
|
||||||
|
this.initAuthStrategyPassword()
|
||||||
// define how to deseralize a user (use the ID to get it from the database)
|
} else {
|
||||||
passport.deserializeUser((function (user, cb) {
|
Logger.error('[Auth] Invalid auth strategy ' + name)
|
||||||
process.nextTick((async function () {
|
}
|
||||||
const parsedUserInfo = JSON.parse(user)
|
|
||||||
// load the user by ID that is stored in the session
|
|
||||||
const dbUser = await Database.userModel.getUserById(parsedUserInfo.id)
|
|
||||||
return cb(null, dbUser)
|
|
||||||
}).bind(this))
|
|
||||||
}).bind(this))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,7 +129,7 @@ class MiscController {
|
|||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
const settingsUpdate = req.body
|
const settingsUpdate = req.body
|
||||||
if (!settingsUpdate || !isObject(settingsUpdate)) {
|
if (!isObject(settingsUpdate)) {
|
||||||
return res.status(400).send('Invalid settings update object')
|
return res.status(400).send('Invalid settings update object')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,5 +604,91 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
return res.json(Database.serverSettings.authenticationSettings)
|
return res.json(Database.serverSettings.authenticationSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PATCH: api/auth-settings
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
*/
|
||||||
|
async updateAuthSettings(req, res) {
|
||||||
|
if (!req.user.isAdminOrUp) {
|
||||||
|
Logger.error(`[MiscController] Non-admin user "${req.user.username}" attempted to update auth settings`)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsUpdate = req.body
|
||||||
|
if (!isObject(settingsUpdate)) {
|
||||||
|
return res.status(400).send('Invalid auth settings update object')
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasUpdates = false
|
||||||
|
|
||||||
|
const currentAuthenticationSettings = Database.serverSettings.authenticationSettings
|
||||||
|
const originalAuthMethods = [...currentAuthenticationSettings.authActiveAuthMethods]
|
||||||
|
|
||||||
|
// TODO: Better validation of auth settings once auth settings are separated from server settings
|
||||||
|
for (const key in currentAuthenticationSettings) {
|
||||||
|
if (settingsUpdate[key] === undefined) continue
|
||||||
|
|
||||||
|
if (key === 'authActiveAuthMethods') {
|
||||||
|
let updatedAuthMethods = settingsUpdate[key]?.filter?.((authMeth) => Database.serverSettings.supportedAuthMethods.includes(authMeth))
|
||||||
|
if (Array.isArray(updatedAuthMethods) && updatedAuthMethods.length) {
|
||||||
|
updatedAuthMethods.sort()
|
||||||
|
currentAuthenticationSettings[key].sort()
|
||||||
|
if (updatedAuthMethods.join() !== currentAuthenticationSettings[key].join()) {
|
||||||
|
Logger.debug(`[MiscController] Updating auth settings key "authActiveAuthMethods" from "${currentAuthenticationSettings[key].join()}" to "${updatedAuthMethods.join()}"`)
|
||||||
|
Database.serverSettings[key] = updatedAuthMethods
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const updatedValueType = typeof settingsUpdate[key]
|
||||||
|
if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
|
||||||
|
if (updatedValueType !== 'boolean') {
|
||||||
|
Logger.warn(`[MiscController] Invalid value for ${key}. Expected boolean`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if (updatedValueType !== null && updatedValueType !== 'string') {
|
||||||
|
Logger.warn(`[MiscController] Invalid value for ${key}. Expected string or null`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let updatedValue = settingsUpdate[key]
|
||||||
|
if (updatedValue === '') updatedValue = null
|
||||||
|
let currentValue = currentAuthenticationSettings[key]
|
||||||
|
if (currentValue === '') currentValue = null
|
||||||
|
|
||||||
|
if (updatedValue !== currentValue) {
|
||||||
|
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${currentValue}" to "${updatedValue}"`)
|
||||||
|
Database.serverSettings[key] = updatedValue
|
||||||
|
hasUpdates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUpdates) {
|
||||||
|
// Use/unuse auth methods
|
||||||
|
Database.serverSettings.supportedAuthMethods.forEach((authMethod) => {
|
||||||
|
if (originalAuthMethods.includes(authMethod) && !Database.serverSettings.authActiveAuthMethods.includes(authMethod)) {
|
||||||
|
// Auth method has been removed
|
||||||
|
Logger.info(`[MiscController] Disabling active auth method "${authMethod}"`)
|
||||||
|
this.auth.unuseAuthStrategy(authMethod)
|
||||||
|
} else if (!originalAuthMethods.includes(authMethod) && Database.serverSettings.authActiveAuthMethods.includes(authMethod)) {
|
||||||
|
// Auth method has been added
|
||||||
|
Logger.info(`[MiscController] Enabling active auth method "${authMethod}"`)
|
||||||
|
this.auth.useAuthStrategy(authMethod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Database.updateServerSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
serverSettings: Database.serverSettings.toJSONForBrowser()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = new MiscController()
|
module.exports = new MiscController()
|
@ -59,19 +59,19 @@ class ServerSettings {
|
|||||||
this.authActiveAuthMethods = ['local']
|
this.authActiveAuthMethods = ['local']
|
||||||
|
|
||||||
// google-oauth20 settings
|
// google-oauth20 settings
|
||||||
this.authGoogleOauth20ClientID = ''
|
this.authGoogleOauth20ClientID = null
|
||||||
this.authGoogleOauth20ClientSecret = ''
|
this.authGoogleOauth20ClientSecret = null
|
||||||
this.authGoogleOauth20CallbackURL = ''
|
this.authGoogleOauth20CallbackURL = null
|
||||||
|
|
||||||
// openid settings
|
// openid settings
|
||||||
this.authOpenIDIssuerURL = ''
|
this.authOpenIDIssuerURL = null
|
||||||
this.authOpenIDAuthorizationURL = ''
|
this.authOpenIDAuthorizationURL = null
|
||||||
this.authOpenIDTokenURL = ''
|
this.authOpenIDTokenURL = null
|
||||||
this.authOpenIDUserInfoURL = ''
|
this.authOpenIDUserInfoURL = null
|
||||||
this.authOpenIDJwksURL = ''
|
this.authOpenIDJwksURL = null
|
||||||
this.authOpenIDLogoutURL = ''
|
this.authOpenIDLogoutURL = null
|
||||||
this.authOpenIDClientID = ''
|
this.authOpenIDClientID = null
|
||||||
this.authOpenIDClientSecret = ''
|
this.authOpenIDClientSecret = null
|
||||||
this.authOpenIDButtonText = 'Login with OpenId'
|
this.authOpenIDButtonText = 'Login with OpenId'
|
||||||
this.authOpenIDAutoLaunch = false
|
this.authOpenIDAutoLaunch = false
|
||||||
this.authOpenIDAutoRegister = false
|
this.authOpenIDAutoRegister = false
|
||||||
@ -118,18 +118,18 @@ class ServerSettings {
|
|||||||
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
||||||
|
|
||||||
this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local']
|
this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local']
|
||||||
this.authGoogleOauth20ClientID = settings.authGoogleOauth20ClientID || ''
|
this.authGoogleOauth20ClientID = settings.authGoogleOauth20ClientID || null
|
||||||
this.authGoogleOauth20ClientSecret = settings.authGoogleOauth20ClientSecret || ''
|
this.authGoogleOauth20ClientSecret = settings.authGoogleOauth20ClientSecret || null
|
||||||
this.authGoogleOauth20CallbackURL = settings.authGoogleOauth20CallbackURL || ''
|
this.authGoogleOauth20CallbackURL = settings.authGoogleOauth20CallbackURL || null
|
||||||
|
|
||||||
this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || ''
|
this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || null
|
||||||
this.authOpenIDAuthorizationURL = settings.authOpenIDAuthorizationURL || ''
|
this.authOpenIDAuthorizationURL = settings.authOpenIDAuthorizationURL || null
|
||||||
this.authOpenIDTokenURL = settings.authOpenIDTokenURL || ''
|
this.authOpenIDTokenURL = settings.authOpenIDTokenURL || null
|
||||||
this.authOpenIDUserInfoURL = settings.authOpenIDUserInfoURL || ''
|
this.authOpenIDUserInfoURL = settings.authOpenIDUserInfoURL || null
|
||||||
this.authOpenIDJwksURL = settings.authOpenIDJwksURL || ''
|
this.authOpenIDJwksURL = settings.authOpenIDJwksURL || null
|
||||||
this.authOpenIDLogoutURL = settings.authOpenIDLogoutURL || ''
|
this.authOpenIDLogoutURL = settings.authOpenIDLogoutURL || null
|
||||||
this.authOpenIDClientID = settings.authOpenIDClientID || ''
|
this.authOpenIDClientID = settings.authOpenIDClientID || null
|
||||||
this.authOpenIDClientSecret = settings.authOpenIDClientSecret || ''
|
this.authOpenIDClientSecret = settings.authOpenIDClientSecret || null
|
||||||
this.authOpenIDButtonText = settings.authOpenIDButtonText || 'Login with OpenId'
|
this.authOpenIDButtonText = settings.authOpenIDButtonText || 'Login with OpenId'
|
||||||
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
|
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
|
||||||
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
|
||||||
@ -142,9 +142,9 @@ class ServerSettings {
|
|||||||
// remove uninitialized methods
|
// remove uninitialized methods
|
||||||
// GoogleOauth20
|
// GoogleOauth20
|
||||||
if (this.authActiveAuthMethods.includes('google-oauth20') && (
|
if (this.authActiveAuthMethods.includes('google-oauth20') && (
|
||||||
this.authGoogleOauth20ClientID === '' ||
|
!this.authGoogleOauth20ClientID ||
|
||||||
this.authGoogleOauth20ClientSecret === '' ||
|
!this.authGoogleOauth20ClientSecret ||
|
||||||
this.authGoogleOauth20CallbackURL === ''
|
!this.authGoogleOauth20CallbackURL
|
||||||
)) {
|
)) {
|
||||||
this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('google-oauth20', 0), 1)
|
this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('google-oauth20', 0), 1)
|
||||||
}
|
}
|
||||||
@ -152,13 +152,13 @@ class ServerSettings {
|
|||||||
// remove uninitialized methods
|
// remove uninitialized methods
|
||||||
// OpenID
|
// OpenID
|
||||||
if (this.authActiveAuthMethods.includes('openid') && (
|
if (this.authActiveAuthMethods.includes('openid') && (
|
||||||
this.authOpenIDIssuerURL === '' ||
|
!this.authOpenIDIssuerURL ||
|
||||||
this.authOpenIDAuthorizationURL === '' ||
|
!this.authOpenIDAuthorizationURL ||
|
||||||
this.authOpenIDTokenURL === '' ||
|
!this.authOpenIDTokenURL ||
|
||||||
this.authOpenIDUserInfoURL === '' ||
|
!this.authOpenIDUserInfoURL ||
|
||||||
this.authOpenIDJwksURL === '' ||
|
!this.authOpenIDJwksURL ||
|
||||||
this.authOpenIDClientID === '' ||
|
!this.authOpenIDClientID ||
|
||||||
this.authOpenIDClientSecret === ''
|
!this.authOpenIDClientSecret
|
||||||
)) {
|
)) {
|
||||||
this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('openid', 0), 1)
|
this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('openid', 0), 1)
|
||||||
}
|
}
|
||||||
@ -254,6 +254,10 @@ class ServerSettings {
|
|||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get supportedAuthMethods() {
|
||||||
|
return ['local', 'openid']
|
||||||
|
}
|
||||||
|
|
||||||
get authenticationSettings() {
|
get authenticationSettings() {
|
||||||
return {
|
return {
|
||||||
authActiveAuthMethods: this.authActiveAuthMethods,
|
authActiveAuthMethods: this.authActiveAuthMethods,
|
||||||
|
@ -35,6 +35,7 @@ const Series = require('../objects/entities/Series')
|
|||||||
|
|
||||||
class ApiRouter {
|
class ApiRouter {
|
||||||
constructor(Server) {
|
constructor(Server) {
|
||||||
|
/** @type {import('../Auth')} */
|
||||||
this.auth = Server.auth
|
this.auth = Server.auth
|
||||||
this.playbackSessionManager = Server.playbackSessionManager
|
this.playbackSessionManager = Server.playbackSessionManager
|
||||||
this.abMergeManager = Server.abMergeManager
|
this.abMergeManager = Server.abMergeManager
|
||||||
@ -310,6 +311,7 @@ class ApiRouter {
|
|||||||
this.router.delete('/genres/:genre', MiscController.deleteGenre.bind(this))
|
this.router.delete('/genres/:genre', MiscController.deleteGenre.bind(this))
|
||||||
this.router.post('/validate-cron', MiscController.validateCronExpression.bind(this))
|
this.router.post('/validate-cron', MiscController.validateCronExpression.bind(this))
|
||||||
this.router.get('/auth-settings', MiscController.getAuthSettings.bind(this))
|
this.router.get('/auth-settings', MiscController.getAuthSettings.bind(this))
|
||||||
|
this.router.patch('/auth-settings', MiscController.updateAuthSettings.bind(this))
|
||||||
this.router.post('/watcher/update', MiscController.updateWatchedPath.bind(this))
|
this.router.post('/watcher/update', MiscController.updateWatchedPath.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user