Skip to content

Commit

Permalink
Update:Auth to use new user model
Browse files Browse the repository at this point in the history
- Express requests include userNew to start migrating API controllers to new user model
  • Loading branch information
advplyr committed Aug 10, 2024
1 parent 59370ca commit 202ceb0
Show file tree
Hide file tree
Showing 14 changed files with 626 additions and 392 deletions.
4 changes: 0 additions & 4 deletions client/components/tables/UsersTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ export default {
this.init()
},
beforeDestroy() {
if (this.$refs.accountModal) {
this.$refs.accountModal.close()
}
if (this.$root.socket) {
this.$root.socket.off('user_added', this.addUpdateUser)
this.$root.socket.off('user_updated', this.addUpdateUser)
Expand Down
9 changes: 7 additions & 2 deletions client/pages/config/users/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export default {
this.showAccountModal = true
}
},
mounted() {}
mounted() {},
beforeDestroy() {
if (this.$refs.accountModal) {
this.$refs.accountModal.close()
}
}
}
</script>
</script>
20 changes: 11 additions & 9 deletions client/store/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const state = () => ({
authorSortBy: 'name',
authorSortDesc: false,
jumpForwardAmount: 10,
jumpBackwardAmount: 10,
jumpBackwardAmount: 10
}
})

Expand All @@ -26,13 +26,15 @@ export const getters = {
getToken: (state) => {
return state.user?.token || null
},
getUserMediaProgress: (state) => (libraryItemId, episodeId = null) => {
if (!state.user.mediaProgress) return null
return state.user.mediaProgress.find((li) => {
if (episodeId && li.episodeId !== episodeId) return false
return li.libraryItemId == libraryItemId
})
},
getUserMediaProgress:
(state) =>
(libraryItemId, episodeId = null) => {
if (!state.user.mediaProgress) return null
return state.user.mediaProgress.find((li) => {
if (episodeId && li.episodeId !== episodeId) return false
return li.libraryItemId == libraryItemId
})
},
getUserBookmarksForItem: (state) => (libraryItemId) => {
if (!state.user.bookmarks) return []
return state.user.bookmarks.filter((bm) => bm.libraryItemId === libraryItemId)
Expand Down Expand Up @@ -153,7 +155,7 @@ export const mutations = {
},
setUserToken(state, token) {
state.user.token = token
localStorage.setItem('token', user.token)
localStorage.setItem('token', token)
},
updateMediaProgress(state, { id, data }) {
if (!state.user) return
Expand Down
48 changes: 31 additions & 17 deletions server/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,11 @@ class Auth {
return null
}

user.authOpenIDSub = userinfo.sub
await Database.userModel.updateFromOld(user)
// Update user with OpenID sub
if (!user.extraData) user.extraData = {}
user.extraData.authOpenIDSub = userinfo.sub
user.changed('extraData', true)
await user.save()

Logger.debug(`[Auth] openid: User found by email/username`)
return user
Expand Down Expand Up @@ -788,12 +791,14 @@ class Auth {
await Database.updateServerSettings()

// New token secret creation added in v2.1.0 so generate new API tokens for each user
const users = await Database.userModel.getOldUsers()
const users = await Database.userModel.findAll({
attributes: ['id', 'username', 'token']
})
if (users.length) {
for (const user of users) {
user.token = await this.generateAccessToken(user)
await user.save({ hooks: false })
}
await Database.updateBulkUsers(users)
}
}

Expand Down Expand Up @@ -879,13 +884,13 @@ class Auth {
/**
* Return the login info payload for a user
*
* @param {Object} user
* @param {import('./models/User')} user
* @returns {Promise<Object>} jsonPayload
*/
async getUserLoginResponsePayload(user) {
const libraryIds = await Database.libraryModel.getAllLibraryIds()
return {
user: user.toJSONForBrowser(),
user: user.toOldJSONForBrowser(),
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
serverSettings: Database.serverSettings.toJSONForBrowser(),
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
Expand All @@ -907,6 +912,7 @@ class Auth {

/**
* User changes their password from request
* TODO: Update responses to use error status codes
*
* @param {import('express').Request} req
* @param {import('express').Response} res
Expand Down Expand Up @@ -941,19 +947,27 @@ class Auth {
}
}

matchingUser.pash = pw

const success = await Database.updateUser(matchingUser)
if (success) {
Logger.info(`[Auth] User "${matchingUser.username}" changed password`)
res.json({
success: true
Database.userModel
.update(
{
pash: pw
},
{
where: { id: matchingUser.id }
}
)
.then(() => {
Logger.info(`[Auth] User "${matchingUser.username}" changed password`)
res.json({
success: true
})
})
} else {
res.json({
error: 'Unknown error'
.catch((error) => {
Logger.error(`[Auth] User "${matchingUser.username}" failed to change password`, error)
res.json({
error: 'Unknown error'
})
})
}
}
}

Expand Down
7 changes: 1 addition & 6 deletions server/Database.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ class Database {
*/
async createRootUser(username, pash, auth) {
if (!this.sequelize) return false
await this.models.user.createRootUser(username, pash, auth)
await this.userModel.createRootUser(username, pash, auth)
this.hasRootUser = true
return true
}
Expand All @@ -390,11 +390,6 @@ class Database {
return this.models.user.updateFromOld(oldUser)
}

updateBulkUsers(oldUsers) {
if (!this.sequelize) return false
return Promise.all(oldUsers.map((u) => this.updateUser(u)))
}

removeUser(userId) {
if (!this.sequelize) return false
return this.models.user.removeById(userId)
Expand Down
18 changes: 17 additions & 1 deletion server/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,25 @@ class Server {
this.io = null
}

/**
* Middleware to check if the current request is authenticated
* req.user is set if authenticated to the OLD user object
* req.userNew is set if authenticated to the NEW user object
*
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
authMiddleware(req, res, next) {
// ask passportjs if the current request is authenticated
this.auth.isAuthenticated(req, res, next)
this.auth.isAuthenticated(req, res, () => {
if (req.user) {
// TODO: req.userNew to become req.user
req.userNew = req.user
req.user = Database.userModel.getOldUser(req.user)
}
next()
})
}

cancelLibraryScan(libraryId) {
Expand Down
65 changes: 35 additions & 30 deletions server/SocketAuthority.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ const Logger = require('./Logger')
const Database = require('./Database')
const Auth = require('./Auth')

/**
* @typedef SocketClient
* @property {string} id socket id
* @property {SocketIO.Socket} socket
* @property {number} connected_at
* @property {import('./models/User')} user
*/

class SocketAuthority {
constructor() {
this.Server = null
this.io = null

/** @type {Object.<string, SocketClient>} */
this.clients = {}
}

Expand All @@ -18,27 +27,29 @@ class SocketAuthority {
*/
getUsersOnline() {
const onlineUsersMap = {}
Object.values(this.clients).filter(c => c.user).forEach(client => {
if (onlineUsersMap[client.user.id]) {
onlineUsersMap[client.user.id].connections++
} else {
onlineUsersMap[client.user.id] = {
...client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions),
connections: 1
Object.values(this.clients)
.filter((c) => c.user)
.forEach((client) => {
if (onlineUsersMap[client.user.id]) {
onlineUsersMap[client.user.id].connections++
} else {
onlineUsersMap[client.user.id] = {
...client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions),
connections: 1
}
}
}
})
})
return Object.values(onlineUsersMap)
}

getClientsForUser(userId) {
return Object.values(this.clients).filter(c => c.user && c.user.id === userId)
return Object.values(this.clients).filter((c) => c.user?.id === userId)
}

/**
* Emits event to all authorized clients
* @param {string} evt
* @param {any} data
* @param {string} evt
* @param {any} data
* @param {Function} [filter] optional filter function to only send event to specific users
*/
emitter(evt, data, filter = null) {
Expand Down Expand Up @@ -67,24 +78,22 @@ class SocketAuthority {
// Emits event to all admin user clients
adminEmitter(evt, data) {
for (const socketId in this.clients) {
if (this.clients[socketId].user && this.clients[socketId].user.isAdminOrUp) {
if (this.clients[socketId].user?.isAdminOrUp) {
this.clients[socketId].socket.emit(evt, data)
}
}
}

/**
* Closes the Socket.IO server and disconnect all clients
*
* @param {Function} callback
*
* @param {Function} callback
*/
close(callback) {
Logger.info('[SocketAuthority] Shutting down')
// This will close all open socket connections, and also close the underlying http server
if (this.io)
this.io.close(callback)
else
callback()
if (this.io) this.io.close(callback)
else callback()
}

initialize(Server) {
Expand All @@ -93,7 +102,7 @@ class SocketAuthority {
this.io = new SocketIO.Server(this.Server.server, {
cors: {
origin: '*',
methods: ["GET", "POST"]
methods: ['GET', 'POST']
}
})

Expand Down Expand Up @@ -144,7 +153,7 @@ class SocketAuthority {
// admin user can send a message to all authenticated users
// displays on the web app as a toast
const client = this.clients[socket.id] || {}
if (client.user && client.user.isAdminOrUp) {
if (client.user?.isAdminOrUp) {
this.emitter('admin_message', payload.message || '')
} else {
Logger.error(`[SocketAuthority] Non-admin user sent the message_all_users event`)
Expand All @@ -162,8 +171,8 @@ class SocketAuthority {
/**
* When setting up a socket connection the user needs to be associated with a socket id
* for this the client will send a 'auth' event that includes the users API token
*
* @param {SocketIO.Socket} socket
*
* @param {SocketIO.Socket} socket
* @param {string} token JWT
*/
async authenticateSocket(socket, token) {
Expand All @@ -176,6 +185,7 @@ class SocketAuthority {
Logger.error('Cannot validate socket - invalid token')
return socket.emit('invalid_token')
}

// get the user via the id from the decoded jwt.
const user = await Database.userModel.getUserByIdOrOldId(token_data.userId)
if (!user) {
Expand All @@ -196,18 +206,13 @@ class SocketAuthority {

client.user = user

if (!client.user.toJSONForBrowser) {
Logger.error('Invalid user...', client.user)
return
}

Logger.debug(`[SocketAuthority] User Online ${client.user.username}`)

this.adminEmitter('user_online', client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions))

// Update user lastSeen without firing sequelize bulk update hooks
user.lastSeen = Date.now()
await Database.userModel.updateFromOld(user, false)
await user.save({ hooks: false })

const initialPayload = {
userId: client.user.id,
Expand All @@ -224,4 +229,4 @@ class SocketAuthority {
this.Server.cancelLibraryScan(id)
}
}
module.exports = new SocketAuthority()
module.exports = new SocketAuthority()
2 changes: 1 addition & 1 deletion server/controllers/LibraryController.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class LibraryController {

// Only emit to users with access to library
const userFilter = (user) => {
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
return user.checkCanAccessLibrary?.(library.id)
}
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)

Expand Down
Loading

0 comments on commit 202ceb0

Please sign in to comment.