fix(island): 🐛 transpilation cause Suspense
to broke promise as children
This commit is contained in:
parent
f24143964f
commit
c6eb438314
|
@ -13,8 +13,7 @@ export function AutoGrid(
|
||||||
<div
|
<div
|
||||||
class='components__auto_grid'
|
class='components__auto_grid'
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns:
|
gridTemplateColumns: `repeat(auto-fit, minmax(${columnWidth}, 1fr));`,
|
||||||
`repeat(auto-fit, minmax(${columnWidth}, 1fr));`,
|
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -69,9 +69,7 @@ export function BlogPost(
|
||||||
<div class='components__blog_post__infos'>
|
<div class='components__blog_post__infos'>
|
||||||
<span>{`Visibilité : ${options.visibility}`}</span>
|
<span>{`Visibilité : ${options.visibility}`}</span>
|
||||||
<span>
|
<span>
|
||||||
{`Date de délivrance : ${
|
{`Date de délivrance : ${new Date(options.dueDate).toLocaleString()}`}
|
||||||
new Date(options.dueDate).toLocaleString()
|
|
||||||
}`}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class='components__blog_post__description'>
|
<div class='components__blog_post__description'>
|
||||||
|
@ -90,9 +88,7 @@ export function BlogPost(
|
||||||
function NewsTags({ tags }: Pick<BlogProps, 'tags'>) {
|
function NewsTags({ tags }: Pick<BlogProps, 'tags'>) {
|
||||||
return (
|
return (
|
||||||
<div class='components__blog_block__tags'>
|
<div class='components__blog_block__tags'>
|
||||||
{tags
|
{tags ? tags.map((tag) => <span>{tag}</span>) : <span>Aucun tag</span>}
|
||||||
? tags.map((tag) => <span>{tag}</span>)
|
|
||||||
: <span>Aucun tag</span>}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,26 @@ import { Ensure } from ':types'
|
||||||
import { JSX } from 'preact'
|
import { JSX } from 'preact'
|
||||||
|
|
||||||
export type PictureProps =
|
export type PictureProps =
|
||||||
& { formats: string[] }
|
& { formats: string[] }
|
||||||
& Ensure<JSX.HTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'loading'>
|
& Ensure<JSX.HTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'loading'>
|
||||||
|
|
||||||
export function Picture(
|
export function Picture(
|
||||||
{ formats, src, ...props }: PictureProps,
|
{ formats, src, ...props }: PictureProps,
|
||||||
) {
|
) {
|
||||||
const groups = unwrapSignalOrValue(src)?.match(/(?<path>.*)(?<ext>\.\w+)/)
|
const groups = unwrapSignalOrValue(src)?.match(/(?<path>.*)(?<ext>\.\w+)/)
|
||||||
?.groups
|
?.groups
|
||||||
if (groups === undefined) {
|
if (groups === undefined) {
|
||||||
throw new SyntaxError(`unable to parse path of "${src.valueOf()}"`)
|
throw new SyntaxError(`unable to parse path of "${src.valueOf()}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { path } = groups
|
const { path } = groups
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<picture>
|
<picture>
|
||||||
{formats.map((format) => (
|
{formats.map((format) => (
|
||||||
<source type={`image/${format}`} srcset={`${path}.${format}`} />
|
<source type={`image/${format}`} srcset={`${path}.${format}`} />
|
||||||
))}
|
))}
|
||||||
<img src={src} {...props} />
|
<img src={src} {...props} />
|
||||||
</picture>
|
</picture>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
10
dev.ts
10
dev.ts
|
@ -6,9 +6,9 @@ import config from './fresh.config.ts'
|
||||||
import '$std/dotenv/load.ts'
|
import '$std/dotenv/load.ts'
|
||||||
|
|
||||||
await dev(import.meta.url, './main.ts', {
|
await dev(import.meta.url, './main.ts', {
|
||||||
...config,
|
...config,
|
||||||
server: {
|
server: {
|
||||||
cert: await Deno.readTextFile('./cert/localhost.pem'),
|
cert: await Deno.readTextFile('./cert/localhost.pem'),
|
||||||
key: await Deno.readTextFile('./cert/localhost-key.pem'),
|
key: await Deno.readTextFile('./cert/localhost-key.pem'),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,56 +3,56 @@ import CardList from ':islands/CardList.tsx'
|
||||||
import type { Ref } from 'preact'
|
import type { Ref } from 'preact'
|
||||||
|
|
||||||
export default function BlogCardList(
|
export default function BlogCardList(
|
||||||
{ limit, usePlaceholder, useObserver }: {
|
{ limit, usePlaceholder, useObserver }: {
|
||||||
limit?: number
|
limit?: number
|
||||||
usePlaceholder?: boolean
|
usePlaceholder?: boolean
|
||||||
useObserver?: boolean
|
useObserver?: boolean
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (usePlaceholder) {
|
if (usePlaceholder) {
|
||||||
return (
|
return (
|
||||||
<CardList
|
<CardList
|
||||||
apiRoute='news/fetchAll'
|
apiRoute='news/fetchAll'
|
||||||
builder={builder}
|
builder={builder}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
placeholder={Placeholder}
|
placeholder={Placeholder}
|
||||||
fallback={Fallback}
|
fallback={Fallback}
|
||||||
useObserver={useObserver}
|
useObserver={useObserver}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardList
|
<CardList
|
||||||
apiRoute='news/fetchAll'
|
apiRoute='news/fetchAll'
|
||||||
builder={builder}
|
builder={builder}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function builder(news: BlogProps) {
|
function builder(news: BlogProps) {
|
||||||
return BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) })
|
return BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) })
|
||||||
}
|
}
|
||||||
|
|
||||||
function Placeholder({ ref }: { ref?: Ref<HTMLDivElement> | undefined }) {
|
function Placeholder({ ref }: { ref?: Ref<HTMLDivElement> | undefined }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class='components__blog_block components__blog_block--card components__blog_block--placeholder'
|
class='components__blog_block components__blog_block--card components__blog_block--placeholder'
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<h3>Chargement ...</h3>
|
<h3>Chargement ...</h3>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Fallback() {
|
function Fallback() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class='components__blog_block components__blog_block--card components__blog_block--fallback'
|
class='components__blog_block components__blog_block--card components__blog_block--fallback'
|
||||||
inert
|
inert
|
||||||
>
|
>
|
||||||
<h3>Pas de news disponible</h3>
|
<h3>Pas de news disponible</h3>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,97 +7,96 @@ import { useEffect, useRef } from 'preact/hooks'
|
||||||
export type Builder<T> = (props: T) => JSX.Element
|
export type Builder<T> = (props: T) => JSX.Element
|
||||||
|
|
||||||
export type CardListProps<ApiResponse, RefType = null> = {
|
export type CardListProps<ApiResponse, RefType = null> = {
|
||||||
apiRoute: string
|
apiRoute: string
|
||||||
builder: Builder<ApiResponse>
|
builder: Builder<ApiResponse>
|
||||||
limit?: number
|
limit?: number
|
||||||
} | {
|
} | {
|
||||||
apiRoute: string
|
apiRoute: string
|
||||||
builder: Builder<ApiResponse>
|
builder: Builder<ApiResponse>
|
||||||
limit?: number
|
limit?: number
|
||||||
useObserver?: boolean
|
useObserver?: boolean
|
||||||
placeholder: ({ ref }: { ref: Ref<RefType> | undefined }) => JSX.Element
|
placeholder: ({ ref }: { ref: Ref<RefType> | undefined }) => JSX.Element
|
||||||
fallback: Fallback
|
fallback: Fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CardList<ApiResponse, RefType = null>(
|
export default function CardList<ApiResponse, RefType = null>(
|
||||||
{ limit, builder, apiRoute, ...props }: CardListProps<ApiResponse, RefType>,
|
{ limit, builder, apiRoute, ...props }: CardListProps<ApiResponse, RefType>,
|
||||||
) {
|
) {
|
||||||
const list: Signal<JSX.Element[]> = useSignal<JSX.Element[]>([])
|
const list: Signal<JSX.Element[]> = useSignal<JSX.Element[]>([])
|
||||||
const ac = new AbortController()
|
const ac = new AbortController()
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
|
|
||||||
const useObserver = 'useObserver' in props ? props.useObserver : false
|
const useObserver = 'useObserver' in props ? props.useObserver : false
|
||||||
const placeholder = 'placeholder' in props ? props.placeholder : false
|
const placeholder = 'placeholder' in props ? props.placeholder : false
|
||||||
const fallback = 'fallback' in props ? props.fallback : false
|
const fallback = 'fallback' in props ? props.fallback : false
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && useObserver) {
|
if (ref.current && useObserver) {
|
||||||
const observer = new IntersectionObserver(([entry]) => {
|
const observer = new IntersectionObserver(([entry]) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
fillList(list, builder, apiRoute, { limit, ac })
|
fillList(list, builder, apiRoute, { limit, ac })
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
rootMargin: '300px',
|
rootMargin: '300px',
|
||||||
})
|
})
|
||||||
observer.observe(ref.current)
|
observer.observe(ref.current)
|
||||||
} else {
|
} else {
|
||||||
fillList(list, builder, apiRoute, { limit, ac })
|
fillList(list, builder, apiRoute, { limit, ac })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (limit && placeholder && fallback) {
|
if (limit && placeholder && fallback) {
|
||||||
const placeholders = Array
|
const placeholders = Array
|
||||||
.from({ length: limit })
|
.from({ length: limit })
|
||||||
.map((_, index) => (
|
.map((_, index) => (
|
||||||
<Suspense
|
<Suspense
|
||||||
loader={placeholder({ ref: index === 0 ? ref : undefined })}
|
loader={placeholder({ ref: index === 0 ? ref : undefined })}
|
||||||
fallback={fallback}
|
fallback={fallback}
|
||||||
signal={ac.signal}
|
signal={ac.signal}
|
||||||
>
|
value={updateFromList(list, index)}
|
||||||
{updateFromList(list, index)}
|
/>
|
||||||
</Suspense>
|
))
|
||||||
))
|
return <>{placeholders}</>
|
||||||
return <>{placeholders}</>
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return <>{list}</>
|
return <>{list}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillList<ApiResponse>(
|
function fillList<ApiResponse>(
|
||||||
list: Signal<JSX.Element[]>,
|
list: Signal<JSX.Element[]>,
|
||||||
builder: Builder<ApiResponse>,
|
builder: Builder<ApiResponse>,
|
||||||
apiRoute: string,
|
apiRoute: string,
|
||||||
{ limit, ac }: { limit?: number; ac?: AbortController },
|
{ limit, ac }: { limit?: number; ac?: AbortController },
|
||||||
) {
|
) {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const propsList = requestApiStream<void, ApiResponse>(
|
const propsList = requestApiStream<void, ApiResponse>(
|
||||||
apiRoute,
|
apiRoute,
|
||||||
'GET',
|
'GET',
|
||||||
)
|
)
|
||||||
|
|
||||||
for await (const props of propsList) {
|
for await (const props of propsList) {
|
||||||
list.value = [
|
list.value = [
|
||||||
...list.value,
|
...list.value,
|
||||||
builder(props),
|
builder(props),
|
||||||
]
|
]
|
||||||
if (limit && list.value.length >= limit) break
|
if (limit && list.value.length >= limit) break
|
||||||
}
|
}
|
||||||
ac?.abort()
|
ac?.abort()
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFromList(
|
function updateFromList(
|
||||||
list: Signal<JSX.Element[]>,
|
list: Signal<JSX.Element[]>,
|
||||||
index: number,
|
index: number,
|
||||||
): Promise<JSX.Element> {
|
): Promise<JSX.Element> {
|
||||||
const { promise, resolve } = Promise.withResolvers<JSX.Element>()
|
const { promise, resolve } = Promise.withResolvers<JSX.Element>()
|
||||||
list.subscribe((value: JSX.Element[]) => {
|
list.subscribe((value: JSX.Element[]) => {
|
||||||
const selected = value.at(index)
|
const selected = value.at(index)
|
||||||
if (selected) {
|
if (selected) {
|
||||||
resolve(selected)
|
resolve(selected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,58 +3,58 @@ import CardList from ':islands/CardList.tsx'
|
||||||
import type { Ref } from 'preact'
|
import type { Ref } from 'preact'
|
||||||
|
|
||||||
export default function MemberCardList(
|
export default function MemberCardList(
|
||||||
{ limit, filters, usePlaceholder, useObserver }: {
|
{ limit, filters, usePlaceholder, useObserver }: {
|
||||||
filters?: [string, string][]
|
filters?: [string, string][]
|
||||||
limit?: number
|
limit?: number
|
||||||
usePlaceholder?: boolean
|
usePlaceholder?: boolean
|
||||||
useObserver?: boolean
|
useObserver?: boolean
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const query = new URL('members/fetchAll', 'https://null/')
|
const query = new URL('members/fetchAll', 'https://null/')
|
||||||
filters?.forEach((filter) => query.searchParams.set(...filter))
|
filters?.forEach((filter) => query.searchParams.set(...filter))
|
||||||
|
|
||||||
const apiRoute = `${query.pathname}${query.search}`
|
const apiRoute = `${query.pathname}${query.search}`
|
||||||
|
|
||||||
if (usePlaceholder) {
|
if (usePlaceholder) {
|
||||||
return (
|
return (
|
||||||
<CardList
|
<CardList
|
||||||
apiRoute={apiRoute}
|
apiRoute={apiRoute}
|
||||||
builder={MemberCard}
|
builder={MemberCard}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
placeholder={Placeholder}
|
placeholder={Placeholder}
|
||||||
fallback={Fallback}
|
fallback={Fallback}
|
||||||
useObserver={useObserver}
|
useObserver={useObserver}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardList
|
<CardList
|
||||||
apiRoute={apiRoute}
|
apiRoute={apiRoute}
|
||||||
builder={MemberCard}
|
builder={MemberCard}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Placeholder({ ref }: { ref?: Ref<HTMLDivElement> | undefined }) {
|
function Placeholder({ ref }: { ref?: Ref<HTMLDivElement> | undefined }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// class='components__blog_block components__blog_block--card components__blog_block--placeholder'
|
// class='components__blog_block components__blog_block--card components__blog_block--placeholder'
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<h3>Chargement ...</h3>
|
<h3>Chargement ...</h3>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Fallback() {
|
function Fallback() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// class='components__blog_block components__blog_block--card components__blog_block--fallback'
|
// class='components__blog_block components__blog_block--card components__blog_block--fallback'
|
||||||
inert
|
inert
|
||||||
>
|
>
|
||||||
<h3>Pas d'utilisateur</h3>
|
<h3>Pas d'utilisateur</h3>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,14 @@ function RenderError(
|
||||||
|
|
||||||
export type Fallback = ({ error }: { error: Error }) => JSX.Element
|
export type Fallback = ({ error }: { error: Error }) => JSX.Element
|
||||||
|
|
||||||
|
export type SuspenseProps = {
|
||||||
|
loader: JSX.Element
|
||||||
|
fallback?: Fallback
|
||||||
|
signal?: AbortSignal
|
||||||
|
} & ({ children: Promise<JSX.Element> } | { value: Promise<JSX.Element> })
|
||||||
|
|
||||||
export default function Suspense(
|
export default function Suspense(
|
||||||
{ loader, fallback, signal, children }: {
|
{ loader, fallback, signal, ...props }: SuspenseProps,
|
||||||
loader: JSX.Element
|
|
||||||
children: Promise<JSX.Element>
|
|
||||||
fallback?: Fallback
|
|
||||||
signal?: AbortSignal
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
const displayed = useSignal(loader)
|
const displayed = useSignal(loader)
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
@ -38,7 +39,9 @@ export default function Suspense(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
children
|
//Prevent transpilation error due to children expected to not be a promise
|
||||||
|
const inner = 'value' in props ? props.value : props.children
|
||||||
|
inner
|
||||||
.then((element) => {
|
.then((element) => {
|
||||||
if (signal?.aborted) return
|
if (signal?.aborted) return
|
||||||
displayed.value = element
|
displayed.value = element
|
||||||
|
|
|
@ -5,24 +5,24 @@ import { SessionHandlers } from ':src/session/mod.ts'
|
||||||
import { respondApi, respondApiStream } from ':src/utils.ts'
|
import { respondApi, respondApiStream } from ':src/utils.ts'
|
||||||
|
|
||||||
export const handler: SessionHandlers = {
|
export const handler: SessionHandlers = {
|
||||||
GET(_req, ctx) {
|
GET(_req, ctx) {
|
||||||
try {
|
try {
|
||||||
const memberList = dbToMemberCardProps(db)
|
const memberList = dbToMemberCardProps(db)
|
||||||
|
|
||||||
const params = ctx.url.searchParams
|
const params = ctx.url.searchParams
|
||||||
|
|
||||||
const groupParam = params.get('group')
|
const groupParam = params.get('group')
|
||||||
if (groupParam) {
|
if (groupParam) {
|
||||||
const list = memberList.filter(
|
const list = memberList.filter(
|
||||||
(member) => member.groups.includes(groupParam),
|
(member) => member.groups.includes(groupParam),
|
||||||
) as AsyncIterableIterator<MemberCardProps>
|
) as AsyncIterableIterator<MemberCardProps>
|
||||||
|
|
||||||
return respondApiStream(list)
|
return respondApiStream(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
return respondApiStream(memberList)
|
return respondApiStream(memberList)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return respondApi('error', error)
|
return respondApi('error', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { SessionHandlers } from ':src/session/mod.ts'
|
||||||
import { respondApi, respondApiStream } from ':src/utils.ts'
|
import { respondApi, respondApiStream } from ':src/utils.ts'
|
||||||
|
|
||||||
export const handler: SessionHandlers = {
|
export const handler: SessionHandlers = {
|
||||||
GET() {
|
GET() {
|
||||||
try {
|
try {
|
||||||
const newsList = fetchNewsList('cohabit')
|
const newsList = fetchNewsList('cohabit')
|
||||||
return respondApiStream(newsList)
|
return respondApiStream(newsList)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return respondApi('error', error)
|
return respondApi('error', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { db } from ':src/db/mod.ts'
|
||||||
import type { SessionHandlers } from ':src/session/mod.ts'
|
import type { SessionHandlers } from ':src/session/mod.ts'
|
||||||
import { respondApi } from ':src/utils.ts'
|
import { respondApi } from ':src/utils.ts'
|
||||||
import { getRelyingParty } from ':src/webauthn/mod.ts'
|
import { getRelyingParty } from ':src/webauthn/mod.ts'
|
||||||
import { Credential, Passkey, Ref, User } from '@cohabit/resources-manager/models'
|
import {
|
||||||
|
Credential,
|
||||||
|
Passkey,
|
||||||
|
Ref,
|
||||||
|
User,
|
||||||
|
} from '@cohabit/resources-manager/models'
|
||||||
import {
|
import {
|
||||||
generateAuthenticationOptions,
|
generateAuthenticationOptions,
|
||||||
verifyAuthenticationResponse,
|
verifyAuthenticationResponse,
|
||||||
|
|
|
@ -12,7 +12,12 @@ import type {
|
||||||
//TODO improve workspace imports
|
//TODO improve workspace imports
|
||||||
import { db } from ':src/db/mod.ts'
|
import { db } from ':src/db/mod.ts'
|
||||||
import { getRelyingParty } from ':src/webauthn/mod.ts'
|
import { getRelyingParty } from ':src/webauthn/mod.ts'
|
||||||
import { Credential, Passkey, Ref, User } from '@cohabit/resources-manager/models'
|
import {
|
||||||
|
Credential,
|
||||||
|
Passkey,
|
||||||
|
Ref,
|
||||||
|
User,
|
||||||
|
} from '@cohabit/resources-manager/models'
|
||||||
import { encodeBase64 } from '@std/encoding'
|
import { encodeBase64 } from '@std/encoding'
|
||||||
|
|
||||||
type Params = { step: 'start' | 'finish' }
|
type Params = { step: 'start' | 'finish' }
|
||||||
|
|
|
@ -29,9 +29,8 @@ export default function Home() {
|
||||||
<section>
|
<section>
|
||||||
<h2>Nos machines</h2>
|
<h2>Nos machines</h2>
|
||||||
<p>
|
<p>
|
||||||
Vous avez besoin d'aide pour concrétiser votre projet ? Le
|
Vous avez besoin d'aide pour concrétiser votre projet ? Le Fablab vous
|
||||||
Fablab vous accompagnes dans vos projets, grâce à son parc
|
accompagnes dans vos projets, grâce à son parc de machine...
|
||||||
de machine...
|
|
||||||
</p>
|
</p>
|
||||||
<AutoGrid columnWidth='15rem' style={{ alignItems: 'center' }}>
|
<AutoGrid columnWidth='15rem' style={{ alignItems: 'center' }}>
|
||||||
<>
|
<>
|
||||||
|
@ -63,29 +62,26 @@ export default function Home() {
|
||||||
<section>
|
<section>
|
||||||
<h2>Présentation</h2>
|
<h2>Présentation</h2>
|
||||||
<p>
|
<p>
|
||||||
Coh@bit est un fablab de l'université de Bordeaux ouvert à
|
Coh@bit est un fablab de l'université de Bordeaux ouvert à tous les
|
||||||
tous les publics depuis 2016. Du collégien à
|
publics depuis 2016. Du collégien à l'enseignant-chercheur, l'équipe
|
||||||
l'enseignant-chercheur, l'équipe du fablab accompagne les
|
du fablab accompagne les adhérents dans la réalisation de leurs
|
||||||
adhérents dans la réalisation de leurs projets de
|
projets de fabrication autour du numérique.
|
||||||
fabrication autour du numérique.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Venez découvrir un tout nouvelle univers où vous pouvez
|
Venez découvrir un tout nouvelle univers où vous pouvez concrétiser
|
||||||
concrétiser vos projet, découvrir des personnes avec les
|
vos projet, découvrir des personnes avec les même affinités que vous,
|
||||||
même affinités que vous, cultiver votre savoir et savoir
|
cultiver votre savoir et savoir faire, dans l'entraide et le partage.
|
||||||
faire, dans l'entraide et le partage.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Créer par Frédéric Bos (Directeur de l'IUT de Bordeaux) en
|
Créer par Frédéric Bos (Directeur de l'IUT de Bordeaux) en 2014,
|
||||||
2014, Coh@bit (Creative Open House at Bordeaux Institut of
|
Coh@bit (Creative Open House at Bordeaux Institut of Technology) est
|
||||||
Technology) est une association réunissant deux entités : le
|
une association réunissant deux entités : le Fablab et le Technoshop.
|
||||||
Fablab et le Technoshop.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Ouvert à tous les publics depuis 2016, allant de
|
Ouvert à tous les publics depuis 2016, allant de
|
||||||
l'enseignant-chercheur au collégien, l'équipe du fablab
|
l'enseignant-chercheur au collégien, l'équipe du fablab accompagne les
|
||||||
accompagne les adhérents dans la réalisation de leurs
|
adhérents dans la réalisation de leurs projets de fabrication
|
||||||
projets de fabrication numérique.
|
numérique.
|
||||||
</p>
|
</p>
|
||||||
<CohabitInfoTable />
|
<CohabitInfoTable />
|
||||||
</section>
|
</section>
|
||||||
|
|
148
src/blog/mod.ts
148
src/blog/mod.ts
|
@ -4,101 +4,99 @@ import { base64ToString } from ':src/utils.ts'
|
||||||
import { extract } from '@std/front-matter/yaml'
|
import { extract } from '@std/front-matter/yaml'
|
||||||
|
|
||||||
export async function fetchNews(
|
export async function fetchNews(
|
||||||
publisher: string,
|
publisher: string,
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<BlogProps> {
|
): Promise<BlogProps> {
|
||||||
const apiUrl = 'https://git.cohabit.fr/api/v1/'
|
const apiUrl = 'https://git.cohabit.fr/api/v1/'
|
||||||
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
|
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
|
||||||
const endpoint = new URL('contents/', baseEndpoint)
|
const endpoint = new URL('contents/', baseEndpoint)
|
||||||
|
|
||||||
// Get readme content api url
|
// Get readme content api url
|
||||||
const readmePath = encodeURIComponent(`${name}/README.md`)
|
const readmePath = encodeURIComponent(`${name}/README.md`)
|
||||||
const contentUrl = new URL(readmePath, endpoint)
|
const contentUrl = new URL(readmePath, endpoint)
|
||||||
|
|
||||||
// Fetch readme content, commit hash and raw url for relative links
|
// Fetch readme content, commit hash and raw url for relative links
|
||||||
const file = await getCommitAndContent(contentUrl)
|
const file = await getCommitAndContent(contentUrl)
|
||||||
// Get commit infos (author + date) and get readme content from base64 source
|
// Get commit infos (author + date) and get readme content from base64 source
|
||||||
const { raw, url, lastUpdate, author } = await getAuthorAndParseContent(
|
const { raw, url, lastUpdate, author } = await getAuthorAndParseContent(
|
||||||
file,
|
file,
|
||||||
baseEndpoint,
|
baseEndpoint,
|
||||||
)
|
)
|
||||||
// Extract frontmatter
|
// Extract frontmatter
|
||||||
const { attrs, body } = extract<NewsFrontMatter>(raw)
|
const { attrs, body } = extract<NewsFrontMatter>(raw)
|
||||||
|
|
||||||
// Transform API responses into BlogProps for BlogCard and BlogPost components
|
// Transform API responses into BlogProps for BlogCard and BlogPost components
|
||||||
return {
|
return {
|
||||||
author,
|
author,
|
||||||
publisher,
|
publisher,
|
||||||
lastUpdate,
|
lastUpdate,
|
||||||
options: attrs['x-cohabit'],
|
options: attrs['x-cohabit'],
|
||||||
title: attrs.title,
|
title: attrs.title,
|
||||||
hash: file.sha,
|
hash: file.sha,
|
||||||
description: attrs.description,
|
description: attrs.description,
|
||||||
body,
|
body,
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
tags: attrs.tags,
|
tags: attrs.tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function* fetchNewsList(
|
export async function* fetchNewsList(
|
||||||
publisher: string,
|
publisher: string,
|
||||||
): AsyncGenerator<BlogProps, void, void> {
|
): AsyncGenerator<BlogProps, void, void> {
|
||||||
const apiUrl = 'https://git.cohabit.fr/api/v1/'
|
const apiUrl = 'https://git.cohabit.fr/api/v1/'
|
||||||
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
|
const baseEndpoint = new URL(`repos/${publisher}/.news/`, apiUrl)
|
||||||
const endpoint = new URL('contents/', baseEndpoint)
|
const endpoint = new URL('contents/', baseEndpoint)
|
||||||
|
|
||||||
// Fetch repo content
|
// Fetch repo content
|
||||||
const root = await fetch(endpoint).then((response) => response.json()) as {
|
const root = await fetch(endpoint).then((response) => response.json()) as {
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
// Fetch `README.md` in sub directories
|
// Fetch `README.md` in sub directories
|
||||||
const blogPropsList = root
|
const blogPropsList = root
|
||||||
// Remove file and dir starting with "."
|
// Remove file and dir starting with "."
|
||||||
.filter(isNewsDirectory)
|
.filter(isNewsDirectory)
|
||||||
// Fetch single news and return BlogProps
|
// Fetch single news and return BlogProps
|
||||||
.map(({ name }) => fetchNews(publisher, name))
|
.map(({ name }) => fetchNews(publisher, name))
|
||||||
|
|
||||||
// Yield each news
|
// Yield each news
|
||||||
for (const blogProps of blogPropsList) {
|
for (const blogProps of blogPropsList) {
|
||||||
yield blogProps
|
yield blogProps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAuthorAndParseContent(
|
async function getAuthorAndParseContent(
|
||||||
file: { download_url: string; content: string; last_commit_sha: string },
|
file: { download_url: string; content: string; last_commit_sha: string },
|
||||||
baseEndpoint: URL,
|
baseEndpoint: URL,
|
||||||
) {
|
) {
|
||||||
const commitUrl = new URL(
|
const commitUrl = new URL(
|
||||||
`git/commits/${file.last_commit_sha}?stat=false&verification=false&files=false`,
|
`git/commits/${file.last_commit_sha}?stat=false&verification=false&files=false`,
|
||||||
baseEndpoint,
|
baseEndpoint,
|
||||||
)
|
)
|
||||||
const infos = await fetch(commitUrl).then((response) =>
|
const infos = await fetch(commitUrl).then((response) => response.json()) as {
|
||||||
response.json()
|
created: string
|
||||||
) as {
|
author: { login: string }
|
||||||
created: string
|
}
|
||||||
author: { login: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: base64ToString(file.content),
|
raw: base64ToString(file.content),
|
||||||
url: file.download_url,
|
url: file.download_url,
|
||||||
lastUpdate: new Date(infos.created),
|
lastUpdate: new Date(infos.created),
|
||||||
author: infos.author.login,
|
author: infos.author.login,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCommitAndContent(contentUrl: URL) {
|
async function getCommitAndContent(contentUrl: URL) {
|
||||||
return await fetch(contentUrl).then((response) => response.json()) as {
|
return await fetch(contentUrl).then((response) => response.json()) as {
|
||||||
download_url: string
|
download_url: string
|
||||||
content: string
|
content: string
|
||||||
sha: string
|
sha: string
|
||||||
last_commit_sha: string
|
last_commit_sha: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNewsDirectory(entry: { name: string; type: string }): boolean {
|
function isNewsDirectory(entry: { name: string; type: string }): boolean {
|
||||||
return entry.type === 'dir' && entry.name.startsWith('.') === false
|
return entry.type === 'dir' && entry.name.startsWith('.') === false
|
||||||
}
|
}
|
||||||
|
|
28
src/cache/middleware.ts
vendored
28
src/cache/middleware.ts
vendored
|
@ -1,19 +1,19 @@
|
||||||
import { FreshContext } from '$fresh/server.ts'
|
import { FreshContext } from '$fresh/server.ts'
|
||||||
|
|
||||||
export function useCache(
|
export function useCache(
|
||||||
_request: Request,
|
_request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
ctx: FreshContext,
|
ctx: FreshContext,
|
||||||
) {
|
) {
|
||||||
if (ctx.config.dev) return
|
if (ctx.config.dev) return
|
||||||
if (
|
if (
|
||||||
ctx.url.pathname.match(
|
ctx.url.pathname.match(
|
||||||
/(.+\.|_)((css)|(ttf)|(woff2)|(png)|(svg)|(jpe?g)|(avif))/,
|
/(.+\.|_)((css)|(ttf)|(woff2)|(png)|(svg)|(jpe?g)|(avif))/,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
response.headers.set(
|
response.headers.set(
|
||||||
'Cache-Control',
|
'Cache-Control',
|
||||||
'public, max-age=31536000, immutable',
|
'public, max-age=31536000, immutable',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,31 +2,31 @@ import { FreshContext } from '$fresh/server.ts'
|
||||||
import { applyCspRulesWithNonce, CspRules } from ':src/csp/mod.ts'
|
import { applyCspRulesWithNonce, CspRules } from ':src/csp/mod.ts'
|
||||||
|
|
||||||
export function useCsp(
|
export function useCsp(
|
||||||
_request: Request,
|
_request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
ctx: FreshContext,
|
ctx: FreshContext,
|
||||||
) {
|
) {
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSP
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSP
|
||||||
|
|
||||||
const trustedDomains = ["'self'", 'https://git.cohabit.fr']
|
const trustedDomains = ["'self'", 'https://git.cohabit.fr']
|
||||||
|
|
||||||
const cspRules: CspRules = {
|
const cspRules: CspRules = {
|
||||||
defaultSrc: ["'none'"],
|
defaultSrc: ["'none'"],
|
||||||
frameAncestors: ["'none'"],
|
frameAncestors: ["'none'"],
|
||||||
upgradeInsecureRequests: true,
|
upgradeInsecureRequests: true,
|
||||||
styleSrc: [...trustedDomains, "'unsafe-inline'"], //set nonce to inline script
|
styleSrc: [...trustedDomains, "'unsafe-inline'"], //set nonce to inline script
|
||||||
manifestSrc: [`${ctx.url.origin.replace('http:', 'https:')}/manifest.json`],
|
manifestSrc: [`${ctx.url.origin.replace('http:', 'https:')}/manifest.json`],
|
||||||
baseUri: ["'none'"],
|
baseUri: ["'none'"],
|
||||||
imgSrc: [
|
imgSrc: [
|
||||||
...trustedDomains,
|
...trustedDomains,
|
||||||
'data:',
|
'data:',
|
||||||
'https:',
|
'https:',
|
||||||
],
|
],
|
||||||
fontSrc: [...trustedDomains, 'https://cdn.jsdelivr.net'],
|
fontSrc: [...trustedDomains, 'https://cdn.jsdelivr.net'],
|
||||||
scriptSrc: ["'self'", "'strict-dynamic'"],
|
scriptSrc: ["'self'", "'strict-dynamic'"],
|
||||||
connectSrc: ["'self'"],
|
connectSrc: ["'self'"],
|
||||||
formAction: ["'none'"],
|
formAction: ["'none'"],
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyCspRulesWithNonce(response, cspRules)
|
return applyCspRulesWithNonce(response, cspRules)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,51 +2,51 @@ import type { ContentSecurityPolicyDirectives } from '$fresh/runtime.ts'
|
||||||
import { getFreshNonce, toSnakeCase } from ':src/utils.ts'
|
import { getFreshNonce, toSnakeCase } from ':src/utils.ts'
|
||||||
|
|
||||||
export type CspRules = ContentSecurityPolicyDirectives & {
|
export type CspRules = ContentSecurityPolicyDirectives & {
|
||||||
upgradeInsecureRequests: true
|
upgradeInsecureRequests: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyCspRules(
|
export function applyCspRules(
|
||||||
{ headers }: { headers: Headers },
|
{ headers }: { headers: Headers },
|
||||||
rules: CspRules,
|
rules: CspRules,
|
||||||
): void {
|
): void {
|
||||||
const rulesString: string[] = []
|
const rulesString: string[] = []
|
||||||
|
|
||||||
for (const rule in rules) {
|
for (const rule in rules) {
|
||||||
const value = rules[rule as unknown as keyof CspRules]
|
const value = rules[rule as unknown as keyof CspRules]
|
||||||
const ruleName = toSnakeCase(rule)
|
const ruleName = toSnakeCase(rule)
|
||||||
|
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
rulesString.push(`${ruleName}`)
|
rulesString.push(`${ruleName}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
rulesString.push(`${ruleName} ${value.join(' ')}`)
|
rulesString.push(`${ruleName} ${value.join(' ')}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
rulesString.push(`${ruleName} ${value}`)
|
rulesString.push(`${ruleName} ${value}`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
throw TypeError(`unsupported csp rule "${rule}" with value (${value})`)
|
throw TypeError(`unsupported csp rule "${rule}" with value (${value})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.set('Content-Security-Policy', rulesString.join('; '))
|
headers.set('Content-Security-Policy', rulesString.join('; '))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function applyCspRulesWithNonce(
|
export async function applyCspRulesWithNonce(
|
||||||
response: Response,
|
response: Response,
|
||||||
rules: CspRules,
|
rules: CspRules,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Get nonce from any html response
|
// Get nonce from any html response
|
||||||
const nonce = await getFreshNonce(response)
|
const nonce = await getFreshNonce(response)
|
||||||
|
|
||||||
// Add nonce to script src if defined
|
// Add nonce to script src if defined
|
||||||
if (nonce) {
|
if (nonce) {
|
||||||
rules.scriptSrc?.push(`'nonce-${nonce}'`)
|
rules.scriptSrc?.push(`'nonce-${nonce}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyCspRules(response, rules)
|
return applyCspRules(response, rules)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import type { MailAddress } from '@cohabit/resources-manager/types'
|
||||||
// Import Datas
|
// Import Datas
|
||||||
import { exists } from '$std/fs/exists.ts'
|
import { exists } from '$std/fs/exists.ts'
|
||||||
import { ensureDir } from '$std/fs/mod.ts'
|
import { ensureDir } from '$std/fs/mod.ts'
|
||||||
import groups from ":src/db/mock/groups.json" with { type: 'json' }
|
import groups from ':src/db/mock/groups.json' with { type: 'json' }
|
||||||
import users from ":src/db/mock/users.json" with { type: 'json' }
|
import users from ':src/db/mock/users.json' with { type: 'json' }
|
||||||
|
|
||||||
await ensureDir('./cache')
|
await ensureDir('./cache')
|
||||||
const dbPath = './cache/db.sqlite'
|
const dbPath = './cache/db.sqlite'
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import { FreshContext } from '$fresh/server.ts'
|
import { FreshContext } from '$fresh/server.ts'
|
||||||
|
|
||||||
export function useSecurityHeaders(
|
export function useSecurityHeaders(
|
||||||
_request: Request,
|
_request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
_ctx: FreshContext,
|
_ctx: FreshContext,
|
||||||
) {
|
) {
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#http_strict_transport_security
|
// See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#http_strict_transport_security
|
||||||
response.headers.set(
|
response.headers.set(
|
||||||
'Strict-Transport-Security',
|
'Strict-Transport-Security',
|
||||||
'max-age=63072000; includeSubDomains; preload',
|
'max-age=63072000; includeSubDomains; preload',
|
||||||
)
|
)
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Referrer_policy
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Referrer_policy
|
||||||
response.headers.set(
|
response.headers.set(
|
||||||
'Referrer-Policy',
|
'Referrer-Policy',
|
||||||
'no-referrer, strict-origin-when-cross-origin',
|
'no-referrer, strict-origin-when-cross-origin',
|
||||||
)
|
)
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/MIME_types
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/MIME_types
|
||||||
response.headers.set('X-Content-Type-Options', 'nosniff')
|
response.headers.set('X-Content-Type-Options', 'nosniff')
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Clickjacking
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Clickjacking
|
||||||
response.headers.set('X-Frame-Options', 'DENY')
|
response.headers.set('X-Frame-Options', 'DENY')
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CORP
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CORP
|
||||||
response.headers.set('Cross-Origin-Resource-Policy', 'same-origin')
|
response.headers.set('Cross-Origin-Resource-Policy', 'same-origin')
|
||||||
//See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
//See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
||||||
//? SRI plugin for non local resources only ?
|
//? SRI plugin for non local resources only ?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { FreshContext } from '$fresh/server.ts'
|
import { FreshContext } from '$fresh/server.ts'
|
||||||
|
|
||||||
export function useServiceworker(
|
export function useServiceworker(
|
||||||
_request: Request,
|
_request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
ctx: FreshContext,
|
ctx: FreshContext,
|
||||||
) {
|
) {
|
||||||
// Allow service worker to serve root scope
|
// Allow service worker to serve root scope
|
||||||
if (ctx.url.pathname.endsWith('island-startserviceworker.js')) {
|
if (ctx.url.pathname.endsWith('island-startserviceworker.js')) {
|
||||||
response.headers.set('Service-Worker-Allowed', '/')
|
response.headers.set('Service-Worker-Allowed', '/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,51 +3,51 @@ import { SessionStore } from ':src/session/mod.ts'
|
||||||
import { getCookies, setCookie } from 'jsr:@std/http@^0.224.4/cookie'
|
import { getCookies, setCookie } from 'jsr:@std/http@^0.224.4/cookie'
|
||||||
|
|
||||||
export function useSession(
|
export function useSession(
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
ctx: FreshContext,
|
ctx: FreshContext,
|
||||||
) {
|
) {
|
||||||
// Check if session already started
|
// Check if session already started
|
||||||
if (SessionStore.getFromRequest(request) !== undefined) {
|
if (SessionStore.getFromRequest(request) !== undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear outdated cookies
|
// Clear outdated cookies
|
||||||
for (const cookie in getCookies(request.headers)) {
|
for (const cookie in getCookies(request.headers)) {
|
||||||
setCookie(response.headers, {
|
setCookie(response.headers, {
|
||||||
name: cookie,
|
name: cookie,
|
||||||
value: '',
|
value: '',
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: 0,
|
expires: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new session
|
// Create new session
|
||||||
const session = SessionStore.createSession()
|
const session = SessionStore.createSession()
|
||||||
ctx.state = { ...ctx.state, session }
|
ctx.state = { ...ctx.state, session }
|
||||||
|
|
||||||
// Set session cookie
|
// Set session cookie
|
||||||
setCookie(response.headers, {
|
setCookie(response.headers, {
|
||||||
name: '__Secure-SESSION',
|
name: '__Secure-SESSION',
|
||||||
value: session.uuid,
|
value: session.uuid,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: 'Strict',
|
sameSite: 'Strict',
|
||||||
secure: true,
|
secure: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: SessionStore.maxAge,
|
expires: SessionStore.maxAge,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set csrf
|
// Set csrf
|
||||||
const csrf = crypto.randomUUID()
|
const csrf = crypto.randomUUID()
|
||||||
session.set('_csrf', csrf)
|
session.set('_csrf', csrf)
|
||||||
|
|
||||||
setCookie(response.headers, {
|
setCookie(response.headers, {
|
||||||
name: '__Host-CSRF',
|
name: '__Host-CSRF',
|
||||||
value: csrf,
|
value: csrf,
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
sameSite: 'Strict',
|
sameSite: 'Strict',
|
||||||
secure: true,
|
secure: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
expires: SessionStore.maxAge,
|
expires: SessionStore.maxAge,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue