197 lines
5.5 KiB
TypeScript
197 lines
5.5 KiB
TypeScript
import type { HttpContext } from '@adonisjs/core/http'
|
|
import {
|
|
credentialsValidator,
|
|
registerValidator,
|
|
requestLoginValidator,
|
|
verifyCodeValidator,
|
|
} from '#validators/auth'
|
|
import mail from '@adonisjs/mail/services/main'
|
|
import { AuthService } from '#services/auth_service'
|
|
import { inject } from '@adonisjs/core'
|
|
import app from '@adonisjs/core/services/app'
|
|
import env from '#start/env'
|
|
import User from '#models/user'
|
|
import redis from '@adonisjs/redis/services/main'
|
|
|
|
@inject()
|
|
export default class AuthController {
|
|
constructor(private authService: AuthService) {}
|
|
|
|
// POST /auth/request
|
|
async requestLogin({ request, response, captcha }: HttpContext) {
|
|
// Validate captcha
|
|
if (app.inProduction) {
|
|
const validateResult = await (captcha.use('turnstile') as any).validate()
|
|
if (!validateResult.success) {
|
|
return response.badRequest({
|
|
success: false,
|
|
message: 'Veuillez valider le captcha',
|
|
error: validateResult.errorCodes,
|
|
})
|
|
}
|
|
}
|
|
|
|
const { email } = await request.validateUsing(requestLoginValidator)
|
|
|
|
// Generate token
|
|
const expiresIn = '15 minutes'
|
|
const payload = await this.authService.generateCode(email, expiresIn)
|
|
|
|
await mail
|
|
.send((message) => {
|
|
message
|
|
.from(env.get('MAIL_FROM')!)
|
|
.to(email)
|
|
.subject(payload.emailTitle)
|
|
.htmlView('mails/auth', payload)
|
|
.textView('mails/auth-fallback', payload)
|
|
})
|
|
// TODO: Handle email sending errors
|
|
.then(console.log)
|
|
.catch(console.error)
|
|
|
|
return {
|
|
success: true,
|
|
}
|
|
}
|
|
|
|
// POST /auth/verify
|
|
async verifyCode({ request, response, auth }: HttpContext) {
|
|
// Validate code
|
|
const { code } = await request.validateUsing(verifyCodeValidator)
|
|
const { success, email } = await this.authService.validateCode(code)
|
|
if (!success) {
|
|
return response.badRequest({
|
|
success: false,
|
|
message: 'Code de vérification invalide',
|
|
})
|
|
}
|
|
|
|
const user = await User.findBy('email', email)
|
|
if (!user) {
|
|
// If the user does not exist, return a token for registration
|
|
const expiresIn = '1 hour'
|
|
const { token, email: userEmail } = this.authService.generateToken(email, expiresIn)
|
|
return {
|
|
token,
|
|
email: userEmail,
|
|
success: true,
|
|
}
|
|
}
|
|
|
|
await auth.use('web').login(user, true) // true for remember me
|
|
return {
|
|
success: true,
|
|
}
|
|
}
|
|
|
|
// GET /auth/autocomplete
|
|
async listNames({ request }: HttpContext) {
|
|
const { className } = request.qs()
|
|
if (!className) {
|
|
return {
|
|
success: false,
|
|
message: 'Veuillez spécifier une classe',
|
|
}
|
|
}
|
|
|
|
// TODO: const lastYear = DateTime.now().minus({ years: 1 })
|
|
return User.query()
|
|
.select('firstName', 'lastName', 'id')
|
|
.where('className', className)
|
|
.orderBy('lastName', 'asc')
|
|
.then((users) => {
|
|
return {
|
|
success: true,
|
|
data: users.map((user) => ({
|
|
value: `${user.firstName}::${user.lastName}`,
|
|
label: user.fullName,
|
|
userId: user.id,
|
|
})),
|
|
}
|
|
})
|
|
}
|
|
|
|
// POST /auth/register
|
|
async register({ request, response, auth }: HttpContext) {
|
|
const { userId, className, token } = await request.validateUsing(registerValidator)
|
|
|
|
// Validate token
|
|
const { success, email } = this.authService.validateToken(token)
|
|
if (!success || !email) {
|
|
return response.badRequest({
|
|
success: false,
|
|
message: 'Votre lien de connexion est invalide ou a expiré.',
|
|
})
|
|
}
|
|
|
|
const user = await User.query().where('id', userId).where('className', className).first()
|
|
if (!user) {
|
|
return response.badRequest({
|
|
success: false,
|
|
message: 'Utilisateur non trouvé. Veuillez vérifier vos informations.',
|
|
})
|
|
}
|
|
|
|
if (user.email && user.email.toLowerCase() !== email.toLowerCase()) {
|
|
return response.badRequest({
|
|
success: false,
|
|
message:
|
|
"L'email associé à votre compte ne correspond pas à celui utilisé pour la connexion.",
|
|
})
|
|
}
|
|
|
|
user.email = email.toLowerCase() // Update email if necessary
|
|
await user.save()
|
|
|
|
// Perform login
|
|
await auth.use('web').login(user, true) // true for remember me
|
|
return {
|
|
success: true,
|
|
}
|
|
}
|
|
|
|
// POST /auth/logout
|
|
async logout({ auth }: HttpContext) {
|
|
await auth.use('web').logout()
|
|
return {
|
|
success: true,
|
|
}
|
|
}
|
|
|
|
// POST /auth/test
|
|
public async testCredentials({ request, auth }: HttpContext) {
|
|
const { username, password } = await request.validateUsing(credentialsValidator)
|
|
await redis.publish(
|
|
'jobs_queue',
|
|
JSON.stringify({
|
|
type: 5, // Test credentials
|
|
bj_username: username,
|
|
bj_password: password,
|
|
user_id: auth.user!.id,
|
|
class_name: env.get('DEFAULT_CLASS_NAME'),
|
|
})
|
|
)
|
|
|
|
return { success: true, message: 'Testing credentials...' }
|
|
}
|
|
|
|
// GET /auth/status
|
|
public async status({ auth }: HttpContext) {
|
|
const redisKey = `auth_success_${auth.user!.id}`
|
|
return { success: true, data: { authenticated: await redis.get(redisKey) } }
|
|
}
|
|
|
|
// TODO: Magic link login
|
|
// magicLink({ }: HttpContext) {
|
|
// // Validate signed url (adonis)
|
|
// // + login current device
|
|
// // + SSE to notify other devices (and login)
|
|
// }
|
|
|
|
// listen({ }: HttpContext) {
|
|
// // Listen for SSE events
|
|
// // Need an AUTH token to connect
|
|
// // AUTH token sent to client in requestLogin
|
|
// }
|
|
}
|