Update:JWT signing

This commit is contained in:
advplyr 2022-07-18 17:19:16 -05:00
parent 86ee4dcff2
commit 9e7b84f289
9 changed files with 76 additions and 24 deletions

View file

@ -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
}
}

View file

@ -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(),

View file

@ -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)

View file

@ -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)
}

View file

@ -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) {