fix(api/webauthn): temporary disable mail before jsx-email lib upgrade
This commit is contained in:
parent
d461e53a00
commit
1cc3ce65eb
|
|
@ -1,178 +1,178 @@
|
||||||
import 'npm:iterator-polyfill'
|
import 'npm:iterator-polyfill'
|
||||||
import { define } from '../../../utils.ts'
|
import { define } from '../../../utils.ts'
|
||||||
// Polyfill AsyncIterator
|
// Polyfill AsyncIterator
|
||||||
import { db } from ':src/db/mod.ts'
|
// import { db } from ':src/db/mod.ts'
|
||||||
import { 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'
|
||||||
import { User } from '@cohabit/resources-manager/models'
|
// import { User } from '@cohabit/resources-manager/models'
|
||||||
import { sleep } from '@jotsr/delayed'
|
// import { sleep } from '@jotsr/delayed'
|
||||||
|
|
||||||
type MagicLinkInfos = {
|
// type MagicLinkInfos = {
|
||||||
remoteId: string
|
// remoteId: string
|
||||||
email: string
|
// email: string
|
||||||
timestamp: number
|
// timestamp: number
|
||||||
}
|
// }
|
||||||
|
|
||||||
export async function getUserByMail(email: string): Promise<User | undefined> {
|
// export async function getUserByMail(email: string): Promise<User | undefined> {
|
||||||
const [user] = await db.resource.user
|
// const [user] = await db.resource.user
|
||||||
.list((user) => user.mail === email)
|
// .list((user) => user.mail === email)
|
||||||
.take(1)
|
// .take(1)
|
||||||
.toArray()
|
// .toArray()
|
||||||
|
|
||||||
return user
|
// return user
|
||||||
}
|
// }
|
||||||
// export const handler = define.handlers({})
|
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 }
|
||||||
|
|
||||||
// check email before continue
|
// // check email before continue
|
||||||
if (!/\S+@\S+\.\S+/.test(email)) {
|
// if (!/\S+@\S+\.\S+/.test(email)) {
|
||||||
return respondApi('error', new SyntaxError('empty or invalid email'), 400)
|
// return respondApi('error', new SyntaxError('empty or invalid email'), 400)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const user = await getUserByMail(email)
|
// const user = await getUserByMail(email)
|
||||||
|
|
||||||
// generate magic link
|
// // generate magic link
|
||||||
const token = crypto.randomUUID()
|
// const token = crypto.randomUUID()
|
||||||
|
|
||||||
if (ctx.state.session === undefined) {
|
// if (ctx.state.session === undefined) {
|
||||||
return respondApi(
|
// return respondApi(
|
||||||
'error',
|
// 'error',
|
||||||
new Error('missing server session for current user'),
|
// new Error('missing server session for current user'),
|
||||||
500,
|
// 500,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
const remoteAddr = ctx.info.remoteAddr
|
// const remoteAddr = ctx.info.remoteAddr
|
||||||
if (!('hostname' in remoteAddr)) {
|
// if (!('hostname' in remoteAddr)) {
|
||||||
return respondApi('error', new Error('connection is not using http'), 500)
|
// 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, { remoteAddr }),
|
// remoteId: remoteId(request, { remoteAddr }),
|
||||||
timestamp: Date.now(),
|
// timestamp: Date.now(),
|
||||||
})
|
// })
|
||||||
|
|
||||||
// send mail to user
|
// // send mail to user
|
||||||
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') ?? remoteAddr.hostname
|
// const ip = request.headers.get('X-FORWARDED-FOR') ?? 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 })
|
||||||
} else {
|
// } else {
|
||||||
//! perform wait to prevent time attacks
|
// //! perform wait to prevent time attacks
|
||||||
await sleep(Math.random() * 5_000 + 2_000) //between 2s and 7s
|
// await sleep(Math.random() * 5_000 + 2_000) //between 2s and 7s
|
||||||
}
|
// }
|
||||||
return respondApi('success')
|
// return respondApi('success')
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('MAGIC_LINK_SENDING', error)
|
// console.error('MAGIC_LINK_SENDING', error)
|
||||||
return respondApi(
|
// return respondApi(
|
||||||
'error',
|
// 'error',
|
||||||
new Error(`unable to send mail to ${email}`),
|
// new Error(`unable to send mail to ${email}`),
|
||||||
500,
|
// 500,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
async GET(ctx) {
|
// async GET(ctx) {
|
||||||
const request = ctx.req
|
// 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')
|
||||||
|
|
||||||
// no token or sessionId
|
// // no token or sessionId
|
||||||
if (token === null || sessionId === null) {
|
// if (token === null || sessionId === null) {
|
||||||
return respondApi('error', 'no token or session provided', 400)
|
// return respondApi('error', 'no token or session provided', 400)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// set session if 3rd party cookies was blocked
|
// // set session if 3rd party cookies was blocked
|
||||||
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 || ctx.state.session === undefined) {
|
// if (ctx.state.session === null || ctx.state.session === undefined) {
|
||||||
return respondApi('error', 'no session datas', 401)
|
// return respondApi('error', 'no session datas', 401)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// wrong or timeout token
|
// // wrong or timeout token
|
||||||
const entry = ctx.state.session.get<MagicLinkInfos>(`MAGIC_LINK__${token}`)
|
// const entry = ctx.state.session.get<MagicLinkInfos>(`MAGIC_LINK__${token}`)
|
||||||
|
|
||||||
const lifespan = Date.now() - 10 * 60 * 1_000 // ten minutes
|
// const lifespan = Date.now() - 10 * 60 * 1_000 // ten minutes
|
||||||
|
|
||||||
if (entry === undefined || entry.timestamp < lifespan) {
|
// if (entry === undefined || entry.timestamp < lifespan) {
|
||||||
return respondApi('error', 'wrong token or timeout exceeded', 401)
|
// return respondApi('error', 'wrong token or timeout exceeded', 401)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const remoteAddr = ctx.info.remoteAddr
|
// const remoteAddr = ctx.info.remoteAddr
|
||||||
if (!('hostname' in remoteAddr)) {
|
// if (!('hostname' in remoteAddr)) {
|
||||||
return respondApi('error', new Error('connection is not using http'), 500)
|
// 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, { remoteAddr })) {
|
// 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)
|
||||||
|
|
||||||
if (redirect) {
|
// if (redirect) {
|
||||||
return Response.redirect(new URL(redirect, ctx.url.origin))
|
// return Response.redirect(new URL(redirect, ctx.url.origin))
|
||||||
}
|
// }
|
||||||
|
|
||||||
return respondApi('success', user)
|
// return respondApi('success', user)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return respondApi(
|
// return respondApi(
|
||||||
'error',
|
// 'error',
|
||||||
new Error(
|
// new Error(
|
||||||
'invalid id, use the same device/ip to query token and verify token',
|
// 'invalid id, use the same device/ip to query token and verify token',
|
||||||
),
|
// ),
|
||||||
401,
|
// 401,
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
function remoteId(
|
// function remoteId(
|
||||||
{ headers }: { headers: Headers },
|
// { headers }: { headers: Headers },
|
||||||
{ remoteAddr }: { remoteAddr: Deno.NetAddr },
|
// { 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')
|
||||||
|
|
||||||
if (forwardedAddress && forwardedProto) {
|
// if (forwardedAddress && forwardedProto) {
|
||||||
return `${forwardedProto}://${forwardedAddress}`
|
// return `${forwardedProto}://${forwardedAddress}`
|
||||||
}
|
// }
|
||||||
|
|
||||||
return `(${remoteAddr.transport}):${remoteAddr.hostname}:${remoteAddr.port}`
|
// return `(${remoteAddr.transport}):${remoteAddr.hostname}:${remoteAddr.port}`
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function sendMagicLink(
|
// async function sendMagicLink(
|
||||||
{ firstname, lastname, mail }: User,
|
// { firstname, lastname, mail }: User,
|
||||||
{ device, ip, endpoint }: { device?: string; ip?: string; endpoint: string },
|
// { device, ip, endpoint }: { device?: string; ip?: string; endpoint: string },
|
||||||
): Promise<void> {
|
// ): Promise<void> {
|
||||||
const message: Mail = {
|
// const message: Mail = {
|
||||||
from: Contact.expand('contact'),
|
// from: Contact.expand('contact'),
|
||||||
to: [Contact.fromString(`${firstname} ${lastname} <${mail}>`)],
|
// to: [Contact.fromString(`${firstname} ${lastname} <${mail}>`)],
|
||||||
subject: 'Lien de connection pour FabLab Coh@bit',
|
// subject: 'Lien de connection pour FabLab Coh@bit',
|
||||||
body: magicLinkTemplate.builder({
|
// body: magicLinkTemplate.builder({
|
||||||
device,
|
// device,
|
||||||
ip,
|
// ip,
|
||||||
endpoint,
|
// endpoint,
|
||||||
})!,
|
// })!,
|
||||||
options: {
|
// options: {
|
||||||
cc: [],
|
// cc: [],
|
||||||
cci: [],
|
// cci: [],
|
||||||
attachments: [],
|
// attachments: [],
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
await send(message)
|
// await send(message)
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue