mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-17 00:08:55 +01:00
Update /status endpoint to return available auth methods, fix socket auth, update openid to use username instead of email
This commit is contained in:
parent
9922294507
commit
f6de373388
@ -25,8 +25,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
<div v-else-if="isInit" class="w-full max-w-md px-8 pb-8 pt-4 -mt-40">
|
||||||
<p class="text-3xl text-white text-center mb-4">{{ $strings.HeaderLogin }}</p>
|
<p class="text-3xl text-white text-center mb-4">{{ $strings.HeaderLogin }}</p>
|
||||||
|
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
|
|
||||||
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
|
||||||
|
|
||||||
<form v-show="login_local" @submit.prevent="submitForm">
|
<form v-show="login_local" @submit.prevent="submitForm">
|
||||||
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelUsername }}</label>
|
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelUsername }}</label>
|
||||||
<ui-text-input v-model="username" :disabled="processing" class="mb-3 w-full" />
|
<ui-text-input v-model="username" :disabled="processing" class="mb-3 w-full" />
|
||||||
@ -37,7 +40,9 @@
|
|||||||
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
|
<ui-btn type="submit" :disabled="processing" color="primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<hr />
|
|
||||||
|
<div v-if="login_local && (login_google_oauth20 || login_openid)" class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
|
|
||||||
<div class="w-full flex py-3">
|
<div class="w-full flex py-3">
|
||||||
<a v-show="login_google_oauth20" :href="`http://localhost:3333/auth/google?callback=${currentUrl}`">
|
<a v-show="login_google_oauth20" :href="`http://localhost:3333/auth/google?callback=${currentUrl}`">
|
||||||
<ui-btn color="primary" class="leading-none">Login with Google</ui-btn>
|
<ui-btn color="primary" class="leading-none">Login with Google</ui-btn>
|
||||||
@ -106,6 +111,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
|
googleAuthUri() {
|
||||||
|
return `${process.env.serverUrl}/auth/openid?callback=${currentUrl}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -210,14 +218,16 @@ export default {
|
|||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$axios
|
||||||
.$get('/status')
|
.$get('/status')
|
||||||
.then((res) => {
|
.then((data) => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.isInit = res.isInit
|
this.isInit = data.isInit
|
||||||
this.showInitScreen = !res.isInit
|
this.showInitScreen = !data.isInit
|
||||||
this.$setServerLanguageCode(res.language)
|
this.$setServerLanguageCode(data.language)
|
||||||
if (this.showInitScreen) {
|
if (this.showInitScreen) {
|
||||||
this.ConfigPath = res.ConfigPath || ''
|
this.ConfigPath = data.ConfigPath || ''
|
||||||
this.MetadataPath = res.MetadataPath || ''
|
this.MetadataPath = data.MetadataPath || ''
|
||||||
|
} else {
|
||||||
|
this.updateLoginVisibility(data.authMethods || [])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -226,43 +236,34 @@ export default {
|
|||||||
this.criticalError = 'Status check failed'
|
this.criticalError = 'Status check failed'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async updateLoginVisibility() {
|
updateLoginVisibility(authMethods) {
|
||||||
await this.$axios
|
if (authMethods.includes('local') || !authMethods.length) {
|
||||||
.$get('/auth_methods')
|
this.login_local = true
|
||||||
.then((response) => {
|
} else {
|
||||||
if (response.includes('local')) {
|
this.login_local = false
|
||||||
this.login_local = true
|
}
|
||||||
} else {
|
|
||||||
this.login_local = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.includes('google-oauth20')) {
|
if (authMethods.includes('google-oauth20')) {
|
||||||
this.login_google_oauth20 = true
|
this.login_google_oauth20 = true
|
||||||
} else {
|
} else {
|
||||||
this.login_google_oauth20 = false
|
this.login_google_oauth20 = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.includes('openid')) {
|
if (authMethods.includes('openid')) {
|
||||||
this.login_openid = true
|
this.login_openid = true
|
||||||
} else {
|
} else {
|
||||||
this.login_openid = false
|
this.login_openid = false
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed', error.response)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.$nextTick(async () => await this.updateLoginVisibility())
|
|
||||||
if (new URLSearchParams(window.location.search).get('setToken')) {
|
if (new URLSearchParams(window.location.search).get('setToken')) {
|
||||||
localStorage.setItem('token', new URLSearchParams(window.location.search).get('setToken'))
|
localStorage.setItem('token', new URLSearchParams(window.location.search).get('setToken'))
|
||||||
}
|
}
|
||||||
if (localStorage.getItem('token')) {
|
if (localStorage.getItem('token')) {
|
||||||
var userfound = await this.checkAuth()
|
if (await this.checkAuth()) return // if valid user no need to check status
|
||||||
if (userfound) return // if valid user no need to check status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkStatus()
|
this.checkStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,9 @@ class Auth {
|
|||||||
(async function (issuer, profile, done) {
|
(async 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
|
const user = await Database.userModel.getUserByUsername(profile.username)
|
||||||
var user = await Database.userModel.getUserByEmail(profile.emails[0].value.toLowerCase())
|
|
||||||
|
|
||||||
if (!user || !user.isActive) {
|
if (!user?.isActive) {
|
||||||
// deny login
|
// deny login
|
||||||
done(null, null)
|
done(null, null)
|
||||||
return
|
return
|
||||||
@ -106,9 +105,10 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the client's choise how the login callback should happen in temp cookies.
|
* Stores the client's choice how the login callback should happen in temp cookies
|
||||||
* @param {*} req Request object.
|
*
|
||||||
* @param {*} res Response object.
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
*/
|
*/
|
||||||
paramsToCookies(req, res) {
|
paramsToCookies(req, res) {
|
||||||
if (req.query.isRest && req.query.isRest.toLowerCase() == "true") {
|
if (req.query.isRest && req.query.isRest.toLowerCase() == "true") {
|
||||||
@ -140,12 +140,12 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the client in the right mode about a successfull login and the token
|
* Informs the client in the right mode about a successfull login and the token
|
||||||
* (clients choise is restored from cookies).
|
* (clients choise is restored from cookies).
|
||||||
* @param {*} req Request object.
|
*
|
||||||
* @param {*} res Response object.
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
*/
|
*/
|
||||||
async handleLoginSuccessBasedOnCookie(req, res) {
|
async handleLoginSuccessBasedOnCookie(req, res) {
|
||||||
// get userLogin json (information about the user, server and the session)
|
// get userLogin json (information about the user, server and the session)
|
||||||
@ -170,16 +170,15 @@ class Auth {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates all (express) routes required for authentication.
|
* Creates all (express) routes required for authentication.
|
||||||
* @param {express.Router} router
|
*
|
||||||
|
* @param {import('express').Router} router
|
||||||
*/
|
*/
|
||||||
async 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 (req, res) => {
|
||||||
(async function (req, res) {
|
// return the user login response json if the login was successfull
|
||||||
// return the user login response json if the login was successfull
|
res.json(await this.getUserLoginResponsePayload(req.user))
|
||||||
res.json(await this.getUserLoginResponsePayload(req.user))
|
})
|
||||||
}).bind(this)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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) => {
|
||||||
@ -222,18 +221,13 @@ class Auth {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get avilible auth methods
|
|
||||||
router.get('/auth_methods', (req, res) => {
|
|
||||||
res.json(global.ServerSettings.authActiveAuthMethods)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* middleware to use in express to only allow authenticated users.
|
* middleware to use in express to only allow authenticated users.
|
||||||
* @param {express.Request} req
|
* @param {import('express').Request} req
|
||||||
* @param {express.Response} res
|
* @param {import('express').Response} res
|
||||||
* @param {express.NextFunction} next
|
* @param {import('express').NextFunction} next
|
||||||
*/
|
*/
|
||||||
isAuthenticated(req, res, next) {
|
isAuthenticated(req, res, next) {
|
||||||
// check if session cookie says that we are authenticated
|
// check if session cookie says that we are authenticated
|
||||||
@ -246,18 +240,20 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to generate a jwt token for a given user.
|
* Function to generate a jwt token for a given user
|
||||||
|
*
|
||||||
* @param {Object} user
|
* @param {Object} user
|
||||||
* @returns the token.
|
* @returns {string} token
|
||||||
*/
|
*/
|
||||||
generateAccessToken(user) {
|
generateAccessToken(user) {
|
||||||
return jwt.sign({ userId: user.id, username: user.username }, global.ServerSettings.tokenSecret)
|
return jwt.sign({ userId: user.id, username: user.username }, global.ServerSettings.tokenSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to validate 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 {Object} tokens data
|
||||||
*/
|
*/
|
||||||
static validateAccessToken(token) {
|
static validateAccessToken(token) {
|
||||||
try {
|
try {
|
||||||
@ -365,9 +361,10 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the login info payload for a user.
|
* Return the login info payload for a user
|
||||||
* @param {string} username
|
*
|
||||||
* @returns {Promise<string>} jsonPayload
|
* @param {Object} user
|
||||||
|
* @returns {Promise<Object>} jsonPayload
|
||||||
*/
|
*/
|
||||||
async getUserLoginResponsePayload(user) {
|
async getUserLoginResponsePayload(user) {
|
||||||
const libraryIds = await Database.libraryModel.getAllLibraryIds()
|
const libraryIds = await Database.libraryModel.getAllLibraryIds()
|
||||||
|
@ -238,7 +238,8 @@ class Server {
|
|||||||
// server has been initialized if a root user exists
|
// server has been initialized if a root user exists
|
||||||
const payload = {
|
const payload = {
|
||||||
isInit: Database.hasRootUser,
|
isInit: Database.hasRootUser,
|
||||||
language: Database.serverSettings.language
|
language: Database.serverSettings.language,
|
||||||
|
authMethods: Database.serverSettings.authActiveAuthMethods
|
||||||
}
|
}
|
||||||
if (!payload.isInit) {
|
if (!payload.isInit) {
|
||||||
payload.ConfigPath = global.ConfigPath
|
payload.ConfigPath = global.ConfigPath
|
||||||
|
@ -146,24 +146,31 @@ class SocketAuthority {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
* 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
|
||||||
|
*
|
||||||
|
* @param {SocketIO.Socket} socket
|
||||||
|
* @param {string} token JWT
|
||||||
|
*/
|
||||||
async authenticateSocket(socket, token) {
|
async authenticateSocket(socket, token) {
|
||||||
// we don't use passport to authenticate the jwt we get over the socket connection.
|
// we don't use passport to authenticate the jwt we get over the socket connection.
|
||||||
// it's easier to directly verify/decode it.
|
// 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.id) {
|
|
||||||
|
if (!token_data?.userId) {
|
||||||
// Token invalid
|
// 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')
|
||||||
}
|
}
|
||||||
// get the user via the id from the decoded jwt.
|
// get the user via the id from the decoded jwt.
|
||||||
const user = await Database.userModel.getUserById(token_data.id)
|
const user = await Database.userModel.getUserByIdOrOldId(token_data.userId)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// user not found
|
// 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = this.clients[socket.id]
|
const client = this.clients[socket.id]
|
||||||
if (!client) {
|
if (!client) {
|
||||||
Logger.error(`[SocketAuthority] Socket for user ${user.username} has no client`)
|
Logger.error(`[SocketAuthority] Socket for user ${user.username} has no client`)
|
||||||
|
Loading…
Reference in New Issue
Block a user