2022-11-05 22:02:51 +01:00
|
|
|
import jwt from 'jsonwebtoken'
|
2022-11-03 19:56:06 +01:00
|
|
|
import { log } from '../utils/log.mjs'
|
|
|
|
import { hash, hashPassword, randomString, verifyPassword } from '../utils/crypto.mjs'
|
2022-11-11 18:02:28 +01:00
|
|
|
import { setPersonAvatar } from '../utils/sanity.mjs'
|
2022-11-05 22:02:51 +01:00
|
|
|
import { clean, asJson, i18nUrl } from '../utils/index.mjs'
|
2022-11-03 19:56:06 +01:00
|
|
|
import { ConfirmationModel } from './confirmation.mjs'
|
|
|
|
|
|
|
|
export function UserModel(tools) {
|
|
|
|
this.config = tools.config
|
|
|
|
this.prisma = tools.prisma
|
|
|
|
this.decrypt = tools.decrypt
|
|
|
|
this.encrypt = tools.encrypt
|
|
|
|
this.mailer = tools.email
|
|
|
|
this.Confirmation = new ConfirmationModel(tools)
|
2022-11-08 21:04:32 +01:00
|
|
|
this.clear = {} // For holding decrypted data
|
2022-11-03 19:56:06 +01:00
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
|
|
|
* Loads a user from the database based on the where clause you pass it
|
|
|
|
*
|
|
|
|
* Stores result in this.record
|
|
|
|
*/
|
2022-11-05 22:02:51 +01:00
|
|
|
UserModel.prototype.read = async function (where) {
|
2022-11-07 20:42:07 +01:00
|
|
|
try {
|
|
|
|
this.record = await this.prisma.user.findUnique({ where })
|
|
|
|
} catch (err) {
|
|
|
|
log.warn({ err, where }, 'Could not read user')
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:04:32 +01:00
|
|
|
this.reveal()
|
2022-11-03 19:56:06 +01:00
|
|
|
|
|
|
|
return this.setExists()
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:04:32 +01:00
|
|
|
/*
|
|
|
|
* Helper method to decrypt at-rest data
|
|
|
|
*/
|
|
|
|
UserModel.prototype.reveal = async function (where) {
|
|
|
|
this.clear = {}
|
|
|
|
if (this.record) {
|
2022-11-08 22:41:30 +01:00
|
|
|
this.clear.bio = this.decrypt(this.record.bio)
|
|
|
|
this.clear.github = this.decrypt(this.record.github)
|
2022-11-08 21:04:32 +01:00
|
|
|
this.clear.email = this.decrypt(this.record.email)
|
|
|
|
this.clear.initial = this.decrypt(this.record.initial)
|
|
|
|
}
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-08 22:41:30 +01:00
|
|
|
/*
|
|
|
|
* Helper method to encrypt at-rest data
|
|
|
|
*/
|
|
|
|
UserModel.prototype.cloak = function (data) {
|
|
|
|
for (const field of ['bio', 'github', 'email']) {
|
|
|
|
if (typeof data[field] !== 'undefined') data[field] = this.encrypt(data[field])
|
|
|
|
}
|
|
|
|
if (typeof data.password === 'string') data.password = asJson(hashPassword(data.password))
|
|
|
|
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:04:32 +01:00
|
|
|
/*
|
|
|
|
* Loads a user from the database based on the where clause you pass it
|
|
|
|
* In addition prepares it for returning the account data
|
|
|
|
*
|
|
|
|
* Stores result in this.record
|
|
|
|
*/
|
|
|
|
UserModel.prototype.readAsAccount = async function (where) {
|
|
|
|
await this.read(where)
|
|
|
|
|
|
|
|
return this.setResponse(200, false, {
|
|
|
|
result: 'success',
|
|
|
|
account: this.asAccount(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-07 20:42:07 +01:00
|
|
|
/*
|
|
|
|
* Finds a user based on one of the accepted unique fields which are:
|
|
|
|
* - lusername (lowercase username)
|
|
|
|
* - ehash
|
|
|
|
* - id
|
|
|
|
*
|
|
|
|
* Stores result in this.record
|
|
|
|
*/
|
|
|
|
UserModel.prototype.find = async function (body) {
|
|
|
|
try {
|
|
|
|
this.record = await this.prisma.user.findFirst({
|
|
|
|
where: {
|
|
|
|
OR: [
|
|
|
|
{ lusername: { equals: clean(body.username) } },
|
|
|
|
{ ehash: { equals: hash(clean(body.username)) } },
|
|
|
|
{ id: { equals: parseInt(body.username) || -1 } },
|
|
|
|
],
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
log.warn({ err, body }, `Error while trying to find user: ${body.username}`)
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:04:32 +01:00
|
|
|
this.reveal()
|
|
|
|
|
2022-11-07 20:42:07 +01:00
|
|
|
return this.setExists()
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
|
|
|
* Loads the user that is making the API request
|
|
|
|
*
|
|
|
|
* Stores result in this.authenticatedUser
|
|
|
|
*/
|
2022-11-05 18:55:59 +01:00
|
|
|
UserModel.prototype.loadAuthenticatedUser = async function (user) {
|
|
|
|
if (!user) return this
|
2022-11-05 19:37:47 +01:00
|
|
|
this.authenticatedUser = await this.prisma.user.findUnique({
|
2022-11-08 21:04:32 +01:00
|
|
|
where: { id: user.uid },
|
2022-11-05 18:55:59 +01:00
|
|
|
include: {
|
|
|
|
apikeys: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
|
|
|
* Checks this.record and sets a boolean to indicate whether
|
|
|
|
* the user exists or not
|
|
|
|
*
|
|
|
|
* Stores result in this.exists
|
|
|
|
*/
|
2022-11-03 19:56:06 +01:00
|
|
|
UserModel.prototype.setExists = function () {
|
|
|
|
this.exists = this.record ? true : false
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
|
|
|
* Creates a user+confirmation and sends out signup email
|
|
|
|
*/
|
2022-11-05 22:02:51 +01:00
|
|
|
UserModel.prototype.create = async function ({ body }) {
|
2022-11-03 19:56:06 +01:00
|
|
|
if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing')
|
|
|
|
if (!body.email) return this.setResponse(400, 'emailMissing')
|
|
|
|
if (!body.language) return this.setResponse(400, 'languageMissing')
|
2022-11-08 22:41:30 +01:00
|
|
|
if (!this.config.languages.includes(body.language))
|
|
|
|
return this.setResponse(400, 'unsupportedLanguage')
|
2022-11-03 19:56:06 +01:00
|
|
|
|
|
|
|
const ehash = hash(clean(body.email))
|
2022-11-05 22:02:51 +01:00
|
|
|
await this.read({ ehash })
|
2022-11-03 19:56:06 +01:00
|
|
|
if (this.exists) return this.setResponse(400, 'emailExists')
|
|
|
|
|
|
|
|
try {
|
2022-11-08 21:04:32 +01:00
|
|
|
this.clear.email = clean(body.email)
|
|
|
|
this.clear.initial = this.clear.email
|
2022-11-03 19:56:06 +01:00
|
|
|
this.language = body.language
|
2022-11-08 21:04:32 +01:00
|
|
|
const email = this.encrypt(this.clear.email)
|
2022-11-03 19:56:06 +01:00
|
|
|
const username = clean(randomString()) // Temporary username
|
2022-11-08 21:04:32 +01:00
|
|
|
const data = {
|
|
|
|
ehash,
|
|
|
|
ihash: ehash,
|
|
|
|
email,
|
|
|
|
initial: email,
|
|
|
|
username,
|
|
|
|
lusername: username,
|
2022-11-08 22:41:30 +01:00
|
|
|
language: body.language,
|
|
|
|
password: asJson(hashPassword(randomString())), // We'll change this later
|
|
|
|
github: this.encrypt(''),
|
|
|
|
bio: this.encrypt(''),
|
2022-11-08 21:04:32 +01:00
|
|
|
}
|
|
|
|
this.record = await this.prisma.user.create({ data })
|
2022-11-03 19:56:06 +01:00
|
|
|
} catch (err) {
|
|
|
|
log.warn(err, 'Could not create user record')
|
|
|
|
return this.setResponse(500, 'createAccountFailed')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update username
|
|
|
|
try {
|
2022-11-08 21:04:32 +01:00
|
|
|
await this.safeUpdate({
|
2022-11-03 19:56:06 +01:00
|
|
|
username: `user-${this.record.id}`,
|
|
|
|
lusername: `user-${this.record.id}`,
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
log.warn(err, 'Could not update username after user creation')
|
2022-11-05 22:02:51 +01:00
|
|
|
return this.setResponse(500, 'usernameUpdateAfterUserCreationFailed')
|
2022-11-03 19:56:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create confirmation
|
|
|
|
this.confirmation = await this.Confirmation.create({
|
|
|
|
type: 'signup',
|
2022-11-08 21:04:32 +01:00
|
|
|
data: {
|
2022-11-03 19:56:06 +01:00
|
|
|
language: this.language,
|
2022-11-08 21:04:32 +01:00
|
|
|
email: this.clear.email,
|
2022-11-03 19:56:06 +01:00
|
|
|
id: this.record.id,
|
|
|
|
ehash: ehash,
|
2022-11-08 21:04:32 +01:00
|
|
|
},
|
2022-11-05 22:02:51 +01:00
|
|
|
userId: this.record.id,
|
2022-11-03 19:56:06 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// Send signup email
|
2022-11-07 19:50:51 +01:00
|
|
|
if (!this.isUnitTest(body) || this.config.tests.sendEmail)
|
|
|
|
await this.mailer.send({
|
|
|
|
template: 'signup',
|
|
|
|
language: this.language,
|
2022-11-08 21:04:32 +01:00
|
|
|
to: this.clear.email,
|
2022-11-07 19:50:51 +01:00
|
|
|
replacements: {
|
|
|
|
actionUrl: i18nUrl(this.language, `/confirm/signup/${this.Confirmation.record.id}`),
|
|
|
|
whyUrl: i18nUrl(this.language, `/docs/faq/email/why-signup`),
|
|
|
|
supportUrl: i18nUrl(this.language, `/patrons/join`),
|
|
|
|
},
|
|
|
|
})
|
2022-11-03 19:56:06 +01:00
|
|
|
|
2022-11-07 19:50:51 +01:00
|
|
|
return this.isUnitTest(body)
|
2022-11-08 21:04:32 +01:00
|
|
|
? this.setResponse(201, false, {
|
|
|
|
email: this.clear.email,
|
|
|
|
confirmation: this.confirmation.record.id,
|
|
|
|
})
|
|
|
|
: this.setResponse(201, false, { email: this.clear.email })
|
2022-11-03 19:56:06 +01:00
|
|
|
}
|
|
|
|
|
2022-11-07 20:42:07 +01:00
|
|
|
/*
|
|
|
|
* Login based on username + password
|
|
|
|
*/
|
|
|
|
UserModel.prototype.passwordLogin = async function (req) {
|
|
|
|
if (Object.keys(req.body) < 1) return this.setReponse(400, 'postBodyMissing')
|
|
|
|
if (!req.body.username) return this.setReponse(400, 'usernameMissing')
|
|
|
|
if (!req.body.password) return this.setReponse(400, 'passwordMissing')
|
|
|
|
|
|
|
|
await this.find(req.body)
|
|
|
|
if (!this.exists) {
|
|
|
|
log.warn(`Login attempt for non-existing user: ${req.body.username} from ${req.ip}`)
|
|
|
|
return this.setResponse(401, 'loginFailed')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Account found, check password
|
|
|
|
const [valid, updatedPasswordField] = verifyPassword(req.body.password, this.record.password)
|
|
|
|
if (!valid) {
|
|
|
|
log.warn(`Wrong password for existing user: ${req.body.username} from ${req.ip}`)
|
|
|
|
return this.setResponse(401, 'loginFailed')
|
|
|
|
}
|
|
|
|
|
|
|
|
log.info(`Login by user ${this.record.id} (${this.record.username})`)
|
|
|
|
|
|
|
|
// Login success
|
|
|
|
if (updatedPasswordField) {
|
|
|
|
// Update the password field with a v3 hash
|
2022-11-08 21:04:32 +01:00
|
|
|
await this.safeUpdate({ password: updatedPasswordField })
|
2022-11-07 20:42:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.isOk() ? this.loginOk() : this.setResponse(401, 'loginFailed')
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
2022-11-05 22:02:51 +01:00
|
|
|
* Confirms a user account
|
2022-11-05 19:37:47 +01:00
|
|
|
*/
|
2022-11-05 22:02:51 +01:00
|
|
|
UserModel.prototype.confirm = async function ({ body, params }) {
|
|
|
|
if (!params.id) return this.setReponse(404, 'missingConfirmationId')
|
|
|
|
if (Object.keys(body) < 1) return this.setResponse(400, 'postBodyMissing')
|
|
|
|
if (!body.consent || typeof body.consent !== 'number' || body.consent < 1)
|
|
|
|
return this.setResponse(400, 'consentRequired')
|
|
|
|
|
|
|
|
// Retrieve confirmation record
|
|
|
|
await this.Confirmation.read({ id: params.id })
|
|
|
|
|
|
|
|
if (!this.Confirmation.exists) {
|
|
|
|
log.warn(err, `Could not find confirmation id ${params.id}`)
|
|
|
|
return this.setResponse(404, 'failedToFindConfirmationId')
|
2022-11-03 19:56:06 +01:00
|
|
|
}
|
|
|
|
|
2022-11-05 22:02:51 +01:00
|
|
|
if (this.Confirmation.record.type !== 'signup') {
|
|
|
|
log.warn(err, `Confirmation mismatch; ${params.id} is not a signup id`)
|
|
|
|
return this.setResponse(404, 'confirmationIdTypeMismatch')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.error) return this
|
2022-11-08 21:04:32 +01:00
|
|
|
const data = this.Confirmation.clear.data
|
2022-11-05 22:02:51 +01:00
|
|
|
if (data.ehash !== this.Confirmation.record.user.ehash)
|
|
|
|
return this.setResponse(404, 'confirmationEhashMismatch')
|
|
|
|
if (data.id !== this.Confirmation.record.user.id)
|
|
|
|
return this.setResponse(404, 'confirmationUserIdMismatch')
|
|
|
|
|
|
|
|
// Load user
|
|
|
|
await this.read({ id: this.Confirmation.record.user.id })
|
|
|
|
if (this.error) return this
|
|
|
|
|
|
|
|
// Update user status, consent, and last login
|
2022-11-08 21:04:32 +01:00
|
|
|
await this.safeUpdate({
|
2022-11-07 20:42:07 +01:00
|
|
|
status: 1,
|
2022-11-05 22:02:51 +01:00
|
|
|
consent: body.consent,
|
|
|
|
lastLogin: new Date(),
|
|
|
|
})
|
|
|
|
if (this.error) return this
|
|
|
|
|
|
|
|
// Account is now active, let's return a passwordless login
|
2022-11-07 20:42:07 +01:00
|
|
|
return this.loginOk()
|
2022-11-03 19:56:06 +01:00
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
2022-11-08 21:04:32 +01:00
|
|
|
* Updates the user data - Used when we create the data ourselves
|
|
|
|
* so we know it's safe
|
2022-11-05 19:37:47 +01:00
|
|
|
*/
|
2022-11-08 21:04:32 +01:00
|
|
|
UserModel.prototype.safeUpdate = async function (data) {
|
2022-11-03 19:56:06 +01:00
|
|
|
try {
|
|
|
|
this.record = await this.prisma.user.update({
|
|
|
|
where: { id: this.record.id },
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
log.warn(err, 'Could not update user record')
|
|
|
|
process.exit()
|
2022-11-05 22:02:51 +01:00
|
|
|
return this.setResponse(500, 'updateUserFailed')
|
2022-11-03 19:56:06 +01:00
|
|
|
}
|
2022-11-10 20:09:30 +01:00
|
|
|
await this.reveal()
|
2022-11-03 19:56:06 +01:00
|
|
|
|
|
|
|
return this.setResponse(200)
|
|
|
|
}
|
|
|
|
|
2022-11-08 21:04:32 +01:00
|
|
|
/*
|
|
|
|
* Updates the user data - Used when we pass through user-provided data
|
|
|
|
* so we can't be certain it's safe
|
|
|
|
*/
|
|
|
|
UserModel.prototype.unsafeUpdate = async function (body) {
|
|
|
|
const data = {}
|
2022-11-08 22:41:30 +01:00
|
|
|
const notes = []
|
|
|
|
// Bio
|
|
|
|
if (typeof body.bio === 'string') data.bio = body.bio
|
|
|
|
// Consent
|
2022-11-08 21:04:32 +01:00
|
|
|
if ([0, 1, 2, 3].includes(body.consent)) data.consent = body.consent
|
2022-11-08 22:41:30 +01:00
|
|
|
// Github
|
|
|
|
if (typeof body.github === 'string') data.github = body.github.split('@').pop()
|
|
|
|
// Imperial
|
|
|
|
if ([true, false].includes(body.imperial)) data.imperial = body.imperial
|
|
|
|
// Language
|
|
|
|
if (this.config.languages.includes(body.language)) data.language = body.language
|
|
|
|
// Newsletter
|
2022-11-08 21:04:32 +01:00
|
|
|
if ([true, false].includes(body.newsletter)) data.newsletter = body.newsletter
|
2022-11-08 22:41:30 +01:00
|
|
|
// Password
|
|
|
|
if (typeof body.password === 'string') data.password = body.password // Will be cloaked below
|
|
|
|
// Username
|
2022-11-08 21:04:32 +01:00
|
|
|
if (typeof body.username === 'string') {
|
2022-11-08 22:41:30 +01:00
|
|
|
const available = await this.isLusernameAvailable(body.username)
|
|
|
|
if (available) {
|
2022-11-08 21:04:32 +01:00
|
|
|
data.username = body.username.trim()
|
|
|
|
data.lusername = clean(body.username)
|
2022-11-08 22:41:30 +01:00
|
|
|
} else {
|
|
|
|
log.info(`Rejected user name change from ${data.username} to ${body.username.trim()}`)
|
|
|
|
notes.push('usernameChangeRejected')
|
2022-11-08 21:04:32 +01:00
|
|
|
}
|
|
|
|
}
|
2022-11-11 18:02:28 +01:00
|
|
|
// Image (img)
|
|
|
|
if (typeof body.img === 'string') {
|
|
|
|
const img = await setPersonAvatar(this.record.id, body.img)
|
|
|
|
data.img = img.url
|
|
|
|
}
|
2022-11-08 21:04:32 +01:00
|
|
|
|
2022-11-08 22:41:30 +01:00
|
|
|
// Now update the record
|
|
|
|
await this.safeUpdate(this.cloak(data))
|
|
|
|
|
2022-11-11 18:02:28 +01:00
|
|
|
const isUnitTest = this.isUnitTest(body)
|
2022-11-08 22:41:30 +01:00
|
|
|
if (typeof body.email === 'string' && this.clear.email !== clean(body.email)) {
|
2022-11-11 18:02:28 +01:00
|
|
|
// Email change (requires confirmation)
|
|
|
|
this.confirmation = await this.Confirmation.create({
|
|
|
|
type: 'emailchange',
|
|
|
|
data: {
|
|
|
|
language: this.record.language,
|
|
|
|
email: {
|
|
|
|
current: this.clear.email,
|
|
|
|
new: body.email,
|
2022-11-08 22:41:30 +01:00
|
|
|
},
|
2022-11-11 18:02:28 +01:00
|
|
|
},
|
|
|
|
userId: this.record.id,
|
|
|
|
})
|
|
|
|
if (!isUnitTest || this.config.tests.sendEmail) {
|
2022-11-08 22:41:30 +01:00
|
|
|
// Send confirmation email
|
|
|
|
await this.mailer.send({
|
|
|
|
template: 'emailchange',
|
|
|
|
language: this.record.language,
|
|
|
|
to: body.email,
|
|
|
|
cc: this.clear.email,
|
|
|
|
replacements: {
|
|
|
|
actionUrl: i18nUrl(this.language, `/confirm/emailchange/${this.Confirmation.record.id}`),
|
|
|
|
whyUrl: i18nUrl(this.language, `/docs/faq/email/why-emailchange`),
|
|
|
|
supportUrl: i18nUrl(this.language, `/patrons/join`),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2022-11-11 18:02:28 +01:00
|
|
|
} else if (typeof body.confirmation === 'string' && body.confirm === 'emailchange') {
|
|
|
|
// Handle email change confirmation
|
|
|
|
await this.Confirmation.read({ id: body.confirmation })
|
|
|
|
|
|
|
|
if (!this.Confirmation.exists) {
|
|
|
|
log.warn(err, `Could not find confirmation id ${params.id}`)
|
|
|
|
return this.setResponse(404, 'failedToFindConfirmationId')
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.Confirmation.record.type !== 'emailchange') {
|
|
|
|
log.warn(err, `Confirmation mismatch; ${params.id} is not an emailchange id`)
|
|
|
|
return this.setResponse(404, 'confirmationIdTypeMismatch')
|
|
|
|
}
|
|
|
|
|
|
|
|
const data = this.Confirmation.clear.data
|
|
|
|
if (data.email.current === this.clear.email && typeof data.email.new === 'string') {
|
|
|
|
await this.safeUpdate({
|
|
|
|
email: this.encrypt(data.email.new),
|
|
|
|
ehash: hash(clean(data.email.new)),
|
|
|
|
})
|
|
|
|
}
|
2022-11-08 21:04:32 +01:00
|
|
|
}
|
|
|
|
|
2022-11-11 18:02:28 +01:00
|
|
|
const returnData = {
|
2022-11-08 22:41:30 +01:00
|
|
|
result: 'success',
|
|
|
|
account: this.asAccount(),
|
2022-11-11 18:02:28 +01:00
|
|
|
}
|
|
|
|
if (isUnitTest) returnData.confirmation = this.Confirmation.record.id
|
|
|
|
|
|
|
|
return this.setResponse(200, false, returnData)
|
2022-11-08 21:04:32 +01:00
|
|
|
}
|
|
|
|
|
2022-11-05 22:02:51 +01:00
|
|
|
/*
|
|
|
|
* Returns account data
|
|
|
|
*/
|
|
|
|
UserModel.prototype.asAccount = function () {
|
|
|
|
return {
|
|
|
|
id: this.record.id,
|
2022-11-10 20:09:30 +01:00
|
|
|
bio: this.clear.bio,
|
2022-11-05 22:02:51 +01:00
|
|
|
consent: this.record.consent,
|
|
|
|
createdAt: this.record.createdAt,
|
2022-11-08 21:04:32 +01:00
|
|
|
data: this.clear.data,
|
|
|
|
email: this.clear.email,
|
2022-11-10 20:09:30 +01:00
|
|
|
github: this.clear.github,
|
2022-11-11 18:02:28 +01:00
|
|
|
img: this.record.img,
|
2022-11-10 20:09:30 +01:00
|
|
|
imperial: this.record.imperial,
|
2022-11-08 21:04:32 +01:00
|
|
|
initial: this.clear.initial,
|
2022-11-10 20:09:30 +01:00
|
|
|
language: this.record.language,
|
2022-11-05 22:02:51 +01:00
|
|
|
lastLogin: this.record.lastLogin,
|
|
|
|
newsletter: this.record.newsletter,
|
|
|
|
patron: this.record.patron,
|
|
|
|
role: this.record.role,
|
|
|
|
status: this.record.status,
|
|
|
|
updatedAt: this.record.updatedAt,
|
|
|
|
username: this.record.username,
|
|
|
|
lusername: this.record.lusername,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns a JSON Web Token (jwt)
|
|
|
|
*/
|
|
|
|
UserModel.prototype.getToken = function () {
|
|
|
|
return jwt.sign(
|
|
|
|
{
|
|
|
|
_id: this.record.id,
|
|
|
|
username: this.record.username,
|
|
|
|
role: this.record.role,
|
|
|
|
status: this.record.status,
|
|
|
|
aud: this.config.jwt.audience,
|
|
|
|
iss: this.config.jwt.issuer,
|
|
|
|
},
|
|
|
|
this.config.jwt.secretOrKey,
|
|
|
|
{ expiresIn: this.config.jwt.expiresIn }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
/*
|
|
|
|
* Helper method to set the response code, result, and body
|
|
|
|
*
|
|
|
|
* Will be used by this.sendResponse()
|
|
|
|
*/
|
|
|
|
UserModel.prototype.setResponse = function (status = 200, error = false, data = {}) {
|
|
|
|
this.response = {
|
|
|
|
status,
|
|
|
|
body: {
|
|
|
|
result: 'success',
|
|
|
|
...data,
|
|
|
|
},
|
2022-11-05 18:55:59 +01:00
|
|
|
}
|
2022-11-05 19:37:47 +01:00
|
|
|
if (status > 201) {
|
|
|
|
this.response.body.error = error
|
|
|
|
this.response.body.result = 'error'
|
|
|
|
this.error = true
|
|
|
|
} else this.error = false
|
2022-11-05 18:55:59 +01:00
|
|
|
|
2022-11-05 19:37:47 +01:00
|
|
|
return this.setExists()
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper method to send response
|
|
|
|
*/
|
|
|
|
UserModel.prototype.sendResponse = async function (res) {
|
|
|
|
return res.status(this.response.status).send(this.response.body)
|
2022-11-05 18:55:59 +01:00
|
|
|
}
|
2022-11-07 19:50:51 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Update method to determine whether this request is
|
|
|
|
* part of a unit test
|
|
|
|
*/
|
|
|
|
UserModel.prototype.isUnitTest = function (body) {
|
2022-11-11 18:02:28 +01:00
|
|
|
if (!body.unittest) return false
|
|
|
|
if (!this.clear.email.split('@').pop() === this.config.tests.domain) return false
|
|
|
|
if (body.email && !body.email.split('@').pop() === this.config.tests.domain) return false
|
|
|
|
|
|
|
|
return true
|
2022-11-07 19:50:51 +01:00
|
|
|
}
|
2022-11-07 20:42:07 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper method to check an account is ok
|
|
|
|
*/
|
|
|
|
UserModel.prototype.isOk = function () {
|
|
|
|
if (
|
|
|
|
this.exists &&
|
|
|
|
this.record &&
|
|
|
|
this.record.status > 0 &&
|
|
|
|
this.record.consent > 0 &&
|
|
|
|
this.record.role &&
|
|
|
|
this.record.role !== 'blocked'
|
|
|
|
)
|
|
|
|
return true
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper method to return from successful login
|
|
|
|
*/
|
|
|
|
UserModel.prototype.loginOk = function () {
|
|
|
|
return this.setResponse(200, false, {
|
|
|
|
result: 'success',
|
|
|
|
token: this.getToken(),
|
|
|
|
account: this.asAccount(),
|
|
|
|
})
|
|
|
|
}
|
2022-11-08 22:41:30 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check to see if a (lowercase) username is available
|
|
|
|
* as well as making sure username is not something we
|
|
|
|
* do not allow
|
|
|
|
*/
|
|
|
|
UserModel.prototype.isLusernameAvailable = async function (lusername) {
|
|
|
|
if (lusername.length < 2) return false
|
|
|
|
let found
|
|
|
|
try {
|
|
|
|
found = await this.prisma.user.findUnique({ where: { lusername } })
|
|
|
|
} catch (err) {
|
|
|
|
log.warn({ err, where }, 'Could not search for free username')
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|