mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-11-14 21:03:53 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6918b6828 | ||
|
|
95aa00af90 | ||
|
|
1d89e3a58c | ||
|
|
41cbcf551d | ||
|
|
1e60d7af64 | ||
|
|
92bae242f1 | ||
|
|
34b037087b | ||
|
|
844b04c6ae | ||
|
|
c38fe60360 | ||
|
|
8c87ae8bad | ||
|
|
f85d03ce26 |
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -13,11 +13,6 @@
|
|||||||
<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 v-if="legacyToken" class="text-xs space-y-2 mt-4">
|
|
||||||
<ui-text-input-with-label label="Legacy API Token" :value="legacyToken" readonly show-copy />
|
|
||||||
|
|
||||||
<p class="text-warning" v-html="$strings.MessageAuthenticationLegacyTokenWarning" />
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-px bg-white/10 my-2" />
|
<div class="w-full h-px bg-white/10 my-2" />
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningStats }}</h1>
|
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningStats }}</h1>
|
||||||
@@ -102,12 +97,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
legacyToken() {
|
|
||||||
return this.user.token
|
|
||||||
},
|
|
||||||
userToken() {
|
|
||||||
return this.user.accessToken
|
|
||||||
},
|
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-24
@@ -40,15 +40,6 @@
|
|||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<div v-if="showNewAuthSystemMessage" class="mb-4">
|
|
||||||
<widgets-alert type="warning">
|
|
||||||
<div>
|
|
||||||
<p>{{ $strings.MessageAuthenticationSecurityMessage }}</p>
|
|
||||||
<a v-if="showNewAuthSystemAdminMessage" href="https://github.com/advplyr/audiobookshelf/discussions/4460" target="_blank" class="underline">{{ $strings.LabelMoreInfo }}</a>
|
|
||||||
</div>
|
|
||||||
</widgets-alert>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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.trim="username" autocomplete="username" :disabled="processing" class="mb-3 w-full" inputName="username" />
|
<ui-text-input v-model.trim="username" autocomplete="username" :disabled="processing" class="mb-3 w-full" inputName="username" />
|
||||||
@@ -94,10 +85,7 @@ export default {
|
|||||||
MetadataPath: '',
|
MetadataPath: '',
|
||||||
login_local: true,
|
login_local: true,
|
||||||
login_openid: false,
|
login_openid: false,
|
||||||
authFormData: null,
|
authFormData: null
|
||||||
// New JWT auth system re-login flags
|
|
||||||
showNewAuthSystemMessage: false,
|
|
||||||
showNewAuthSystemAdminMessage: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -201,8 +189,6 @@ export default {
|
|||||||
},
|
},
|
||||||
async submitForm() {
|
async submitForm() {
|
||||||
this.error = null
|
this.error = null
|
||||||
this.showNewAuthSystemMessage = false
|
|
||||||
this.showNewAuthSystemAdminMessage = false
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -238,15 +224,6 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
// Force re-login if user is using an old token with no expiration
|
|
||||||
if (res.user.isOldToken) {
|
|
||||||
this.username = res.user.username
|
|
||||||
this.showNewAuthSystemMessage = true
|
|
||||||
// Admin user sees link to github discussion
|
|
||||||
this.showNewAuthSystemAdminMessage = res.user.type === 'admin' || res.user.type === 'root'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setUser(res)
|
this.setUser(res)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.37.2",
|
"version": "3.0.0-beta",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "dist-server/index.js",
|
"main": "dist-server/index.js",
|
||||||
|
|||||||
+2
-16
@@ -62,18 +62,6 @@ class Auth {
|
|||||||
return passport.authenticate('jwt', { session: false })(req, res, next)
|
return passport.authenticate('jwt', { session: false })(req, res, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to generate a jwt token for a given user
|
|
||||||
* TODO: Old method with no expiration
|
|
||||||
* @deprecated
|
|
||||||
*
|
|
||||||
* @param {{ id:string, username:string }} user
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
generateAccessToken(user) {
|
|
||||||
return this.tokenManager.generateAccessToken(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate all JWT sessions for a given user
|
* Invalidate all JWT sessions for a given user
|
||||||
* If user is current user and refresh token is valid, rotate tokens for the current session
|
* If user is current user and refresh token is valid, rotate tokens for the current session
|
||||||
@@ -269,10 +257,8 @@ class Auth {
|
|||||||
} else {
|
} else {
|
||||||
// UI request -> check if we have a callback url
|
// UI request -> check if we have a callback url
|
||||||
if (req.cookies.auth_cb) {
|
if (req.cookies.auth_cb) {
|
||||||
let stateQuery = req.cookies.auth_state ? `&state=${req.cookies.auth_state}` : ''
|
const stateQuery = req.cookies.auth_state ? `&state=${req.cookies.auth_state}` : ''
|
||||||
// UI request -> redirect to auth_cb url and send the jwt token as parameter
|
res.redirect(302, `${req.cookies.auth_cb}?accessToken=${userResponse.user.accessToken}${stateQuery}`)
|
||||||
// TODO: Temporarily continue sending the old token as setToken
|
|
||||||
res.redirect(302, `${req.cookies.auth_cb}?setToken=${userResponse.user.token}&accessToken=${userResponse.user.accessToken}${stateQuery}`)
|
|
||||||
} else {
|
} else {
|
||||||
Logger.error('[Auth] No callback or already expired')
|
Logger.error('[Auth] No callback or already expired')
|
||||||
res.redirect(`/login?error=${encodeURIComponent('No callback or already expired')}&autoLaunch=0`)
|
res.redirect(`/login?error=${encodeURIComponent('No callback or already expired')}&autoLaunch=0`)
|
||||||
|
|||||||
+2
-3
@@ -415,10 +415,9 @@ class Database {
|
|||||||
* Create root user
|
* Create root user
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} pash
|
* @param {string} pash
|
||||||
* @param {Auth} auth
|
|
||||||
* @returns {Promise<boolean>} true if created
|
* @returns {Promise<boolean>} true if created
|
||||||
*/
|
*/
|
||||||
async createRootUser(username, pash, auth) {
|
async createRootUser(username, pash) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
const transaction = await this.sequelize.transaction()
|
const transaction = await this.sequelize.transaction()
|
||||||
try {
|
try {
|
||||||
@@ -427,7 +426,7 @@ class Database {
|
|||||||
await transaction.rollback()
|
await transaction.rollback()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
await this.userModel.createRootUser(username, pash, auth, transaction)
|
await this.userModel.createRootUser(username, pash, transaction)
|
||||||
await transaction.commit()
|
await transaction.commit()
|
||||||
this.hasRootUser = true
|
this.hasRootUser = true
|
||||||
return true
|
return true
|
||||||
|
|||||||
+1
-1
@@ -479,7 +479,7 @@ class Server {
|
|||||||
const rootUsername = newRoot.username || 'root'
|
const rootUsername = newRoot.username || 'root'
|
||||||
const rootPash = newRoot.password ? await this.auth.localAuthStrategy.hashPassword(newRoot.password) : ''
|
const rootPash = newRoot.password ? await this.auth.localAuthStrategy.hashPassword(newRoot.password) : ''
|
||||||
if (!rootPash) Logger.warn(`[Server] Creating root user with no password`)
|
if (!rootPash) Logger.warn(`[Server] Creating root user with no password`)
|
||||||
const created = await Database.createRootUser(rootUsername, rootPash, this.auth)
|
const created = await Database.createRootUser(rootUsername, rootPash)
|
||||||
if (!created) {
|
if (!created) {
|
||||||
Logger.error(`[Server] attempt to init server when server already has a root user`)
|
Logger.error(`[Server] attempt to init server when server already has a root user`)
|
||||||
return res.sendStatus(500)
|
return res.sendStatus(500)
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ class TokenManager {
|
|||||||
*/
|
*/
|
||||||
static isBearerAccessTokenPayload(decoded) {
|
static isBearerAccessTokenPayload(decoded) {
|
||||||
if (!decoded?.userId) return false
|
if (!decoded?.userId) return false
|
||||||
if (decoded.type === 'refresh') return false
|
// Legacy API tokens were signed with only userId and username, and never expired.
|
||||||
|
if (decoded.type !== 'access') return false
|
||||||
|
if (!decoded.exp) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,30 +102,6 @@ class TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a JWT token for a given user
|
|
||||||
* TODO: Old method with no expiration
|
|
||||||
* @deprecated
|
|
||||||
*
|
|
||||||
* @param {{ id:string, username:string }} user
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static generateAccessToken(user) {
|
|
||||||
return jwt.sign({ userId: user.id, username: user.username }, TokenManager.TokenSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to generate a jwt token for a given user
|
|
||||||
* TODO: Old method with no expiration
|
|
||||||
* @deprecated
|
|
||||||
*
|
|
||||||
* @param {{ id:string, username:string }} user
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
generateAccessToken(user) {
|
|
||||||
return TokenManager.generateAccessToken(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate access token for a given user
|
* Generate access token for a given user
|
||||||
*
|
*
|
||||||
@@ -325,15 +303,6 @@ class TokenManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Temporary flag to report old tokens to users
|
|
||||||
// May be a better place for this but here means we dont have to decode the token again
|
|
||||||
if (!jwt_payload.exp && !user.isOldToken) {
|
|
||||||
Logger.debug(`[TokenManager] User ${user.username} is using an access token without an expiration`)
|
|
||||||
user.isOldToken = true
|
|
||||||
} else if (jwt_payload.exp && user.isOldToken !== undefined) {
|
|
||||||
delete user.isOldToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// approve login
|
// approve login
|
||||||
done(null, user)
|
done(null, user)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,12 @@ class UserController {
|
|||||||
*/
|
*/
|
||||||
async findAll(req, res) {
|
async findAll(req, res) {
|
||||||
if (!req.user.isAdminOrUp) return res.sendStatus(403)
|
if (!req.user.isAdminOrUp) return res.sendStatus(403)
|
||||||
const hideRootToken = !req.user.isRoot
|
|
||||||
|
|
||||||
const includes = (req.query.include || '').split(',').map((i) => i.trim())
|
const includes = (req.query.include || '').split(',').map((i) => i.trim())
|
||||||
|
|
||||||
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
|
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
|
||||||
const allUsers = await Database.userModel.findAll()
|
const allUsers = await Database.userModel.findAll()
|
||||||
const users = allUsers.map((u) => u.toOldJSONForBrowser(hideRootToken, true))
|
const users = allUsers.map((u) => u.toOldJSONForBrowser(true))
|
||||||
|
|
||||||
if (includes.includes('latestSession')) {
|
if (includes.includes('latestSession')) {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
@@ -97,7 +96,7 @@ class UserController {
|
|||||||
return oldMediaProgress
|
return oldMediaProgress
|
||||||
})
|
})
|
||||||
|
|
||||||
const userJson = req.reqUser.toOldJSONForBrowser(!req.user.isRoot)
|
const userJson = req.reqUser.toOldJSONForBrowser()
|
||||||
|
|
||||||
userJson.mediaProgress = oldMediaProgresses
|
userJson.mediaProgress = oldMediaProgresses
|
||||||
|
|
||||||
@@ -128,7 +127,6 @@ class UserController {
|
|||||||
|
|
||||||
const userId = uuidv4()
|
const userId = uuidv4()
|
||||||
const pash = await this.auth.localAuthStrategy.hashPassword(req.body.password)
|
const pash = await this.auth.localAuthStrategy.hashPassword(req.body.password)
|
||||||
const token = this.auth.generateAccessToken({ id: userId, username: req.body.username })
|
|
||||||
const userType = req.body.type || 'user'
|
const userType = req.body.type || 'user'
|
||||||
|
|
||||||
// librariesAccessible and itemTagsSelected can be on req.body or req.body.permissions
|
// librariesAccessible and itemTagsSelected can be on req.body or req.body.permissions
|
||||||
@@ -173,7 +171,6 @@ class UserController {
|
|||||||
username: req.body.username,
|
username: req.body.username,
|
||||||
email: typeof req.body.email === 'string' ? req.body.email : null,
|
email: typeof req.body.email === 'string' ? req.body.email : null,
|
||||||
pash,
|
pash,
|
||||||
token,
|
|
||||||
isActive: !!req.body.isActive,
|
isActive: !!req.body.isActive,
|
||||||
permissions,
|
permissions,
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
@@ -236,16 +233,13 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hasUpdates = false
|
let hasUpdates = false
|
||||||
let shouldUpdateToken = false
|
|
||||||
let shouldInvalidateJwtSessions = false
|
let shouldInvalidateJwtSessions = false
|
||||||
// When changing username create a new API token
|
|
||||||
if (updatePayload.username && updatePayload.username !== user.username) {
|
if (updatePayload.username && updatePayload.username !== user.username) {
|
||||||
const usernameExists = await Database.userModel.checkUserExistsWithUsername(updatePayload.username)
|
const usernameExists = await Database.userModel.checkUserExistsWithUsername(updatePayload.username)
|
||||||
if (usernameExists) {
|
if (usernameExists) {
|
||||||
return res.status(400).send('Username already taken')
|
return res.status(400).send('Username already taken')
|
||||||
}
|
}
|
||||||
user.username = updatePayload.username
|
user.username = updatePayload.username
|
||||||
shouldUpdateToken = true
|
|
||||||
shouldInvalidateJwtSessions = true
|
shouldInvalidateJwtSessions = true
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
@@ -327,11 +321,6 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasUpdates) {
|
if (hasUpdates) {
|
||||||
if (shouldUpdateToken) {
|
|
||||||
user.token = this.auth.generateAccessToken(user)
|
|
||||||
Logger.info(`[UserController] User ${user.username} has generated a new api token`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle JWT session invalidation for username/password changes
|
// Handle JWT session invalidation for username/password changes
|
||||||
if (shouldInvalidateJwtSessions) {
|
if (shouldInvalidateJwtSessions) {
|
||||||
const newTokens = await this.auth.invalidateJwtSessionsForUser(user, req, res)
|
const newTokens = await this.auth.invalidateJwtSessionsForUser(user, req, res)
|
||||||
|
|||||||
+3
-18
@@ -5,7 +5,6 @@ const { LRUCache } = require('lru-cache')
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const { isNullOrNaN } = require('../utils')
|
const { isNullOrNaN } = require('../utils')
|
||||||
const TokenManager = require('../auth/TokenManager')
|
|
||||||
|
|
||||||
class UserCache {
|
class UserCache {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -94,8 +93,6 @@ class User extends Model {
|
|||||||
this.pash
|
this.pash
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
this.type
|
this.type
|
||||||
/** @type {string} */
|
|
||||||
this.token
|
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
this.isActive
|
this.isActive
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
@@ -186,20 +183,16 @@ class User extends Model {
|
|||||||
* Create root user
|
* Create root user
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} pash
|
* @param {string} pash
|
||||||
* @param {import('../Auth')} auth
|
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async createRootUser(username, pash, auth, transaction = null) {
|
static async createRootUser(username, pash, transaction = null) {
|
||||||
const userId = uuidv4()
|
const userId = uuidv4()
|
||||||
|
|
||||||
const token = auth.generateAccessToken({ id: userId, username })
|
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
id: userId,
|
id: userId,
|
||||||
type: 'root',
|
type: 'root',
|
||||||
username,
|
username,
|
||||||
pash,
|
pash,
|
||||||
token,
|
|
||||||
isActive: true,
|
isActive: true,
|
||||||
permissions: this.getDefaultPermissionsForUserType('root'),
|
permissions: this.getDefaultPermissionsForUserType('root'),
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
@@ -314,15 +307,12 @@ class User extends Model {
|
|||||||
const username = userinfo.preferred_username || userinfo.name || userinfo.sub
|
const username = userinfo.preferred_username || userinfo.name || userinfo.sub
|
||||||
const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
|
const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
|
||||||
|
|
||||||
const token = TokenManager.generateAccessToken({ id: userId, username })
|
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
id: userId,
|
id: userId,
|
||||||
type: 'user',
|
type: 'user',
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
pash: null,
|
pash: null,
|
||||||
token,
|
|
||||||
isActive: true,
|
isActive: true,
|
||||||
permissions: this.getDefaultPermissionsForUserType('user'),
|
permissions: this.getDefaultPermissionsForUserType('user'),
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
@@ -514,6 +504,7 @@ class User extends Model {
|
|||||||
email: DataTypes.STRING,
|
email: DataTypes.STRING,
|
||||||
pash: DataTypes.STRING,
|
pash: DataTypes.STRING,
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
|
// Column remains so existing databases do not need a migration. New users leave it null.
|
||||||
token: DataTypes.STRING,
|
token: DataTypes.STRING,
|
||||||
isActive: {
|
isActive: {
|
||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
@@ -596,11 +587,10 @@ class User extends Model {
|
|||||||
/**
|
/**
|
||||||
* User data for browser using old model
|
* User data for browser using old model
|
||||||
*
|
*
|
||||||
* @param {boolean} [hideRootToken=false]
|
|
||||||
* @param {boolean} [minimal=false]
|
* @param {boolean} [minimal=false]
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
toOldJSONForBrowser(hideRootToken = false, minimal = false) {
|
toOldJSONForBrowser(minimal = false) {
|
||||||
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
|
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
|
||||||
const librariesAccessible = this.permissions?.librariesAccessible || []
|
const librariesAccessible = this.permissions?.librariesAccessible || []
|
||||||
const itemTagsSelected = this.permissions?.itemTagsSelected || []
|
const itemTagsSelected = this.permissions?.itemTagsSelected || []
|
||||||
@@ -613,11 +603,6 @@ class User extends Model {
|
|||||||
username: this.username,
|
username: this.username,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
// TODO: Old non-expiring token
|
|
||||||
token: this.type === 'root' && hideRootToken ? '' : this.token,
|
|
||||||
// TODO: Temporary flag not saved in db that is set in Auth.js jwtAuthCheck
|
|
||||||
// Necessary to detect apps using old tokens that no longer match the old token stored on the user
|
|
||||||
isOldToken: this.isOldToken,
|
|
||||||
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
|
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
|
||||||
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
|
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
|
||||||
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
|
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
|
||||||
|
|||||||
@@ -75,9 +75,6 @@ class Stream extends EventEmitter {
|
|||||||
get codecsToForceAAC() {
|
get codecsToForceAAC() {
|
||||||
return ['alac', 'ac3', 'eac3', 'opus']
|
return ['alac', 'ac3', 'eac3', 'opus']
|
||||||
}
|
}
|
||||||
get userToken() {
|
|
||||||
return this.user.token
|
|
||||||
}
|
|
||||||
// Fmp4 does not work on iOS devices: https://github.com/advplyr/audiobookshelf-app/issues/85
|
// Fmp4 does not work on iOS devices: https://github.com/advplyr/audiobookshelf-app/issues/85
|
||||||
// Workaround: Force AAC transcode for FLAC
|
// Workaround: Force AAC transcode for FLAC
|
||||||
get hlsSegmentType() {
|
get hlsSegmentType() {
|
||||||
|
|||||||
@@ -39,6 +39,16 @@ describe('TokenManager', () => {
|
|||||||
describe('jwtAuthCheck', () => {
|
describe('jwtAuthCheck', () => {
|
||||||
const user = { id: userId, username: 'testuser', isActive: true }
|
const user = { id: userId, username: 'testuser', isActive: true }
|
||||||
|
|
||||||
|
it('rejects legacy API tokens that have no type or expiration', async () => {
|
||||||
|
const legacyToken = jwt.sign({ userId, username: 'testuser' }, secret)
|
||||||
|
const decoded = jwt.verify(legacyToken, secret)
|
||||||
|
const done = sinon.spy()
|
||||||
|
|
||||||
|
await tokenManager.jwtAuthCheck(decoded, done)
|
||||||
|
|
||||||
|
expect(done.calledWith(null, null)).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
it('rejects refresh tokens for API auth', async () => {
|
it('rejects refresh tokens for API auth', async () => {
|
||||||
const refreshToken = tokenManager.generateRefreshToken(user)
|
const refreshToken = tokenManager.generateRefreshToken(user)
|
||||||
const decoded = jwt.verify(refreshToken, secret)
|
const decoded = jwt.verify(refreshToken, secret)
|
||||||
|
|||||||
Reference in new issue
Block a user