Compare commits
No commits in common. "1082f29143cfe4fff62dbf4c220c904b4447e3b3" and "c724dac0860b7ccccc5082c04ba81bfd0a6bd8a8" have entirely different histories.
1082f29143
...
c724dac086
6 changed files with 39 additions and 118 deletions
|
|
@ -9,7 +9,7 @@ import User from '#models/user'
|
||||||
|
|
||||||
@inject()
|
@inject()
|
||||||
export default class AuthController {
|
export default class AuthController {
|
||||||
constructor(private authService: AuthService) {}
|
constructor(private authService: AuthService) { }
|
||||||
|
|
||||||
// POST /auth/request
|
// POST /auth/request
|
||||||
async requestLogin({ request, response, captcha }: HttpContext) {
|
async requestLogin({ request, response, captcha }: HttpContext) {
|
||||||
|
|
@ -31,6 +31,7 @@ export default class AuthController {
|
||||||
const expiresIn = '15 minutes'
|
const expiresIn = '15 minutes'
|
||||||
const payload = await this.authService.generateCode(email, expiresIn)
|
const payload = await this.authService.generateCode(email, expiresIn)
|
||||||
|
|
||||||
|
// Send email
|
||||||
await mail
|
await mail
|
||||||
.send((message) => {
|
.send((message) => {
|
||||||
message
|
message
|
||||||
|
|
@ -61,6 +62,7 @@ export default class AuthController {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find user by id
|
||||||
const user = await User.findBy('email', email)
|
const user = await User.findBy('email', email)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// If the user does not exist, return a token for registration
|
// If the user does not exist, return a token for registration
|
||||||
|
|
@ -73,86 +75,53 @@ export default class AuthController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform login
|
||||||
await auth.use('web').login(user, true) // true for remember me
|
await auth.use('web').login(user, true) // true for remember me
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /auth/autocomplete
|
|
||||||
async listNames({ request }: HttpContext) {
|
|
||||||
const { className } = request.qs()
|
|
||||||
if (!className) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: 'Veuillez spécifier une classe',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return User.query()
|
|
||||||
.select('firstName', 'lastName')
|
|
||||||
.where('className', className)
|
|
||||||
.orderBy('lastName', 'asc')
|
|
||||||
.then((users) => {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: users.map((user) => ({
|
|
||||||
value: `${user.firstName}::${user.lastName}`,
|
|
||||||
label: user.fullName,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /auth/register
|
// POST /auth/register
|
||||||
async register({ request, response, auth }: HttpContext) {
|
async register({ request, response, auth }: HttpContext) {
|
||||||
const { name, className, token } = await request.validateUsing(registerValidator)
|
const { firstName, lastName, className, token } = await request.validateUsing(registerValidator)
|
||||||
|
|
||||||
// Validate token
|
// Validate token
|
||||||
const { success, email } = this.authService.validateToken(token)
|
const { success, email } = this.authService.validateToken(token)
|
||||||
const [firstName, lastName] = name.split('::')
|
if (!success || !email) {
|
||||||
if (!success || !email || !firstName || !lastName) {
|
|
||||||
return response.badRequest({
|
return response.badRequest({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Votre lien de connexion est invalide ou a expiré.',
|
message: 'Votre lien de connexion est invalide ou a expiré.',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.query()
|
// Check if user already exists
|
||||||
.where('firstName', firstName)
|
const existingUser = await User.findBy('email', email)
|
||||||
.where('lastName', lastName)
|
if (existingUser) {
|
||||||
.where('className', className)
|
// If user already exists, perform login
|
||||||
.first()
|
await auth.use('web').login(existingUser, true) // true for remember me
|
||||||
if (!user) {
|
return {
|
||||||
return response.badRequest({
|
success: true,
|
||||||
success: false,
|
user: existingUser,
|
||||||
message: 'Utilisateur non trouvé. Veuillez vérifier vos informations.',
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.email && user.email.toLowerCase() !== email.toLowerCase()) {
|
// TODO: Check if className is allowed (else redirect for account giving)
|
||||||
return response.badRequest({
|
|
||||||
success: false,
|
// TODO: Rewrite user creation (NEVER CREATE USER - use string similarity)
|
||||||
message:
|
// Create new user
|
||||||
"L'email associé à votre compte ne correspond pas à celui utilisé pour la connexion.",
|
const user = await User.create({
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
className,
|
||||||
|
email
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
user.email = email.toLowerCase() // Update email if necessary
|
|
||||||
await user.save()
|
|
||||||
|
|
||||||
// Perform login
|
// Perform login
|
||||||
await auth.use('web').login(user, true) // true for remember me
|
await auth.use('web').login(user, true) // true for remember me
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
}
|
user
|
||||||
}
|
|
||||||
|
|
||||||
// POST /auth/logout
|
|
||||||
async logout({ auth }: HttpContext) {
|
|
||||||
await auth.use('web').logout()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,17 +43,13 @@ export default class CollesController {
|
||||||
.preload('subject')
|
.preload('subject')
|
||||||
.preload('room')
|
.preload('room')
|
||||||
.first()
|
.first()
|
||||||
|
// TODO: Include BJID and BJSecret !
|
||||||
if (!colle) {
|
if (!colle) {
|
||||||
return response.notFound({ message: 'Colle not found' })
|
return response.notFound({ message: 'Colle not found' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: colle,
|
||||||
...colle.serialize(),
|
|
||||||
bjid: colle.bjid,
|
|
||||||
bjsecret: colle.bjsecret,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,14 +67,6 @@ export default class CollesController {
|
||||||
return response.badRequest({ message: 'Invalid date format' })
|
return response.badRequest({ message: 'Invalid date format' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the content and comment for rendering
|
|
||||||
if (payload.comment) {
|
|
||||||
payload.comment = this.service.prepareHtmlForRendering(payload.comment)
|
|
||||||
}
|
|
||||||
if (payload.content) {
|
|
||||||
payload.content = this.service.prepareHtmlForRendering(payload.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
const colleData = {
|
const colleData = {
|
||||||
studentId: student.id,
|
studentId: student.id,
|
||||||
examinerId: examiner.id,
|
examinerId: examiner.id,
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ export default class User extends BaseModel {
|
||||||
@column()
|
@column()
|
||||||
declare className: string
|
declare className: string
|
||||||
|
|
||||||
@column({ serializeAs: null})
|
@column()
|
||||||
declare firstName: string
|
declare firstName: string
|
||||||
|
|
||||||
@column({ serializeAs: null})
|
@column()
|
||||||
declare lastName: string
|
declare lastName: string
|
||||||
|
|
||||||
@computed()
|
@computed()
|
||||||
|
|
@ -22,7 +22,7 @@ export default class User extends BaseModel {
|
||||||
return `${this.firstName} ${this.lastName}`
|
return `${this.firstName} ${this.lastName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@column({ serializeAs: null })
|
@column()
|
||||||
declare email: string
|
declare email: string
|
||||||
|
|
||||||
@column.dateTime({ autoCreate: true })
|
@column.dateTime({ autoCreate: true })
|
||||||
|
|
|
||||||
|
|
@ -92,45 +92,4 @@ export class ColleService {
|
||||||
favoriteColles,
|
favoriteColles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-process HTML content to render LaTeX
|
|
||||||
private removeTrailingLines(htmlString: string) {
|
|
||||||
return htmlString.replace(/(<br\s*\/?>\s*)+$/gi, '').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractLatexImages(html: string) {
|
|
||||||
const imgRegex = /<img[^>]+src="(https:\/\/latex\.codecogs\.com\/gif\.latex\?(=?.*?))"[^>]*>/g
|
|
||||||
let parts = []
|
|
||||||
let latexMatches: string[] = []
|
|
||||||
let lastIndex = 0
|
|
||||||
|
|
||||||
html.replace(imgRegex, (match, _, latex, index) => {
|
|
||||||
parts.push(html.slice(lastIndex, index)) // Add HTML before image
|
|
||||||
latexMatches.push(decodeURIComponent(latex)) // Extract and decode LaTeX
|
|
||||||
lastIndex = index + match.length
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
parts.push(html.slice(lastIndex)) // Add remaining HTML after last image
|
|
||||||
|
|
||||||
return { parts, latexMatches }
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareHtmlForRendering(html: string) {
|
|
||||||
const strippedHtml = this.removeTrailingLines(html)
|
|
||||||
const { parts, latexMatches } = this.extractLatexImages(strippedHtml)
|
|
||||||
|
|
||||||
const outputHtml = parts
|
|
||||||
.map((part, i) => {
|
|
||||||
if (!latexMatches[i]) {
|
|
||||||
return part
|
|
||||||
}
|
|
||||||
return `${part}$$${latexMatches[i]}$$`
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
// Remove all "\," from string
|
|
||||||
const regex = /\\,/g
|
|
||||||
return outputHtml.replace(regex, ' ')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,16 @@ export const verifyCodeValidator = vine.compile(
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function toTitleCase(value: string) {
|
||||||
|
return value.replace(/\w\S*/g, (txt) => {
|
||||||
|
return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const registerValidator = vine.compile(
|
export const registerValidator = vine.compile(
|
||||||
vine.object({
|
vine.object({
|
||||||
name: vine.string().minLength(2).maxLength(50).trim(),
|
firstName: vine.string().minLength(2).maxLength(50).trim().transform(toTitleCase),
|
||||||
|
lastName: vine.string().minLength(2).maxLength(50).trim().toUpperCase(),
|
||||||
className: vine.string().minLength(2).maxLength(50),
|
className: vine.string().minLength(2).maxLength(50),
|
||||||
token: vine.string(),
|
token: vine.string(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ router.group(() => {
|
||||||
router.post('/auth/request', [AuthController, 'requestLogin']).use(authThrottle)
|
router.post('/auth/request', [AuthController, 'requestLogin']).use(authThrottle)
|
||||||
router.post('/auth/verify', [AuthController, 'verifyCode']).use(throttle)
|
router.post('/auth/verify', [AuthController, 'verifyCode']).use(throttle)
|
||||||
router.post('/auth/register', [AuthController, 'register']).use(throttle)
|
router.post('/auth/register', [AuthController, 'register']).use(throttle)
|
||||||
router.post('/auth/logout', [AuthController, 'logout'])
|
|
||||||
router.get('/auth/autocomplete', [AuthController, 'listNames']).use(throttle)
|
|
||||||
// TODO: Magic link login
|
// TODO: Magic link login
|
||||||
// router.get('/auth/magic-link', 'AuthController.magicLink').use(throttle)
|
// router.get('/auth/magic-link', 'AuthController.magicLink').use(throttle)
|
||||||
// router.get('/auth/listen', 'AuthController.listen')
|
// router.get('/auth/listen', 'AuthController.listen')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue