mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-12 00:38:22 +02:00
Update:JWT signing
This commit is contained in:
parent
86ee4dcff2
commit
9e7b84f289
@ -146,7 +146,6 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
show: {
|
show: {
|
||||||
handler(newVal) {
|
handler(newVal) {
|
||||||
console.log('accoutn modal show change', newVal)
|
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
@ -162,6 +161,9 @@ export default {
|
|||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
title() {
|
title() {
|
||||||
return this.isNew ? 'Add New Account' : `Update ${(this.account || {}).username}`
|
return this.isNew ? 'Add New Account' : `Update ${(this.account || {}).username}`
|
||||||
},
|
},
|
||||||
@ -250,6 +252,12 @@ export default {
|
|||||||
this.$toast.error(`Failed to update account: ${data.error}`)
|
this.$toast.error(`Failed to update account: ${data.error}`)
|
||||||
} else {
|
} else {
|
||||||
console.log('Account updated', data.user)
|
console.log('Account updated', data.user)
|
||||||
|
|
||||||
|
if (data.user.id === this.user.id && data.user.token !== this.user.token) {
|
||||||
|
console.log('Current user token was updated')
|
||||||
|
this.$store.commit('user/setUserToken', data.user.token)
|
||||||
|
}
|
||||||
|
|
||||||
this.$toast.success('Account updated')
|
this.$toast.success('Account updated')
|
||||||
this.show = false
|
this.show = false
|
||||||
}
|
}
|
||||||
@ -305,7 +313,6 @@ export default {
|
|||||||
|
|
||||||
this.isNew = !this.account
|
this.isNew = !this.account
|
||||||
if (this.account) {
|
if (this.account) {
|
||||||
console.log(this.account)
|
|
||||||
this.newUser = {
|
this.newUser = {
|
||||||
username: this.account.username,
|
username: this.account.username,
|
||||||
password: this.account.password,
|
password: this.account.password,
|
||||||
|
@ -13,11 +13,12 @@
|
|||||||
<widgets-online-indicator :value="!!userOnline" />
|
<widgets-online-indicator :value="!!userOnline" />
|
||||||
<h1 class="text-xl pl-2">{{ username }}</h1>
|
<h1 class="text-xl pl-2">{{ username }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="cursor-pointer text-gray-400 hover:text-white" @click="copyToClipboard(userToken)">
|
<div v-if="userToken" class="flex text-xs mt-4">
|
||||||
<p v-if="userToken" class="py-2 text-xs">
|
<ui-text-input-with-label label="API Token" :value="userToken" readonly />
|
||||||
<strong class="text-white">API Token: </strong><br /><span class="text-white">{{ userToken }}</span
|
|
||||||
><span class="material-icons pl-2 text-base">content_copy</span>
|
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
|
||||||
</p>
|
<span class="material-icons pl-2 text-base">content_copy</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-2" />
|
<div class="w-full h-px bg-white bg-opacity-10 my-2" />
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
@ -138,12 +139,15 @@ export default {
|
|||||||
this.$copyToClipboard(str, this)
|
this.$copyToClipboard(str, this)
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.listeningSessions = await this.$axios.$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`).then((data) => {
|
this.listeningSessions = await this.$axios
|
||||||
return data.sessions || []
|
.$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`)
|
||||||
}).catch((err) => {
|
.then((data) => {
|
||||||
console.error('Failed to load listening sesions', err)
|
return data.sessions || []
|
||||||
return []
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
|
console.error('Failed to load listening sesions', err)
|
||||||
|
return []
|
||||||
|
})
|
||||||
this.listeningStats = await this.$axios.$get(`/api/users/${this.user.id}/listening-stats`).catch((err) => {
|
this.listeningStats = await this.$axios.$get(`/api/users/${this.user.id}/listening-stats`).catch((err) => {
|
||||||
console.error('Failed to load listening sesions', err)
|
console.error('Failed to load listening sesions', err)
|
||||||
return []
|
return []
|
||||||
|
@ -136,6 +136,10 @@ export const mutations = {
|
|||||||
localStorage.removeItem('token')
|
localStorage.removeItem('token')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setUserToken(state, token) {
|
||||||
|
state.user.token = token
|
||||||
|
localStorage.setItem('token', user.token)
|
||||||
|
},
|
||||||
updateMediaProgress(state, { id, data }) {
|
updateMediaProgress(state, { id, data }) {
|
||||||
if (!state.user) return
|
if (!state.user) return
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
1
index.js
1
index.js
@ -1,4 +1,3 @@
|
|||||||
if (process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
|
|
||||||
const server = require('./server/Server')
|
const server = require('./server/Server')
|
||||||
global.appRoot = __dirname
|
global.appRoot = __dirname
|
||||||
|
|
||||||
|
@ -31,6 +31,26 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initTokenSecret() {
|
||||||
|
if (process.env.TOKEN_SECRET) { // User can supply their own token secret
|
||||||
|
Logger.debug(`[Auth] Setting token secret - using user passed in TOKEN_SECRET env var`)
|
||||||
|
this.db.serverSettings.tokenSecret = process.env.TOKEN_SECRET
|
||||||
|
} else {
|
||||||
|
Logger.debug(`[Auth] Setting token secret - using random bytes`)
|
||||||
|
this.db.serverSettings.tokenSecret = require('crypto').randomBytes(256).toString('base64')
|
||||||
|
}
|
||||||
|
await this.db.updateServerSettings()
|
||||||
|
|
||||||
|
// New token secret creation added in v2.1.0 so generate new API tokens for each user
|
||||||
|
if (this.db.users.length) {
|
||||||
|
for (const user of this.db.users) {
|
||||||
|
user.token = await this.generateAccessToken({ userId: user.id, username: user.username })
|
||||||
|
Logger.warn(`[Auth] User ${user.username} api token has been updated using new token secret`)
|
||||||
|
}
|
||||||
|
await this.db.updateEntities('user', this.db.users)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async authMiddleware(req, res, next) {
|
async authMiddleware(req, res, next) {
|
||||||
var token = null
|
var token = null
|
||||||
|
|
||||||
@ -74,7 +94,7 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateAccessToken(payload) {
|
generateAccessToken(payload) {
|
||||||
return jwt.sign(payload, process.env.TOKEN_SECRET);
|
return jwt.sign(payload, global.ServerSettings.tokenSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateUser(token) {
|
authenticateUser(token) {
|
||||||
@ -83,12 +103,12 @@ class Auth {
|
|||||||
|
|
||||||
verifyToken(token) {
|
verifyToken(token) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
jwt.verify(token, process.env.TOKEN_SECRET, (err, payload) => {
|
jwt.verify(token, global.ServerSettings.tokenSecret, (err, payload) => {
|
||||||
if (!payload || err) {
|
if (!payload || err) {
|
||||||
Logger.error('JWT Verify Token Failed', err)
|
Logger.error('JWT Verify Token Failed', err)
|
||||||
return resolve(null)
|
return resolve(null)
|
||||||
}
|
}
|
||||||
var user = this.users.find(u => u.id === payload.userId)
|
var user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
||||||
resolve(user || null)
|
resolve(user || null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -98,7 +118,7 @@ class Auth {
|
|||||||
return {
|
return {
|
||||||
user: user.toJSONForBrowser(),
|
user: user.toJSONForBrowser(),
|
||||||
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
||||||
serverSettings: this.db.serverSettings.toJSON(),
|
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
||||||
Source: global.Source
|
Source: global.Source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,11 @@ class Server {
|
|||||||
await this.db.init()
|
await this.db.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create token secret if does not exist (Added v2.1.0)
|
||||||
|
if (!this.db.serverSettings.tokenSecret) {
|
||||||
|
await this.auth.initTokenSecret()
|
||||||
|
}
|
||||||
|
|
||||||
await this.checkUserMediaProgress() // Remove invalid user item progress
|
await this.checkUserMediaProgress() // Remove invalid user item progress
|
||||||
await this.purgeMetadata() // Remove metadata folders without library item
|
await this.purgeMetadata() // Remove metadata folders without library item
|
||||||
await this.cacheManager.ensureCachePaths()
|
await this.cacheManager.ensureCachePaths()
|
||||||
@ -314,7 +319,7 @@ class Server {
|
|||||||
const newRoot = req.body.newRoot
|
const newRoot = req.body.newRoot
|
||||||
let rootPash = newRoot.password ? await this.auth.hashPass(newRoot.password) : ''
|
let rootPash = newRoot.password ? await this.auth.hashPass(newRoot.password) : ''
|
||||||
if (!rootPash) Logger.warn(`[Server] Creating root user with no password`)
|
if (!rootPash) Logger.warn(`[Server] Creating root user with no password`)
|
||||||
let rootToken = await this.auth.generateAccessToken({ userId: 'root' })
|
let rootToken = await this.auth.generateAccessToken({ userId: 'root', username: newRoot.username })
|
||||||
await this.db.createRootUser(newRoot.username, rootPash, rootToken)
|
await this.db.createRootUser(newRoot.username, rootPash, rootToken)
|
||||||
|
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
@ -459,8 +464,6 @@ class Server {
|
|||||||
await this.db.updateEntity('user', user)
|
await this.db.updateEntity('user', user)
|
||||||
|
|
||||||
const initialPayload = {
|
const initialPayload = {
|
||||||
// TODO: this is sent with user auth now, update mobile app to use that then remove this
|
|
||||||
serverSettings: this.db.serverSettings.toJSON(),
|
|
||||||
metadataPath: global.MetadataPath,
|
metadataPath: global.MetadataPath,
|
||||||
configPath: global.ConfigPath,
|
configPath: global.ConfigPath,
|
||||||
user: client.user.toJSONForBrowser(),
|
user: client.user.toJSONForBrowser(),
|
||||||
|
@ -242,7 +242,7 @@ class MiscController {
|
|||||||
const userResponse = {
|
const userResponse = {
|
||||||
user: req.user,
|
user: req.user,
|
||||||
userDefaultLibraryId: req.user.getDefaultLibraryId(this.db.libraries),
|
userDefaultLibraryId: req.user.getDefaultLibraryId(this.db.libraries),
|
||||||
serverSettings: this.db.serverSettings.toJSON(),
|
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
||||||
Source: global.Source
|
Source: global.Source
|
||||||
}
|
}
|
||||||
res.json(userResponse)
|
res.json(userResponse)
|
||||||
|
@ -43,7 +43,7 @@ class UserController {
|
|||||||
account.id = getId('usr')
|
account.id = getId('usr')
|
||||||
account.pash = await this.auth.hashPass(account.password)
|
account.pash = await this.auth.hashPass(account.password)
|
||||||
delete account.password
|
delete account.password
|
||||||
account.token = await this.auth.generateAccessToken({ userId: account.id })
|
account.token = await this.auth.generateAccessToken({ userId: account.id, username })
|
||||||
account.createdAt = Date.now()
|
account.createdAt = Date.now()
|
||||||
var newUser = new User(account)
|
var newUser = new User(account)
|
||||||
var success = await this.db.insertEntity('user', newUser)
|
var success = await this.db.insertEntity('user', newUser)
|
||||||
@ -74,12 +74,14 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var account = req.body
|
var account = req.body
|
||||||
|
var shouldUpdateToken = false
|
||||||
|
|
||||||
if (account.username !== undefined && account.username !== user.username) {
|
if (account.username !== undefined && account.username !== user.username) {
|
||||||
var usernameExists = this.db.users.find(u => u.username.toLowerCase() === account.username.toLowerCase())
|
var usernameExists = this.db.users.find(u => u.username.toLowerCase() === account.username.toLowerCase())
|
||||||
if (usernameExists) {
|
if (usernameExists) {
|
||||||
return res.status(500).send('Username already taken')
|
return res.status(500).send('Username already taken')
|
||||||
}
|
}
|
||||||
|
shouldUpdateToken = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updating password
|
// Updating password
|
||||||
@ -90,6 +92,10 @@ class UserController {
|
|||||||
|
|
||||||
var hasUpdated = user.update(account)
|
var hasUpdated = user.update(account)
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
|
if (shouldUpdateToken) {
|
||||||
|
user.token = await this.auth.generateAccessToken({ userId: user.id, username: user.username })
|
||||||
|
Logger.info(`[UserController] User ${user.username} was generated a new api token`)
|
||||||
|
}
|
||||||
await this.db.updateEntity('user', user)
|
await this.db.updateEntity('user', user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ const Logger = require('../../Logger')
|
|||||||
class ServerSettings {
|
class ServerSettings {
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
this.id = 'server-settings'
|
this.id = 'server-settings'
|
||||||
|
this.tokenSecret = null
|
||||||
|
|
||||||
// Scanner
|
// Scanner
|
||||||
this.scannerParseSubtitle = false
|
this.scannerParseSubtitle = false
|
||||||
@ -63,6 +64,7 @@ class ServerSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
construct(settings) {
|
construct(settings) {
|
||||||
|
this.tokenSecret = settings.tokenSecret
|
||||||
this.scannerFindCovers = !!settings.scannerFindCovers
|
this.scannerFindCovers = !!settings.scannerFindCovers
|
||||||
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
|
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
|
||||||
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
||||||
@ -110,9 +112,10 @@ class ServerSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() { // Use toJSONForBrowser if sending to client
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
tokenSecret: this.tokenSecret, // Do not return to client
|
||||||
scannerFindCovers: this.scannerFindCovers,
|
scannerFindCovers: this.scannerFindCovers,
|
||||||
scannerCoverProvider: this.scannerCoverProvider,
|
scannerCoverProvider: this.scannerCoverProvider,
|
||||||
scannerParseSubtitle: this.scannerParseSubtitle,
|
scannerParseSubtitle: this.scannerParseSubtitle,
|
||||||
@ -145,6 +148,12 @@ class ServerSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSONForBrowser() {
|
||||||
|
const json = this.toJSON()
|
||||||
|
delete json.tokenSecret
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
update(payload) {
|
update(payload) {
|
||||||
var hasUpdates = false
|
var hasUpdates = false
|
||||||
for (const key in payload) {
|
for (const key in payload) {
|
||||||
|
Loading…
Reference in New Issue
Block a user