feat(api/magiclink): reactivate magic link API

This commit is contained in:
Julien Oculi 2025-05-15 15:14:27 +02:00
parent e0869c5e24
commit 385143c859

View file

@ -1,10 +1,8 @@
import 'npm:iterator-polyfill' import 'npm:iterator-polyfill'
import { define } from '../../../utils.ts' import { define } from '../../../utils.ts'
// Polyfill AsyncIterator // Polyfill AsyncIterator
import { FreshContext } from 'fresh'
import { db } from ':src/db/mod.ts' import { db } from ':src/db/mod.ts'
import { SessionHandlers, SessionStore } from ':src/session/mod.ts' import { SessionStore } from ':src/session/mod.ts'
import { respondApi } from ':src/utils.ts' import { respondApi } from ':src/utils.ts'
import { Contact, type Mail, send } from '@cohabit/mailer' import { Contact, type Mail, send } from '@cohabit/mailer'
import { magicLinkTemplate } from '@cohabit/mailer/templates' import { magicLinkTemplate } from '@cohabit/mailer/templates'
@ -25,9 +23,8 @@ export async function getUserByMail(email: string): Promise<User | undefined> {
return user return user
} }
*/ // export const handler = define.handlers({})
export const handler = define.handlers({}) export const handler = define.handlers({
/*
async POST(ctx) { async POST(ctx) {
const request = ctx.req const request = ctx.req
const { email } = await request.json() as { email: string } const { email } = await request.json() as { email: string }
@ -41,13 +38,27 @@ export const handler = define.handlers({})
// generate magic link // generate magic link
const token = crypto.randomUUID() const token = crypto.randomUUID()
if (ctx.state.session === undefined) {
return respondApi(
'error',
new Error('missing server session for current user'),
500,
)
}
const remoteAddr = ctx.info.remoteAddr
if (!('hostname' in remoteAddr)) {
return respondApi('error', new Error('connection is not using http'), 500)
}
const endpoint = const endpoint =
`${ctx.url.origin}/api/magiclink?token=${token}&session=${ctx.state.session.uuid}&redirect=/profil` `${ctx.url.origin}/api/magiclink?token=${token}&session=${ctx.state.session.uuid}&redirect=/profil`
// save token to session // save token to session
ctx.state.session.flash<MagicLinkInfos>(`MAGIC_LINK__${token}`, { ctx.state.session.flash<MagicLinkInfos>(`MAGIC_LINK__${token}`, {
email, email,
remoteId: remoteId(request, ctx), remoteId: remoteId(request, { remoteAddr }),
timestamp: Date.now(), timestamp: Date.now(),
}) })
@ -55,8 +66,7 @@ export const handler = define.handlers({})
try { try {
if (user) { if (user) {
// Get user ip through proxy else from tcp connection // Get user ip through proxy else from tcp connection
const ip = request.headers.get('X-FORWARDED-FOR') ?? const ip = request.headers.get('X-FORWARDED-FOR') ?? remoteAddr.hostname
ctx.remoteAddr.hostname
const device = request.headers.get('Sec-Ch-Ua-Platform') ?? undefined const device = request.headers.get('Sec-Ch-Ua-Platform') ?? undefined
await sendMagicLink(user, { device, ip, endpoint }) await sendMagicLink(user, { device, ip, endpoint })
@ -74,7 +84,9 @@ export const handler = define.handlers({})
) )
} }
}, },
async GET(request, ctx) { async GET(ctx) {
const request = ctx.req
const token = ctx.url.searchParams.get('token') const token = ctx.url.searchParams.get('token')
const redirect = ctx.url.searchParams.get('redirect') const redirect = ctx.url.searchParams.get('redirect')
const sessionId = ctx.url.searchParams.get('session') const sessionId = ctx.url.searchParams.get('session')
@ -88,7 +100,7 @@ export const handler = define.handlers({})
ctx.state.session = ctx.state.session ?? SessionStore.getSession(sessionId) ctx.state.session = ctx.state.session ?? SessionStore.getSession(sessionId)
// no session available // no session available
if (ctx.state.session === null) { if (ctx.state.session === null || ctx.state.session === undefined) {
return respondApi('error', 'no session datas', 401) return respondApi('error', 'no session datas', 401)
} }
@ -101,8 +113,13 @@ export const handler = define.handlers({})
return respondApi('error', 'wrong token or timeout exceeded', 401) return respondApi('error', 'wrong token or timeout exceeded', 401)
} }
const remoteAddr = ctx.info.remoteAddr
if (!('hostname' in remoteAddr)) {
return respondApi('error', new Error('connection is not using http'), 500)
}
// check remote id (same user/machine that has query the token) // check remote id (same user/machine that has query the token)
if (entry.remoteId === remoteId(request, ctx)) { if (entry.remoteId === remoteId(request, { remoteAddr })) {
const user = await getUserByMail(entry.email) const user = await getUserByMail(entry.email)
ctx.state.session.set('user', user) ctx.state.session.set('user', user)
@ -122,10 +139,10 @@ export const handler = define.handlers({})
) )
}, },
}) })
/*
function remoteId( function remoteId(
{ headers }: { headers: Headers }, { headers }: { headers: Headers },
{ remoteAddr }: { remoteAddr: FreshContext['remoteAddr'] }, { remoteAddr }: { remoteAddr: Deno.NetAddr },
): string { ): string {
const forwardedAddress = headers.get('X-FORWARDED-FOR') const forwardedAddress = headers.get('X-FORWARDED-FOR')
const forwardedProto = headers.get('X-FORWARDED-PROTO') const forwardedProto = headers.get('X-FORWARDED-PROTO')