ajout app
This commit is contained in:
191
Voltaserve/idp/src/user/service.ts
Normal file
191
Voltaserve/idp/src/user/service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import fs from 'fs/promises'
|
||||
import { ErrorCode, newError } from '@/infra/error'
|
||||
import { hashPassword, verifyPassword } from '@/infra/password'
|
||||
import search, { USER_SEARCH_INDEX } from '@/infra/search'
|
||||
import userRepo, { User } from '@/user/repo'
|
||||
import { newHyphenlessUuid } from '@/infra/id'
|
||||
import { sendTemplateMail } from '@/infra/mail'
|
||||
import { getConfig } from '@/config/config'
|
||||
|
||||
export type UserDTO = {
|
||||
id: string
|
||||
fullName: string
|
||||
picture: string
|
||||
email: string
|
||||
username: string
|
||||
pendingEmail?: string
|
||||
}
|
||||
|
||||
export type UserUpdateEmailRequestOptions = {
|
||||
email: string
|
||||
}
|
||||
|
||||
export type UserUpdateEmailConfirmationOptions = {
|
||||
token: string
|
||||
}
|
||||
|
||||
export type UserUpdateFullNameOptions = {
|
||||
fullName: string
|
||||
}
|
||||
|
||||
export type UserUpdatePasswordOptions = {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export type UserDeleteOptions = {
|
||||
password: string
|
||||
}
|
||||
|
||||
export async function getUser(id: string): Promise<UserDTO> {
|
||||
return mapEntity(await userRepo.findByID(id))
|
||||
}
|
||||
|
||||
export async function getByPicture(picture: string): Promise<UserDTO> {
|
||||
return mapEntity(await userRepo.findByPicture(picture))
|
||||
}
|
||||
|
||||
export async function updateFullName(
|
||||
id: string,
|
||||
options: UserUpdateFullNameOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
user = await userRepo.update({ id: user.id, fullName: options.fullName })
|
||||
await search.index(USER_SEARCH_INDEX).updateDocuments([
|
||||
{
|
||||
...user,
|
||||
fullName: user.fullName,
|
||||
},
|
||||
])
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function updateEmailRequest(
|
||||
id: string,
|
||||
options: UserUpdateEmailRequestOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
if (options.email === user.email) {
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
return mapEntity(user)
|
||||
} else {
|
||||
let usernameUnavailable = false
|
||||
try {
|
||||
await userRepo.findByUsername(options.email)
|
||||
usernameUnavailable = true
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
if (usernameUnavailable) {
|
||||
throw newError({ code: ErrorCode.UsernameUnavailable })
|
||||
}
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
emailUpdateToken: newHyphenlessUuid(),
|
||||
emailUpdateValue: options.email,
|
||||
})
|
||||
try {
|
||||
await sendTemplateMail('email-update', options.email, {
|
||||
'EMAIL': options.email,
|
||||
'UI_URL': getConfig().publicUIURL,
|
||||
'TOKEN': user.emailUpdateToken,
|
||||
})
|
||||
return mapEntity(user)
|
||||
} catch (error) {
|
||||
await userRepo.update({
|
||||
id,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
throw newError({ code: ErrorCode.InternalServerError, error })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateEmailConfirmation(
|
||||
options: UserUpdateEmailConfirmationOptions
|
||||
) {
|
||||
let user = await userRepo.findByEmailUpdateToken(options.token)
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
email: user.emailUpdateValue,
|
||||
username: user.emailUpdateValue,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
})
|
||||
await search.index(USER_SEARCH_INDEX).updateDocuments([
|
||||
{
|
||||
...user,
|
||||
email: user.email,
|
||||
username: user.email,
|
||||
emailUpdateToken: null,
|
||||
emailUpdateValue: null,
|
||||
},
|
||||
])
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function updatePassword(
|
||||
id: string,
|
||||
options: UserUpdatePasswordOptions
|
||||
): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
if (verifyPassword(options.currentPassword, user.passwordHash)) {
|
||||
user = await userRepo.update({
|
||||
id: user.id,
|
||||
passwordHash: hashPassword(options.newPassword),
|
||||
})
|
||||
return mapEntity(user)
|
||||
} else {
|
||||
throw newError({ code: ErrorCode.PasswordValidationFailed })
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePicture(
|
||||
id: string,
|
||||
path: string,
|
||||
contentType: string
|
||||
): Promise<UserDTO> {
|
||||
const picture = await fs.readFile(path, { encoding: 'base64' })
|
||||
const { id: userId } = await userRepo.findByID(id)
|
||||
const user = await userRepo.update({
|
||||
id: userId,
|
||||
picture: `data:${contentType};base64,${picture}`,
|
||||
})
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function deletePicture(id: string): Promise<UserDTO> {
|
||||
let user = await userRepo.findByID(id)
|
||||
user = await userRepo.update({ id: user.id, picture: null })
|
||||
return mapEntity(user)
|
||||
}
|
||||
|
||||
export async function deleteUser(id: string, options: UserDeleteOptions) {
|
||||
const user = await userRepo.findByID(id)
|
||||
if (verifyPassword(options.password, user.passwordHash)) {
|
||||
await userRepo.delete(user.id)
|
||||
await search.index(USER_SEARCH_INDEX).deleteDocuments([user.id])
|
||||
} else {
|
||||
throw newError({ code: ErrorCode.InvalidPassword })
|
||||
}
|
||||
}
|
||||
|
||||
export function mapEntity(entity: User): UserDTO {
|
||||
const user: UserDTO = {
|
||||
id: entity.id,
|
||||
email: entity.email,
|
||||
username: entity.username,
|
||||
fullName: entity.fullName,
|
||||
picture: entity.picture,
|
||||
pendingEmail: entity.emailUpdateValue,
|
||||
}
|
||||
Object.keys(user).forEach(
|
||||
(index) => !user[index] && user[index] !== undefined && delete user[index]
|
||||
)
|
||||
return user
|
||||
}
|
Reference in New Issue
Block a user