mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-26 19:21:39 +00:00
Merge pull request #5395 from advplyr/logout_all_devices
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Run Component Tests / Run Component Tests (push) Waiting to run
Build and Push Docker Image / build (push) Waiting to run
Verify all i18n files are alphabetized / update_translations (push) Waiting to run
Integration Test / build and test (push) Waiting to run
Run Unit Tests / Run Unit Tests (push) Waiting to run
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Run Component Tests / Run Component Tests (push) Waiting to run
Build and Push Docker Image / build (push) Waiting to run
Verify all i18n files are alphabetized / update_translations (push) Waiting to run
Integration Test / build and test (push) Waiting to run
Run Unit Tests / Run Unit Tests (push) Waiting to run
Add logout all devices button and extend auth logout endpoint
This commit is contained in:
commit
4c71a076e7
4 changed files with 39 additions and 7 deletions
|
|
@ -69,8 +69,9 @@
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-4 mt-8 flex">
|
<div class="py-4 mt-8 flex flex-wrap gap-2">
|
||||||
<ui-btn color="bg-primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
<ui-btn v-if="!isGuest" color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(true)"> <span class="material-symbols mr-4 icon-text">devices</span>{{ $strings.ButtonLogoutAllDevices }} </ui-btn>
|
||||||
|
<ui-btn color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
||||||
|
|
@ -87,6 +88,7 @@ export default {
|
||||||
newPassword: null,
|
newPassword: null,
|
||||||
confirmPassword: null,
|
confirmPassword: null,
|
||||||
changingPassword: false,
|
changingPassword: false,
|
||||||
|
loggingOut: false,
|
||||||
selectedLanguage: '',
|
selectedLanguage: '',
|
||||||
newEReaderDevice: {
|
newEReaderDevice: {
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -135,7 +137,7 @@ export default {
|
||||||
updateLocalLanguage(lang) {
|
updateLocalLanguage(lang) {
|
||||||
this.$setLanguageCode(lang)
|
this.$setLanguageCode(lang)
|
||||||
},
|
},
|
||||||
logout() {
|
logout(allDevices = false) {
|
||||||
// Disconnect from socket
|
// Disconnect from socket
|
||||||
if (this.$root.socket) {
|
if (this.$root.socket) {
|
||||||
console.log('Disconnecting from socket', this.$root.socket.id)
|
console.log('Disconnecting from socket', this.$root.socket.id)
|
||||||
|
|
@ -149,8 +151,10 @@ export default {
|
||||||
this.$store.commit('libraries/setUserPlaylists', [])
|
this.$store.commit('libraries/setUserPlaylists', [])
|
||||||
this.$store.commit('libraries/setCollections', [])
|
this.$store.commit('libraries/setCollections', [])
|
||||||
|
|
||||||
|
this.loggingOut = true
|
||||||
|
const url = allDevices ? '/logout?allDevices=1' : '/logout'
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post('/logout')
|
.$post(url)
|
||||||
.then((logoutPayload) => {
|
.then((logoutPayload) => {
|
||||||
const redirect_url = logoutPayload.redirect_url
|
const redirect_url = logoutPayload.redirect_url
|
||||||
|
|
||||||
|
|
@ -163,6 +167,9 @@ export default {
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loggingOut = false
|
||||||
|
})
|
||||||
},
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
this.password = null
|
this.password = null
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
"ButtonLatest": "Latest",
|
"ButtonLatest": "Latest",
|
||||||
"ButtonLibrary": "Library",
|
"ButtonLibrary": "Library",
|
||||||
"ButtonLogout": "Logout",
|
"ButtonLogout": "Logout",
|
||||||
|
"ButtonLogoutAllDevices": "Logout All Devices",
|
||||||
"ButtonLookup": "Lookup",
|
"ButtonLookup": "Lookup",
|
||||||
"ButtonManageTracks": "Manage Tracks",
|
"ButtonManageTracks": "Manage Tracks",
|
||||||
"ButtonMapChapterTitles": "Map Chapter Titles",
|
"ButtonMapChapterTitles": "Map Chapter Titles",
|
||||||
|
|
|
||||||
|
|
@ -471,18 +471,23 @@ class Auth {
|
||||||
res.json(openIdIssuerConfig)
|
res.json(openIdIssuerConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Logout route
|
/**
|
||||||
|
* Logout route
|
||||||
|
* Use ?allDevices=1 to destroy every session for this user instead of just the current one
|
||||||
|
*/
|
||||||
router.post('/logout', async (req, res) => {
|
router.post('/logout', async (req, res) => {
|
||||||
// Refresh token be alternatively be sent in the header
|
// Refresh token be alternatively be sent in the header
|
||||||
const refreshToken = req.cookies.refresh_token || req.headers['x-refresh-token']
|
const refreshToken = req.cookies.refresh_token || req.headers['x-refresh-token']
|
||||||
|
const allDevices = req.query.allDevices === '1'
|
||||||
|
|
||||||
// Clear refresh token cookie
|
// Clear refresh token cookie
|
||||||
res.clearCookie('refresh_token', {
|
res.clearCookie('refresh_token', {
|
||||||
path: '/'
|
path: '/'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Invalidate the session in database using refresh token
|
if (allDevices) {
|
||||||
if (refreshToken) {
|
await this.tokenManager.invalidateAllSessionsForRefreshToken(refreshToken)
|
||||||
|
} else if (refreshToken) {
|
||||||
await this.tokenManager.invalidateRefreshToken(refreshToken)
|
await this.tokenManager.invalidateRefreshToken(refreshToken)
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`[Auth] logout: No refresh token on request`)
|
Logger.info(`[Auth] logout: No refresh token on request`)
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,25 @@ class TokenManager {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy all JWT sessions for the user that owns this refresh token
|
||||||
|
*
|
||||||
|
* @param {string} refreshToken
|
||||||
|
*/
|
||||||
|
async invalidateAllSessionsForRefreshToken(refreshToken) {
|
||||||
|
if (!refreshToken) return
|
||||||
|
|
||||||
|
const session = await Database.sessionModel.findOne({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [{ refreshToken: refreshToken }, { lastRefreshToken: refreshToken }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
const numDeleted = await Database.sessionModel.destroy({ where: { userId: session.userId } })
|
||||||
|
Logger.info(`[TokenManager] Invalidated all JWT sessions for user ${session.userId}, ${numDeleted} deleted`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate a refresh token - used for logout
|
* Invalidate a refresh token - used for logout
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue