107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import encryption from '@adonisjs/core/services/encryption'
|
|
import cache from '@adonisjs/cache/services/main'
|
|
import env from '#start/env'
|
|
import User from '#models/user'
|
|
import { CmpStrAsync } from 'cmpstr'
|
|
|
|
const cmp = CmpStrAsync.create().setMetric('levenshtein').setFlags('i')
|
|
|
|
export class AuthService {
|
|
async generateToken(email: string, userId: number, expiresIn: string) {
|
|
// Generate magic link token
|
|
const identifier = `${email}:${userId}`
|
|
const token = encryption.encrypt(identifier, expiresIn)
|
|
const magicLink = env.get('PUBLIC_AUTH_URL') + '/success?signature=' + token
|
|
|
|
// Generate code
|
|
const formattedOTP = Math.floor(Math.random() * 1000000)
|
|
.toString()
|
|
.padStart(6, '0')
|
|
await cache.set({
|
|
key: 'auth:otp:' + formattedOTP,
|
|
value: identifier,
|
|
ttl: expiresIn,
|
|
})
|
|
|
|
const emailTitle = `${formattedOTP} est votre code de vérification pour ${env.get('APP_NAME')}`
|
|
return {
|
|
emailTitle,
|
|
formattedOTP,
|
|
magicLink,
|
|
expiresIn,
|
|
email,
|
|
token,
|
|
}
|
|
}
|
|
|
|
parseIdentifier(id: string) {
|
|
const [email, userId] = id.split(':')
|
|
return {
|
|
email: email.toLowerCase(),
|
|
userId: parseInt(userId, 10),
|
|
}
|
|
}
|
|
|
|
async validateCode(code: string) {
|
|
// Validate code
|
|
const key = 'auth:otp:' + code
|
|
const id = await cache.get({ key })
|
|
if (!id) return { success: false, userId: null, email: null }
|
|
|
|
// Delete code from cache
|
|
await cache.delete({ key })
|
|
|
|
return {
|
|
success: true,
|
|
...this.parseIdentifier(id)
|
|
}
|
|
}
|
|
|
|
parseNameFromEmail(email: string): Promise<string> {
|
|
// Parse name from email
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
const [firstName, lastName] = email.split('@')[0].split('.')
|
|
resolve(`${firstName} ${lastName}`.toLowerCase())
|
|
} catch (error) {
|
|
reject(new Error('Invalid email format'))
|
|
}
|
|
})
|
|
}
|
|
|
|
findUserByEmail(email: string) {
|
|
return User.query().where('email', email).first()
|
|
}
|
|
|
|
async findUser(email: string) {
|
|
// Try to find user by email
|
|
let user: User | null | undefined = await this.findUserByEmail(email)
|
|
if (user) return user
|
|
|
|
// If not found, try to parse name from email and find user by name
|
|
const name = await this.parseNameFromEmail(email)
|
|
const users = await User.query()
|
|
// Select all users with no email
|
|
.whereNull('email')
|
|
const names = users.map((user) => `${user.firstName} ${user.lastName}`.toLowerCase())
|
|
|
|
// Search for similar names
|
|
const data = await cmp.searchAsync(name, names)
|
|
if (data.length === 0) {
|
|
console.warn(`No user found for email "${email}" or name "${name}"`)
|
|
return null
|
|
}
|
|
const source = data[0]
|
|
const { match: similarity } = await cmp.testAsync(source, name)
|
|
|
|
console.log(similarity)
|
|
// If similarity is high enough, return the user
|
|
if (similarity > 0.8) {
|
|
user = users.find((u) => `${u.firstName} ${u.lastName}`.toLowerCase() === source)
|
|
return user
|
|
}
|
|
console.warn(
|
|
`No user found for email "${email}" or name "${name}". Similarity: ${similarity} with source "${source}"`
|
|
)
|
|
}
|
|
}
|