Update:Auth to use new user model

- Express requests include userNew to start migrating API controllers to new user model
This commit is contained in:
advplyr 2024-08-10 15:46:04 -05:00
parent 59370cae81
commit 202ceb02b5
14 changed files with 626 additions and 392 deletions

View file

@ -42,31 +42,41 @@ class User extends Model {
}
/**
* Get all oldUsers
* @returns {Promise<oldUser>}
*
* @param {string} type
* @returns
*/
static async getOldUsers() {
const users = await this.findAll({
include: this.sequelize.models.mediaProgress
})
return users.map((u) => this.getOldUser(u))
static getDefaultPermissionsForUserType(type) {
return {
download: true,
update: type === 'root' || type === 'admin',
delete: type === 'root',
upload: type === 'root' || type === 'admin',
accessAllLibraries: true,
accessAllTags: true,
accessExplicitContent: true,
librariesAccessible: [],
itemTagsSelected: []
}
}
/**
* Get old user model from new
*
* @param {Object} userExpanded
* @param {User} userExpanded
* @returns {oldUser}
*/
static getOldUser(userExpanded) {
const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress())
const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
const permissions = userExpanded.permissions || {}
const librariesAccessible = [...(userExpanded.permissions?.librariesAccessible || [])]
const itemTagsSelected = [...(userExpanded.permissions?.itemTagsSelected || [])]
const permissions = { ...(userExpanded.permissions || {}) }
delete permissions.librariesAccessible
delete permissions.itemTagsSelected
const seriesHideFromContinueListening = userExpanded.extraData?.seriesHideFromContinueListening || []
return new oldUser({
id: userExpanded.id,
oldUserId: userExpanded.extraData?.oldUserId || null,
@ -76,7 +86,7 @@ class User extends Model {
type: userExpanded.type,
token: userExpanded.token,
mediaProgress,
seriesHideFromContinueListening: userExpanded.extraData?.seriesHideFromContinueListening || [],
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
bookmarks: userExpanded.bookmarks,
isActive: userExpanded.isActive,
isLocked: userExpanded.isLocked,
@ -168,32 +178,35 @@ class User extends Model {
* Create root user
* @param {string} username
* @param {string} pash
* @param {Auth} auth
* @returns {Promise<oldUser>}
* @param {import('../Auth')} auth
* @returns {Promise<User>}
*/
static async createRootUser(username, pash, auth) {
const userId = uuidv4()
const token = await auth.generateAccessToken({ id: userId, username })
const newRoot = new oldUser({
const newUser = {
id: userId,
type: 'root',
username,
pash,
token,
isActive: true,
createdAt: Date.now()
})
await this.createFromOld(newRoot)
return newRoot
permissions: this.getDefaultPermissionsForUserType('root'),
bookmarks: [],
extraData: {
seriesHideFromContinueListening: []
}
}
return this.create(newUser)
}
/**
* Create user from openid userinfo
* @param {Object} userinfo
* @param {Auth} auth
* @returns {Promise<oldUser>}
* @param {import('../Auth')} auth
* @returns {Promise<User>}
*/
static async createUserFromOpenIdUserInfo(userinfo, auth) {
const userId = uuidv4()
@ -203,7 +216,7 @@ class User extends Model {
const token = await auth.generateAccessToken({ id: userId, username })
const newUser = new oldUser({
const newUser = {
id: userId,
type: 'user',
username,
@ -211,51 +224,30 @@ class User extends Model {
pash: null,
token,
isActive: true,
authOpenIDSub: userinfo.sub,
createdAt: Date.now()
})
if (await this.createFromOld(newUser)) {
SocketAuthority.adminEmitter('user_added', newUser.toJSONForBrowser())
return newUser
permissions: this.getDefaultPermissionsForUserType('user'),
bookmarks: [],
extraData: {
authOpenIDSub: userinfo.sub,
seriesHideFromContinueListening: []
}
}
const user = await this.create(newUser)
if (user) {
SocketAuthority.adminEmitter('user_added', user.toOldJSONForBrowser())
return user
}
return null
}
/**
* Get a user by id or by the old database id
* @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id
* @param {string} userId
* @returns {Promise<oldUser|null>} null if not found
*/
static async getUserByIdOrOldId(userId) {
if (!userId) return null
const user = await this.findOne({
where: {
[sequelize.Op.or]: [
{
id: userId
},
{
extraData: {
[sequelize.Op.substring]: userId
}
}
]
},
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by username case insensitive
* @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found
* @returns {Promise<User>}
*/
static async getUserByUsername(username) {
if (!username) return null
const user = await this.findOne({
return this.findOne({
where: {
username: {
[sequelize.Op.like]: username
@ -263,18 +255,16 @@ class User extends Model {
},
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by email case insensitive
* @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found
* @param {string} email
* @returns {Promise<User>}
*/
static async getUserByEmail(email) {
if (!email) return null
const user = await this.findOne({
return this.findOne({
where: {
email: {
[sequelize.Op.like]: email
@ -282,20 +272,45 @@ class User extends Model {
},
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
* Get user by id
* @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found
* @returns {Promise<User>}
*/
static async getUserById(userId) {
if (!userId) return null
const user = await this.findByPk(userId, {
return this.findByPk(userId, {
include: this.sequelize.models.mediaProgress
})
}
/**
* Get user by id or old id
* JWT tokens generated before 2.3.0 used old user ids
*
* @param {string} userId
* @returns {Promise<User>}
*/
static async getUserByIdOrOldId(userId) {
if (!userId) return null
return this.findOne({
where: {
[sequelize.Op.or]: [{ id: userId }, { 'extraData.oldUserId': userId }]
},
include: this.sequelize.models.mediaProgress
})
}
/**
* @deprecated
* Get old user by id
* @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found
*/
static async getOldUserById(userId) {
const user = await this.getUserById(userId)
if (!user) return null
return this.getOldUser(user)
}
@ -303,16 +318,14 @@ class User extends Model {
/**
* Get user by openid sub
* @param {string} sub
* @returns {Promise<oldUser|null>} returns null if not found
* @returns {Promise<User>}
*/
static async getUserByOpenIDSub(sub) {
if (!sub) return null
const user = await this.findOne({
return this.findOne({
where: sequelize.where(sequelize.literal(`extraData->>"authOpenIDSub"`), sub),
include: this.sequelize.models.mediaProgress
})
if (!user) return null
return this.getOldUser(user)
}
/**
@ -344,6 +357,20 @@ class User extends Model {
return count > 0
}
/**
* Check if user exists with username
* @param {string} username
* @returns {boolean}
*/
static async checkUserExistsWithUsername(username) {
const count = await this.count({
where: {
username
}
})
return count > 0
}
/**
* Initialize model
* @param {import('../Database').sequelize} sequelize
@ -380,6 +407,99 @@ class User extends Model {
}
)
}
get isAdminOrUp() {
return this.type === 'root' || this.type === 'admin'
}
get isUser() {
return this.type === 'user'
}
/** @type {string|null} */
get authOpenIDSub() {
return this.extraData?.authOpenIDSub || null
}
/**
* User data for clients
* Emitted on socket events user_online, user_offline and user_stream_update
*
* @param {import('../objects/PlaybackSession')[]} sessions
* @returns
*/
toJSONForPublic(sessions) {
const session = sessions?.find((s) => s.userId === this.id)?.toJSONForClient() || null
return {
id: this.id,
username: this.username,
type: this.type,
session,
lastSeen: this.lastSeen?.valueOf() || null,
createdAt: this.createdAt.valueOf()
}
}
/**
* User data for browser using old model
*
* @param {boolean} [hideRootToken=false]
* @param {boolean} [minimal=false]
* @returns
*/
toOldJSONForBrowser(hideRootToken = false, minimal = false) {
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
const librariesAccessible = this.permissions?.librariesAccessible || []
const itemTagsSelected = this.permissions?.itemTagsSelected || []
const permissions = { ...this.permissions }
delete permissions.librariesAccessible
delete permissions.itemTagsSelected
const json = {
id: this.id,
username: this.username,
email: this.email,
type: this.type,
token: this.type === 'root' && hideRootToken ? '' : this.token,
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
isActive: this.isActive,
isLocked: this.isLocked,
lastSeen: this.lastSeen?.valueOf() || null,
createdAt: this.createdAt.valueOf(),
permissions: permissions,
librariesAccessible: [...librariesAccessible],
itemTagsSelected: [...itemTagsSelected],
hasOpenIDLink: !!this.authOpenIDSub
}
if (minimal) {
delete json.mediaProgress
delete json.bookmarks
}
return json
}
/**
* Check user has access to library
*
* @param {string} libraryId
* @returns {boolean}
*/
checkCanAccessLibrary(libraryId) {
if (this.permissions?.accessAllLibraries) return true
if (!this.permissions?.librariesAccessible) return false
return this.permissions.librariesAccessible.includes(libraryId)
}
/**
* Get first available library id for user
*
* @param {string[]} libraryIds
* @returns {string|null}
*/
getDefaultLibraryId(libraryIds) {
// Libraries should already be in ascending display order, find first accessible
return libraryIds.find((lid) => this.checkCanAccessLibrary(lid)) || null
}
}
module.exports = User