diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue
index 8f572b22..45437458 100644
--- a/client/components/modals/AccountModal.vue
+++ b/client/components/modals/AccountModal.vue
@@ -146,7 +146,6 @@ export default {
watch: {
show: {
handler(newVal) {
- console.log('accoutn modal show change', newVal)
if (newVal) {
this.init()
}
@@ -162,6 +161,9 @@ export default {
this.$emit('input', val)
}
},
+ user() {
+ return this.$store.state.user.user
+ },
title() {
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}`)
} else {
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.show = false
}
@@ -305,7 +313,6 @@ export default {
this.isNew = !this.account
if (this.account) {
- console.log(this.account)
this.newUser = {
username: this.account.username,
password: this.account.password,
diff --git a/client/pages/config/users/_id/index.vue b/client/pages/config/users/_id/index.vue
index ed234e35..78b9d0e0 100644
--- a/client/pages/config/users/_id/index.vue
+++ b/client/pages/config/users/_id/index.vue
@@ -13,11 +13,12 @@
-
- API Token:
{{ userToken }}content_copy
-
+
@@ -138,12 +139,15 @@ export default {
this.$copyToClipboard(str, this)
},
async init() {
- this.listeningSessions = await this.$axios.$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`).then((data) => {
- return data.sessions || []
- }).catch((err) => {
- console.error('Failed to load listening sesions', err)
- return []
- })
+ this.listeningSessions = await this.$axios
+ .$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`)
+ .then((data) => {
+ return data.sessions || []
+ })
+ .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) => {
console.error('Failed to load listening sesions', err)
return []
diff --git a/client/store/user.js b/client/store/user.js
index 9913c684..c5daa9ce 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -136,6 +136,10 @@ export const mutations = {
localStorage.removeItem('token')
}
},
+ setUserToken(state, token) {
+ state.user.token = token
+ localStorage.setItem('token', user.token)
+ },
updateMediaProgress(state, { id, data }) {
if (!state.user) return
if (!data) {
diff --git a/index.js b/index.js
index 4400312e..c15a9e0b 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,3 @@
-if (process.env.TOKEN_SECRET == null) process.env.TOKEN_SECRET = '09f26e402586e2faa8da4c98a35f1b20d6b033c6097befa8be3486a829587fe2f90a832bd3ff9d42710a4da095a2ce285b009f0c3730cd9b8e1af3eb84df6611'
const server = require('./server/Server')
global.appRoot = __dirname
diff --git a/server/Auth.js b/server/Auth.js
index 29fdbffc..68bc0933 100644
--- a/server/Auth.js
+++ b/server/Auth.js
@@ -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) {
var token = null
@@ -74,7 +94,7 @@ class Auth {
}
generateAccessToken(payload) {
- return jwt.sign(payload, process.env.TOKEN_SECRET);
+ return jwt.sign(payload, global.ServerSettings.tokenSecret);
}
authenticateUser(token) {
@@ -83,12 +103,12 @@ class Auth {
verifyToken(token) {
return new Promise((resolve) => {
- jwt.verify(token, process.env.TOKEN_SECRET, (err, payload) => {
+ jwt.verify(token, global.ServerSettings.tokenSecret, (err, payload) => {
if (!payload || err) {
Logger.error('JWT Verify Token Failed', err)
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)
})
})
@@ -98,7 +118,7 @@ class Auth {
return {
user: user.toJSONForBrowser(),
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
- serverSettings: this.db.serverSettings.toJSON(),
+ serverSettings: this.db.serverSettings.toJSONForBrowser(),
Source: global.Source
}
}
diff --git a/server/Server.js b/server/Server.js
index 0d75eaea..b80a6942 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -136,6 +136,11 @@ class Server {
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.purgeMetadata() // Remove metadata folders without library item
await this.cacheManager.ensureCachePaths()
@@ -314,7 +319,7 @@ class Server {
const newRoot = req.body.newRoot
let rootPash = newRoot.password ? await this.auth.hashPass(newRoot.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)
res.sendStatus(200)
@@ -459,8 +464,6 @@ class Server {
await this.db.updateEntity('user', user)
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,
configPath: global.ConfigPath,
user: client.user.toJSONForBrowser(),
diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js
index 399ce742..c760b7da 100644
--- a/server/controllers/MiscController.js
+++ b/server/controllers/MiscController.js
@@ -242,7 +242,7 @@ class MiscController {
const userResponse = {
user: req.user,
userDefaultLibraryId: req.user.getDefaultLibraryId(this.db.libraries),
- serverSettings: this.db.serverSettings.toJSON(),
+ serverSettings: this.db.serverSettings.toJSONForBrowser(),
Source: global.Source
}
res.json(userResponse)
diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js
index f8b49638..6e2f4b9b 100644
--- a/server/controllers/UserController.js
+++ b/server/controllers/UserController.js
@@ -43,7 +43,7 @@ class UserController {
account.id = getId('usr')
account.pash = await this.auth.hashPass(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()
var newUser = new User(account)
var success = await this.db.insertEntity('user', newUser)
@@ -74,12 +74,14 @@ class UserController {
}
var account = req.body
+ var shouldUpdateToken = false
if (account.username !== undefined && account.username !== user.username) {
var usernameExists = this.db.users.find(u => u.username.toLowerCase() === account.username.toLowerCase())
if (usernameExists) {
return res.status(500).send('Username already taken')
}
+ shouldUpdateToken = true
}
// Updating password
@@ -90,6 +92,10 @@ class UserController {
var hasUpdated = user.update(account)
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)
}
diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js
index 4860d494..ddb8a086 100644
--- a/server/objects/settings/ServerSettings.js
+++ b/server/objects/settings/ServerSettings.js
@@ -5,6 +5,7 @@ const Logger = require('../../Logger')
class ServerSettings {
constructor(settings) {
this.id = 'server-settings'
+ this.tokenSecret = null
// Scanner
this.scannerParseSubtitle = false
@@ -63,6 +64,7 @@ class ServerSettings {
}
construct(settings) {
+ this.tokenSecret = settings.tokenSecret
this.scannerFindCovers = !!settings.scannerFindCovers
this.scannerCoverProvider = settings.scannerCoverProvider || 'google'
this.scannerParseSubtitle = settings.scannerParseSubtitle
@@ -110,9 +112,10 @@ class ServerSettings {
}
}
- toJSON() {
+ toJSON() { // Use toJSONForBrowser if sending to client
return {
id: this.id,
+ tokenSecret: this.tokenSecret, // Do not return to client
scannerFindCovers: this.scannerFindCovers,
scannerCoverProvider: this.scannerCoverProvider,
scannerParseSubtitle: this.scannerParseSubtitle,
@@ -145,6 +148,12 @@ class ServerSettings {
}
}
+ toJSONForBrowser() {
+ const json = this.toJSON()
+ delete json.tokenSecret
+ return json
+ }
+
update(payload) {
var hasUpdates = false
for (const key in payload) {