123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903 |
- /* global WIKI */
- import { difference, find, first, flatten, flattenDeep, get, has, isArray, isEmpty, isNil, isString, last, set, toString, truncate, uniq } from 'lodash-es'
- import tfa from 'node-2fa'
- import jwt from 'jsonwebtoken'
- import { Model } from 'objection'
- import validate from 'validate.js'
- import qr from 'qr-image'
- import bcrypt from 'bcryptjs'
- import { Group } from './groups.mjs'
- /**
- * Users model
- */
- export class User extends Model {
- static get tableName() { return 'users' }
- static get jsonSchema () {
- return {
- type: 'object',
- required: ['email'],
- properties: {
- id: {type: 'string'},
- email: {type: 'string'},
- name: {type: 'string', minLength: 1, maxLength: 255},
- pictureUrl: {type: 'string'},
- isSystem: {type: 'boolean'},
- isActive: {type: 'boolean'},
- isVerified: {type: 'boolean'},
- createdAt: {type: 'string'},
- updatedAt: {type: 'string'}
- }
- }
- }
- static get jsonAttributes() {
- return ['auth', 'meta', 'prefs']
- }
- static get relationMappings() {
- return {
- groups: {
- relation: Model.ManyToManyRelation,
- modelClass: Group,
- join: {
- from: 'users.id',
- through: {
- from: 'userGroups.userId',
- to: 'userGroups.groupId'
- },
- to: 'groups.id'
- }
- }
- }
- }
- async $beforeUpdate(opt, context) {
- await super.$beforeUpdate(opt, context)
- this.updatedAt = new Date().toISOString()
- }
- async $beforeInsert(context) {
- await super.$beforeInsert(context)
- this.createdAt = new Date().toISOString()
- this.updatedAt = new Date().toISOString()
- }
- // ------------------------------------------------
- // Instance Methods
- // ------------------------------------------------
- async generateTFA(strategyId, siteId) {
- WIKI.logger.debug(`Generating new TFA secret for user ${this.id}...`)
- const site = WIKI.sites[siteId] ?? WIKI.sites[0] ?? { config: { title: 'Wiki' } }
- const tfaInfo = tfa.generateSecret({
- name: site.config.title,
- account: this.email
- })
- this.auth[strategyId].tfaSecret = tfaInfo.secret
- this.auth[strategyId].tfaIsActive = false
- await this.$query().patch({
- auth: this.auth
- })
- const safeTitle = site.config.title.replace(/[\s-.,=!@#$%?&*()+[\]{}/\\;<>]/g, '')
- return qr.imageSync(`otpauth://totp/${safeTitle}:${this.email}?secret=${tfaInfo.secret}`, { type: 'svg' })
- }
- async enableTFA(strategyId) {
- this.auth[strategyId].tfaIsActive = true
- return this.$query().patch({
- auth: this.auth
- })
- }
- async disableTFA(strategyId) {
- this.auth[strategyId].tfaIsActive = false
- return this.$query().patch({
- tfaIsActive: false,
- tfaSecret: ''
- })
- }
- verifyTFA(strategyId, code) {
- return tfa.verifyToken(this.auth[strategyId].tfaSecret, code)?.delta === 0
- }
- getPermissions () {
- return uniq(flatten(this.groups.map(g => g.permissions)))
- }
- getGroups() {
- return uniq(this.groups.map(g => g.id))
- }
- // ------------------------------------------------
- // Model Methods
- // ------------------------------------------------
- static async getById(id) {
- return WIKI.db.users.query().findById(id).withGraphFetched('groups').modifyGraph('groups', builder => {
- builder.select('groups.id', 'permissions')
- })
- }
- static async processProfile({ profile, providerKey }) {
- const provider = get(WIKI.auth.strategies, providerKey, {})
- provider.info = find(WIKI.data.authentication, ['key', provider.stategyKey])
- // Find existing user
- let user = await WIKI.db.users.query().findOne({
- providerId: toString(profile.id),
- providerKey
- })
- // Parse email
- let primaryEmail = ''
- if (isArray(profile.emails)) {
- const e = find(profile.emails, ['primary', true])
- primaryEmail = (e) ? e.value : first(profile.emails).value
- } else if (isArray(profile.email)) {
- primaryEmail = first(flattenDeep([profile.email]))
- } else if (isString(profile.email) && profile.email.length > 5) {
- primaryEmail = profile.email
- } else if (isString(profile.mail) && profile.mail.length > 5) {
- primaryEmail = profile.mail
- } else if (profile.user && profile.user.email && profile.user.email.length > 5) {
- primaryEmail = profile.user.email
- } else {
- throw new Error('Missing or invalid email address from profile.')
- }
- primaryEmail = primaryEmail.toLowerCase()
- // Find pending social user
- if (!user) {
- user = await WIKI.db.users.query().findOne({
- email: primaryEmail,
- providerId: null,
- providerKey
- })
- if (user) {
- user = await user.$query().patchAndFetch({
- providerId: toString(profile.id)
- })
- }
- }
- // Parse display name
- let displayName = ''
- if (isString(profile.displayName) && profile.displayName.length > 0) {
- displayName = profile.displayName
- } else if (isString(profile.name) && profile.name.length > 0) {
- displayName = profile.name
- } else {
- displayName = primaryEmail.split('@')[0]
- }
- // Parse picture URL / Data
- let pictureUrl = ''
- if (profile.picture && Buffer.isBuffer(profile.picture)) {
- pictureUrl = 'internal'
- } else {
- pictureUrl = truncate(get(profile, 'picture', get(user, 'pictureUrl', null)), {
- length: 255,
- omission: ''
- })
- }
- // Update existing user
- if (user) {
- if (!user.isActive) {
- throw new WIKI.Error.AuthAccountBanned()
- }
- if (user.isSystem) {
- throw new Error('This is a system reserved account and cannot be used.')
- }
- user = await user.$query().patchAndFetch({
- email: primaryEmail,
- name: displayName,
- pictureUrl: pictureUrl
- })
- if (pictureUrl === 'internal') {
- await WIKI.db.users.updateUserAvatarData(user.id, profile.picture)
- }
- return user
- }
- // Self-registration
- if (provider.selfRegistration) {
- // Check if email domain is whitelisted
- if (get(provider, 'domainWhitelist', []).length > 0) {
- const emailDomain = last(primaryEmail.split('@'))
- if (!provider.domainWhitelist.includes(emailDomain)) {
- throw new WIKI.Error.AuthRegistrationDomainUnauthorized()
- }
- }
- // Create account
- user = await WIKI.db.users.query().insertAndFetch({
- providerKey: providerKey,
- providerId: toString(profile.id),
- email: primaryEmail,
- name: displayName,
- pictureUrl: pictureUrl,
- locale: WIKI.config.lang.code,
- defaultEditor: 'markdown',
- tfaIsActive: false,
- isSystem: false,
- isActive: true,
- isVerified: true
- })
- // Assign to group(s)
- if (provider.autoEnrollGroups.length > 0) {
- await user.$relatedQuery('groups').relate(provider.autoEnrollGroups)
- }
- if (pictureUrl === 'internal') {
- await WIKI.db.users.updateUserAvatarData(user.id, profile.picture)
- }
- return user
- }
- throw new Error('You are not authorized to login.')
- }
- /**
- * Login a user
- */
- static async login ({ strategyId, siteId, username, password }, context) {
- if (has(WIKI.auth.strategies, strategyId)) {
- const selStrategy = WIKI.auth.strategies[strategyId]
- if (!selStrategy.isEnabled) {
- throw new WIKI.Error.AuthProviderInvalid()
- }
- const strInfo = find(WIKI.data.authentication, ['key', selStrategy.module])
- // Inject form user/pass
- if (strInfo.useForm) {
- set(context.req, 'body.email', username)
- set(context.req, 'body.password', password)
- set(context.req.params, 'strategy', strategyId)
- }
- // Authenticate
- return new Promise((resolve, reject) => {
- WIKI.auth.passport.authenticate(selStrategy.id, {
- session: !strInfo.useForm,
- scope: strInfo.scopes ? strInfo.scopes : null
- }, async (err, user, info) => {
- if (err) { return reject(err) }
- if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
- try {
- const resp = await WIKI.db.users.afterLoginChecks(user, selStrategy.id, context, {
- siteId,
- skipTFA: !strInfo.useForm,
- skipChangePwd: !strInfo.useForm
- })
- resolve(resp)
- } catch (err) {
- reject(err)
- }
- })(context.req, context.res, () => {})
- })
- } else {
- throw new WIKI.Error.AuthProviderInvalid()
- }
- }
- /**
- * Perform post-login checks
- */
- static async afterLoginChecks (user, strategyId, context, { siteId, skipTFA, skipChangePwd } = { skipTFA: false, skipChangePwd: false, siteId: null }) {
- const str = WIKI.auth.strategies[strategyId]
- if (!str) {
- throw new Error('ERR_INVALID_STRATEGY')
- }
- // Get redirect target
- user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin')
- let redirect = '/'
- if (user.groups && user.groups.length > 0) {
- for (const grp of user.groups) {
- if (!isEmpty(grp.redirectOnLogin) && grp.redirectOnLogin !== '/') {
- redirect = grp.redirectOnLogin
- break
- }
- }
- }
- // Get auth strategy flags
- const authStr = user.auth[strategyId] || {}
- // Is 2FA required?
- if (!skipTFA) {
- if (authStr.tfaIsActive && authStr.tfaSecret) {
- try {
- const tfaToken = await WIKI.db.userKeys.generateToken({
- kind: 'tfa',
- userId: user.id,
- meta: {
- strategyId
- }
- })
- return {
- nextAction: 'provideTfa',
- continuationToken: tfaToken,
- redirect
- }
- } catch (errc) {
- WIKI.logger.warn(errc)
- throw new WIKI.Error.AuthGenericError()
- }
- } else if (str.config?.enforceTfa || authStr.tfaRequired) {
- try {
- const tfaQRImage = await user.generateTFA(strategyId, siteId)
- const tfaToken = await WIKI.db.userKeys.generateToken({
- kind: 'tfaSetup',
- userId: user.id,
- meta: {
- strategyId
- }
- })
- return {
- nextAction: 'setupTfa',
- continuationToken: tfaToken,
- tfaQRImage,
- redirect
- }
- } catch (errc) {
- WIKI.logger.warn(errc)
- throw new WIKI.Error.AuthGenericError()
- }
- }
- }
- // Must Change Password?
- if (!skipChangePwd && authStr.mustChangePwd) {
- try {
- const pwdChangeToken = await WIKI.db.userKeys.generateToken({
- kind: 'changePwd',
- userId: user.id,
- meta: {
- strategyId
- }
- })
- return {
- nextAction: 'changePassword',
- continuationToken: pwdChangeToken,
- redirect
- }
- } catch (errc) {
- WIKI.logger.warn(errc)
- throw new WIKI.Error.AuthGenericError()
- }
- }
- return new Promise((resolve, reject) => {
- context.req.login(user, { session: false }, async errc => {
- if (errc) { return reject(errc) }
- const jwtToken = await WIKI.db.users.refreshToken(user, strategyId)
- resolve({
- nextAction: 'redirect',
- jwt: jwtToken.token,
- redirect
- })
- })
- })
- }
- /**
- * Generate a new token for a user
- */
- static async refreshToken (user) {
- if (isString(user)) {
- user = await WIKI.db.users.query().findById(user).withGraphFetched('groups').modifyGraph('groups', builder => {
- builder.select('groups.id', 'permissions')
- })
- if (!user) {
- WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
- throw new WIKI.Error.AuthGenericError()
- }
- if (!user.isActive) {
- WIKI.logger.warn(`Failed to refresh token for user ${user}: Inactive.`)
- throw new WIKI.Error.AuthAccountBanned()
- }
- } else if (isNil(user.groups)) {
- user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions')
- }
- // Update Last Login Date
- // -> Bypass Objection.js to avoid updating the updatedAt field
- await WIKI.db.knex('users').where('id', user.id).update({ lastLoginAt: new Date().toISOString() })
- return {
- token: jwt.sign({
- id: user.id,
- email: user.email,
- groups: user.getGroups()
- }, {
- key: WIKI.config.auth.certs.private,
- passphrase: WIKI.config.auth.secret
- }, {
- algorithm: 'RS256',
- expiresIn: WIKI.config.auth.tokenExpiration,
- audience: WIKI.config.auth.audience,
- issuer: 'urn:wiki.js'
- }),
- user
- }
- }
- /**
- * Verify a TFA login
- */
- static async loginTFA ({ strategyId, siteId, securityCode, continuationToken, setup }, context) {
- if (securityCode.length === 6 && continuationToken.length > 1) {
- const { user, strategyId: expectedStrategyId } = await WIKI.db.userKeys.validateToken({
- kind: setup ? 'tfaSetup' : 'tfa',
- token: continuationToken,
- skipDelete: setup
- })
- if (strategyId !== expectedStrategyId) {
- throw new Error('ERR_INVALID_STRATEGY')
- }
- if (user) {
- if (user.verifyTFA(strategyId, securityCode)) {
- if (setup) {
- await user.enableTFA(strategyId)
- }
- return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipTFA: true })
- } else {
- throw new Error('ERR_TFA_INCORRECT_TOKEN')
- }
- }
- }
- throw new Error('ERR_TFA_INVALID_REQUEST')
- }
- /**
- * Change Password from a Mandatory Password Change after Login
- */
- static async loginChangePassword ({ strategyId, siteId, continuationToken, newPassword }, context) {
- if (!newPassword || newPassword.length < 8) {
- throw new Error('ERR_PASSWORD_TOO_SHORT')
- }
- const { user, strategyId: expectedStrategyId } = await WIKI.db.userKeys.validateToken({
- kind: 'changePwd',
- token: continuationToken
- })
- if (strategyId !== expectedStrategyId) {
- throw new Error('ERR_INVALID_STRATEGY')
- }
- if (user) {
- user.auth[strategyId].password = await bcrypt.hash(newPassword, 12)
- user.auth[strategyId].mustChangePwd = false
- await user.$query().patch({
- auth: user.auth
- })
- return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipChangePwd: true, skipTFA: true })
- } else {
- throw new Error('ERR_INVALID_USER')
- }
- }
- /**
- * Change Password from Profile
- */
- static async changePassword ({ strategyId, siteId, currentPassword, newPassword }, context) {
- const userId = context.req.user?.id
- if (!userId) {
- throw new Error('ERR_NOT_AUTHENTICATED')
- }
- const user = await WIKI.db.users.query().findById(userId)
- if (!user) {
- throw new Error('ERR_INVALID_USER')
- }
- if (!newPassword || newPassword.length < 8) {
- throw new Error('ERR_PASSWORD_TOO_SHORT')
- }
- if (!user.auth[strategyId]?.password) {
- throw new Error('ERR_INVALID_STRATEGY')
- }
- if (await bcrypt.compare(currentPassword, user.auth[strategyId].password) !== true) {
- throw new Error('ERR_INCORRECT_CURRENT_PASSWORD')
- }
- user.auth[strategyId].password = await bcrypt.hash(newPassword, 12)
- user.auth[strategyId].mustChangePwd = false
- await user.$query().patch({
- auth: user.auth
- })
- return true
- }
- /**
- * Send a password reset request
- */
- static async loginForgotPassword ({ email }, context) {
- const usr = await WIKI.db.users.query().where({
- email,
- providerKey: 'local'
- }).first()
- if (!usr) {
- WIKI.logger.debug(`Password reset attempt on nonexistant local account ${email}: [DISCARDED]`)
- return
- }
- const resetToken = await WIKI.db.userKeys.generateToken({
- userId: usr.id,
- kind: 'resetPwd'
- })
- await WIKI.mail.send({
- template: 'accountResetPwd',
- to: email,
- subject: `Password Reset Request`,
- data: {
- preheadertext: `A password reset was requested for ${WIKI.config.title}`,
- title: `A password reset was requested for ${WIKI.config.title}`,
- content: `Click the button below to reset your password. If you didn't request this password reset, simply discard this email.`,
- buttonLink: `${WIKI.config.host}/login-reset/${resetToken}`,
- buttonText: 'Reset Password'
- },
- text: `A password reset was requested for wiki ${WIKI.config.title}. Open the following link to proceed: ${WIKI.config.host}/login-reset/${resetToken}`
- })
- }
- /**
- * Create a new user
- *
- * @param {Object} param0 User Fields
- */
- static async createNewUser ({ email, password, name, groups, userInitiated = false, mustChangePassword = false, sendWelcomeEmail = false, sendWelcomeEmailFromSiteId }) {
- const localAuth = await WIKI.db.authentication.getStrategy('local')
- // Check if self-registration is enabled
- if (userInitiated && !localAuth.registration) {
- throw new Error('ERR_REGISTRATION_DISABLED')
- }
- // Input sanitization
- email = email.toLowerCase().trim()
- // Input validation
- const validation = validate({
- email,
- password,
- name
- }, {
- email: {
- email: true,
- length: {
- maximum: 255
- }
- },
- password: {
- presence: {
- allowEmpty: false
- },
- length: {
- minimum: 6
- }
- },
- name: {
- presence: {
- allowEmpty: false
- },
- length: {
- minimum: 2,
- maximum: 255
- }
- }
- }, { format: 'flat' })
- if (validation && validation.length > 0) {
- throw new Error(`ERR_INVALID_INPUT: ${validation[0]}`)
- }
- // Check if email address is allowed
- if (userInitiated && localAuth.allowedEmailRegex) {
- const emailCheckRgx = new RegExp(localAuth.allowedEmailRegex, 'i')
- if (!emailCheckRgx.test(email)) {
- throw new Error('ERR_EMAIL_ADDRESS_NOT_ALLOWED')
- }
- }
- // Check if email already exists
- const usr = await WIKI.db.users.query().findOne({ email })
- if (usr) {
- throw new Error('ERR_DUPLICATE_ACCOUNT_EMAIL')
- }
- // Check if site ID is provided when send welcome email is enabled
- if (sendWelcomeEmail && !sendWelcomeEmailFromSiteId) {
- throw new Error('ERR_INVALID_SITE')
- }
- WIKI.logger.debug(`Creating new user account for ${email}...`)
- // Create the account
- const newUsr = await WIKI.db.users.query().insert({
- email,
- name,
- auth: {
- [localAuth.id]: {
- password: await bcrypt.hash(password, 12),
- mustChangePwd: mustChangePassword,
- restrictLogin: false,
- tfaRequired: false,
- tfaSecret: ''
- }
- },
- hasAvatar: false,
- isSystem: false,
- isActive: true,
- isVerified: true,
- meta: {
- jobTitle: '',
- location: '',
- pronouns: ''
- },
- prefs: {
- cvd: 'none',
- timezone: WIKI.config.userDefaults.timezone || 'America/New_York',
- appearance: 'site',
- dateFormat: WIKI.config.userDefaults.dateFormat || 'YYYY-MM-DD',
- timeFormat: WIKI.config.userDefaults.timeFormat || '12h'
- }
- }).returning('*')
- // Assign to group(s)
- const groupsToEnroll = [WIKI.data.systemIds.usersGroupId]
- if (groups?.length > 0) {
- groupsToEnroll.push(...groups)
- }
- if (userInitiated && localAuth.autoEnrollGroups?.length > 0) {
- groupsToEnroll.push(...localAuth.autoEnrollGroups)
- }
- await newUsr.$relatedQuery('groups').relate(uniq(groupsToEnroll))
- // Verification Email
- if (userInitiated && localAuth.config?.emailValidation) {
- // Create verification token
- const verificationToken = await WIKI.db.userKeys.generateToken({
- kind: 'verify',
- userId: newUsr.id
- })
- // TODO: Handle multilingual text
- // Send verification email
- await WIKI.mail.send({
- template: 'UserVerify',
- to: email,
- subject: 'Verify your account',
- data: {
- preheadertext: 'Verify your account in order to gain access to the wiki.',
- title: 'Verify your account',
- content: 'Click the button below in order to verify your account and gain access to the wiki.',
- buttonLink: `${WIKI.config.mail.defaultBaseURL}/verify/${verificationToken}`,
- buttonText: 'Verify'
- },
- text: `You must open the following link in your browser to verify your account and gain access to the wiki: ${WIKI.config.mail.defaultBaseURL}/verify/${verificationToken}`
- })
- } else if (sendWelcomeEmail) {
- // TODO: Handle multilingual text
- const site = WIKI.sites[sendWelcomeEmailFromSiteId]
- if (site) {
- const siteUrl = site.hostname === '*' ? WIKI.config.mail.defaultBaseURL : `https://${site.hostname}`
- // Send welcome email
- await WIKI.mail.send({
- template: 'UserWelcome',
- to: email,
- subject: `Welcome to the wiki ${site.config.title}`,
- data: {
- siteTitle: site.config.title,
- preview: `You've been invited to the wiki ${site.config.title}`,
- title: `You've been invited to the wiki ${site.config.title}`,
- content: `Click the button below to access the wiki.`,
- email,
- password,
- buttonLink: `${siteUrl}/login`,
- buttonText: 'Login',
- logo: `${siteUrl}/_site/logo`
- },
- text: `You've been invited to the wiki ${site.config.title}: ${siteUrl}/login`
- })
- } else {
- WIKI.logger.warn('An invalid site ID was provided when creating user. No welcome email was sent.')
- throw new Error('ERR_INVALID_SITE')
- }
- }
- WIKI.logger.debug(`Created new user account for ${email} successfully.`)
- return newUsr
- }
- /**
- * Update an existing user
- *
- * @param {Object} param0 User ID and fields to update
- */
- static async updateUser (id, { email, name, groups, auth, isVerified, isActive, meta, prefs }) {
- const usr = await WIKI.db.users.query().findById(id)
- if (usr) {
- let usrData = {}
- if (!isEmpty(email) && email !== usr.email) {
- const dupUsr = await WIKI.db.users.query().select('id').where({ email }).first()
- if (dupUsr) {
- throw new Error('ERR_DUPLICATE_ACCOUNT_EMAIL')
- }
- usrData.email = email.toLowerCase()
- }
- if (!isEmpty(name) && name !== usr.name) {
- usrData.name = name.trim()
- }
- if (isArray(groups)) {
- const usrGroupsRaw = await usr.$relatedQuery('groups')
- const usrGroups = usrGroupsRaw.map(g => g.id)
- // Relate added groups
- const addUsrGroups = difference(groups, usrGroups)
- for (const grp of addUsrGroups) {
- await usr.$relatedQuery('groups').relate(grp)
- }
- // Unrelate removed groups
- const remUsrGroups = difference(usrGroups, groups)
- for (const grp of remUsrGroups) {
- await usr.$relatedQuery('groups').unrelate().where('groupId', grp)
- }
- }
- if (!isNil(auth?.tfaRequired)) {
- usr.auth[WIKI.data.systemIds.localAuthId].tfaRequired = auth.tfaRequired
- usrData.auth = usr.auth
- }
- if (!isNil(auth?.mustChangePwd)) {
- usr.auth[WIKI.data.systemIds.localAuthId].mustChangePwd = auth.mustChangePwd
- usrData.auth = usr.auth
- }
- if (!isNil(auth?.restrictLogin)) {
- usr.auth[WIKI.data.systemIds.localAuthId].restrictLogin = auth.restrictLogin
- usrData.auth = usr.auth
- }
- if (!isNil(isVerified)) {
- usrData.isVerified = isVerified
- }
- if (!isNil(isActive)) {
- usrData.isVerified = isActive
- }
- if (!isEmpty(meta)) {
- usrData.meta = meta
- }
- if (!isEmpty(prefs)) {
- usrData.prefs = prefs
- }
- await WIKI.db.users.query().patch(usrData).findById(id)
- } else {
- throw new WIKI.Error.UserNotFound()
- }
- }
- /**
- * Delete a User
- *
- * @param {*} id User ID
- */
- static async deleteUser (id, replaceId) {
- const usr = await WIKI.db.users.query().findById(id)
- if (usr) {
- await WIKI.db.assets.query().patch({ authorId: replaceId }).where('authorId', id)
- await WIKI.db.comments.query().patch({ authorId: replaceId }).where('authorId', id)
- await WIKI.db.pageHistory.query().patch({ authorId: replaceId }).where('authorId', id)
- await WIKI.db.pages.query().patch({ authorId: replaceId }).where('authorId', id)
- await WIKI.db.pages.query().patch({ creatorId: replaceId }).where('creatorId', id)
- await WIKI.db.userKeys.query().delete().where('userId', id)
- await WIKI.db.users.query().deleteById(id)
- } else {
- throw new WIKI.Error.UserNotFound()
- }
- }
- /**
- * Logout the current user
- */
- static async logout (context) {
- if (!context.req.user || context.req.user.id === WIKI.config.auth.guestUserId) {
- return '/'
- }
- if (context.req.user.strategyId && has(WIKI.auth.strategies, context.req.user.strategyId)) {
- const selStrategy = WIKI.auth.strategies[context.req.user.strategyId]
- if (!selStrategy.isEnabled) {
- throw new WIKI.Error.AuthProviderInvalid()
- }
- const provider = find(WIKI.data.authentication, ['key', selStrategy.module])
- if (provider.logout) {
- return provider.logout(provider.config)
- }
- }
- return '/'
- }
- static async getGuestUser () {
- const user = await WIKI.db.users.query().findById(WIKI.config.auth.guestUserId).withGraphJoined('groups').modifyGraph('groups', builder => {
- builder.select('groups.id', 'permissions')
- })
- if (!user) {
- WIKI.logger.error('CRITICAL ERROR: Guest user is missing!')
- process.exit(1)
- }
- user.permissions = user.getPermissions()
- return user
- }
- static async getRootUser () {
- let user = await WIKI.db.users.query().findById(WIKI.config.auth.rootAdminUserId)
- if (!user) {
- WIKI.logger.error('CRITICAL ERROR: Root Administrator user is missing!')
- process.exit(1)
- }
- user.permissions = ['manage:system']
- return user
- }
- /**
- * Add / Update User Avatar Data
- */
- static async updateUserAvatarData (userId, data) {
- try {
- WIKI.logger.debug(`Updating user ${userId} avatar data...`)
- if (data.length > 1024 * 1024) {
- throw new Error('Avatar image filesize is too large. 1MB max.')
- }
- const existing = await WIKI.db.knex('userAvatars').select('id').where('id', userId).first()
- if (existing) {
- await WIKI.db.knex('userAvatars').where({
- id: userId
- }).update({
- data
- })
- } else {
- await WIKI.db.knex('userAvatars').insert({
- id: userId,
- data
- })
- }
- } catch (err) {
- WIKI.logger.warn(`Failed to process binary thumbnail data for user ${userId}: ${err.message}`)
- }
- }
- static async getUserAvatarData (userId) {
- try {
- const usrData = await WIKI.db.knex('userAvatars').where('id', userId).first()
- if (usrData) {
- return usrData.data
- } else {
- return null
- }
- } catch (err) {
- WIKI.logger.warn(`Failed to process binary thumbnail data for user ${userId}`)
- }
- }
- }
|