Compare commits
8 commits
c6758721ec
...
5cb7142824
Author | SHA1 | Date | |
---|---|---|---|
Julien Oculi | 5cb7142824 | ||
Julien Oculi | cdcffff098 | ||
Julien Oculi | a18142277f | ||
Julien Oculi | ad34e6a3e4 | ||
Julien Oculi | 7568cb43a6 | ||
Julien Oculi | 3adc705e25 | ||
Julien Oculi | 79a6214f10 | ||
Julien Oculi | 4b228bfce5 |
|
@ -23,7 +23,7 @@ export default function LoginForm() {
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label='Utiliser une Passkey'
|
label='Utiliser une Passkey si disponible'
|
||||||
name='passkey'
|
name='passkey'
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked
|
checked
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'npm:iterator-polyfill'
|
||||||
import { FreshContext } from '$fresh/server.ts'
|
import { FreshContext } from '$fresh/server.ts'
|
||||||
import { Contact, type Mail, send } from '@cohabit/cohamail/mod.ts'
|
import { Contact, type Mail, send } from '@cohabit/cohamail/mod.ts'
|
||||||
import { magicLinkTemplate } from '@cohabit/cohamail/templates/mod.ts'
|
import { magicLinkTemplate } from '@cohabit/cohamail/templates/mod.ts'
|
||||||
import { SessionHandlers } from '../../../src/session/mod.ts'
|
import { SessionHandlers, SessionStore } from '../../../src/session/mod.ts'
|
||||||
import { respondApi } from '../../../src/utils.ts'
|
import { respondApi } from '../../../src/utils.ts'
|
||||||
import { sleep } from '@jotsr/delayed'
|
import { sleep } from '@jotsr/delayed'
|
||||||
import { User } from '@cohabit/ressources_manager/src/models/mod.ts'
|
import { User } from '@cohabit/ressources_manager/src/models/mod.ts'
|
||||||
|
@ -39,19 +39,21 @@ export const handler: SessionHandlers = {
|
||||||
// generate magic link
|
// generate magic link
|
||||||
const token = crypto.randomUUID()
|
const token = crypto.randomUUID()
|
||||||
const endpoint =
|
const endpoint =
|
||||||
`${ctx.url.origin}/api/magiclink?token=${token}&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(ctx),
|
remoteId: remoteId(request, ctx),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// send mail to user
|
// send mail to user
|
||||||
try {
|
try {
|
||||||
if (user) {
|
if (user) {
|
||||||
const ip = ctx.remoteAddr.hostname
|
// Get user ip through proxy else from tcp connection
|
||||||
|
const ip = request.headers.get('X-FORWARDED-FOR') ??
|
||||||
|
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 })
|
||||||
|
@ -69,14 +71,24 @@ export const handler: SessionHandlers = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async GET(_request, ctx) {
|
async GET(request, ctx) {
|
||||||
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')
|
||||||
|
|
||||||
// no token
|
// no token or sessionId
|
||||||
if (token === null) {
|
if (token === null || sessionId === null) {
|
||||||
return respondApi('error', 'no token provided', 400)
|
return respondApi('error', 'no token or session provided', 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set session if 3rd party cookies was blocked
|
||||||
|
ctx.state.session = ctx.state.session ?? SessionStore.getSession(sessionId)
|
||||||
|
|
||||||
|
// no session available
|
||||||
|
if (ctx.state.session === null) {
|
||||||
|
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}`)
|
||||||
|
|
||||||
|
@ -87,12 +99,12 @@ export const handler: SessionHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(ctx)) {
|
if (entry.remoteId === remoteId(request, ctx)) {
|
||||||
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.basePath))
|
return Response.redirect(new URL(redirect, ctx.url.origin))
|
||||||
}
|
}
|
||||||
|
|
||||||
return respondApi('success', user)
|
return respondApi('success', user)
|
||||||
|
@ -109,8 +121,16 @@ export const handler: SessionHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteId(
|
function remoteId(
|
||||||
|
{ headers }: { headers: Headers },
|
||||||
{ remoteAddr }: { remoteAddr: FreshContext['remoteAddr'] },
|
{ remoteAddr }: { remoteAddr: FreshContext['remoteAddr'] },
|
||||||
): string {
|
): string {
|
||||||
|
const forwardedAddress = headers.get('X-FORWARDED-FOR')
|
||||||
|
const forwardedProto = headers.get('X-FORWARDED-PROTO')
|
||||||
|
|
||||||
|
if (forwardedAddress && forwardedProto) {
|
||||||
|
return `${forwardedProto}://${forwardedAddress}`
|
||||||
|
}
|
||||||
|
|
||||||
return `(${remoteAddr.transport}):${remoteAddr.hostname}:${remoteAddr.port}`
|
return `(${remoteAddr.transport}):${remoteAddr.hostname}:${remoteAddr.port}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,8 +118,7 @@ export const handler: SessionHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { newCounter } = authenticationInfo
|
const { newCounter } = authenticationInfo
|
||||||
|
const newPasskey = { ...passkey, counter: newCounter }
|
||||||
passkey.counter = newCounter
|
|
||||||
|
|
||||||
// Update credential store
|
// Update credential store
|
||||||
const [credential] = await db.ressource.credential.list(
|
const [credential] = await db.ressource.credential.list(
|
||||||
|
@ -133,7 +132,7 @@ export const handler: SessionHandlers = {
|
||||||
|
|
||||||
// Save credential to db
|
// Save credential to db
|
||||||
await db.ressource.credential.set([
|
await db.ressource.credential.set([
|
||||||
credential.update({ store: passkey }),
|
credential.update({ store: newPasskey }),
|
||||||
])
|
])
|
||||||
|
|
||||||
// log user
|
// log user
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const handler: SessionHandlers = {
|
||||||
|
|
||||||
const options = await generateRegistrationOptions({
|
const options = await generateRegistrationOptions({
|
||||||
rpName: relyingParty.name,
|
rpName: relyingParty.name,
|
||||||
rpID: relyingParty.id,
|
rpID: relyingParty.origin,
|
||||||
userName: user.login,
|
userName: user.login,
|
||||||
attestationType: 'none',
|
attestationType: 'none',
|
||||||
excludeCredentials,
|
excludeCredentials,
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
export function getRelyingParty(url: string | URL) {
|
export function getRelyingParty(url: string | URL) {
|
||||||
url = new URL(url)
|
url = new URL(url)
|
||||||
|
|
||||||
|
// Prevent protocol mismatch when server is behind a proxy
|
||||||
|
if (!['localhost', '127.0.0.1', '0.0.0.0'].includes(url.hostname)) {
|
||||||
|
url.protocol = 'https'
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Human-readable title for your website
|
* Human-readable title for your website
|
||||||
|
|
Loading…
Reference in a new issue