update cloudflare context
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import type { Route } from './+types/api'
|
import type { Route } from './+types/api'
|
||||||
|
|
||||||
import type { User } from '@repo/auth/auth'
|
import type { User } from '@repo/auth/auth'
|
||||||
import { userContext } from '@repo/auth/context'
|
import { userContext, cloudflareContext } from '@repo/auth/context'
|
||||||
|
|
||||||
export const loader = proxy
|
export const loader = proxy
|
||||||
export const action = proxy
|
export const action = proxy
|
||||||
@@ -11,8 +11,12 @@ async function proxy({
|
|||||||
context
|
context
|
||||||
}: Route.ActionArgs): Promise<Response> {
|
}: Route.ActionArgs): Promise<Response> {
|
||||||
const pathname = new URL(request.url).pathname.replace(/^\/api\//, '')
|
const pathname = new URL(request.url).pathname.replace(/^\/api\//, '')
|
||||||
|
const cloudflare = context.get(cloudflareContext) as {
|
||||||
|
env: Env
|
||||||
|
ctx: ExecutionContext
|
||||||
|
}
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext) as User
|
||||||
const url = new URL(pathname, context.cloudflare.env.API_URL)
|
const url = new URL(pathname, cloudflare.env.API_URL)
|
||||||
const headers = new Headers(request.headers)
|
const headers = new Headers(request.headers)
|
||||||
|
|
||||||
headers.set('Authorization', `Bearer ${user.accessToken}`)
|
headers.set('Authorization', `Bearer ${user.accessToken}`)
|
||||||
|
|||||||
@@ -3,30 +3,29 @@ import type { Route } from './+types/login'
|
|||||||
import { redirect } from 'react-router'
|
import { redirect } from 'react-router'
|
||||||
|
|
||||||
import { createAuth, type User } from '@repo/auth/auth'
|
import { createAuth, type User } from '@repo/auth/auth'
|
||||||
import { requestIdContext } from '@repo/auth/context'
|
import { requestIdContext, cloudflareContext } from '@repo/auth/context'
|
||||||
import { createSessionStorage } from '@repo/auth/session'
|
import { createSessionStorage } from '@repo/auth/session'
|
||||||
|
|
||||||
export async function loader({ request, context }: Route.ActionArgs) {
|
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||||
const url = new URL(request.url)
|
const url = new URL(request.url)
|
||||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
|
const sessionStorage = createSessionStorage(cloudflare.env)
|
||||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||||
const requestId = context.get(requestIdContext)
|
const requestId = context.get(requestIdContext)
|
||||||
const user = session.get('user') as User | null
|
const user = session.get('user')
|
||||||
const returnTo = (
|
const returnTo = (session.get('returnTo') as string | undefined) ?? '/'
|
||||||
session.has('returnTo') ? session.get('returnTo') : '/'
|
|
||||||
) as string
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return redirect(returnTo)
|
return redirect(returnTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authenticator = createAuth(
|
const authenticator = createAuth(cloudflare.env, `${url.origin}/login`)
|
||||||
context.cloudflare.env,
|
const authenticatedUser = (await authenticator.authenticate(
|
||||||
`${url?.origin}/login`
|
'oidc',
|
||||||
)
|
request
|
||||||
const user = await authenticator.authenticate('oidc', request)
|
)) as User
|
||||||
session.set('user', user)
|
session.set('user', authenticatedUser)
|
||||||
|
|
||||||
console.log(`[${requestId}] Redirecting the user to ${returnTo}`)
|
console.log(`[${requestId}] Redirecting the user to ${returnTo}`)
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import type { OAuth2Strategy } from 'remix-auth-oauth2'
|
|||||||
|
|
||||||
import { createAuth, type User } from '@repo/auth/auth'
|
import { createAuth, type User } from '@repo/auth/auth'
|
||||||
import { createSessionStorage } from '@repo/auth/session'
|
import { createSessionStorage } from '@repo/auth/session'
|
||||||
|
import { cloudflareContext } from '@repo/auth/context'
|
||||||
|
|
||||||
export async function loader({ request, context }: Route.LoaderArgs) {
|
export async function loader({ request, context }: Route.LoaderArgs) {
|
||||||
const authenticator = createAuth(context.cloudflare.env)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
const authenticator = createAuth(cloudflare.env)
|
||||||
|
const sessionStorage = createSessionStorage(cloudflare.env)
|
||||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||||
const user = session.get('user') as User
|
const user = session.get('user')
|
||||||
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
|
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
|
||||||
|
|
||||||
if (user?.accessToken && strategy) {
|
if (user?.accessToken && strategy) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Await, Link } from 'react-router'
|
|||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
import { type User } from '@repo/auth/auth'
|
import { type User } from '@repo/auth/auth'
|
||||||
import { userContext } from '@repo/auth/context'
|
import { userContext, cloudflareContext } from '@repo/auth/context'
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
@@ -26,6 +26,7 @@ export function meta({}: Route.MetaArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loader({ request, context }: Route.ActionArgs) {
|
export async function loader({ request, context }: Route.ActionArgs) {
|
||||||
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext) as User
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const status = searchParams.getAll('status') || []
|
const status = searchParams.getAll('status') || []
|
||||||
@@ -45,15 +46,17 @@ export async function loader({ request, context }: Route.ActionArgs) {
|
|||||||
sort: [sort],
|
sort: [sort],
|
||||||
page,
|
page,
|
||||||
hitsPerPage,
|
hitsPerPage,
|
||||||
env: context.cloudflare.env
|
env: cloudflare.env
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: payments
|
payments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Component({ loaderData: { data } }) {
|
export default function Component({
|
||||||
|
loaderData: { payments }
|
||||||
|
}: Route.ComponentProps) {
|
||||||
return (
|
return (
|
||||||
<Container className="space-y-2.5">
|
<Container className="space-y-2.5">
|
||||||
<Suspense fallback={<Skeleton />}>
|
<Suspense fallback={<Skeleton />}>
|
||||||
@@ -81,15 +84,17 @@ export default function Component({ loaderData: { data } }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Await resolve={data}>
|
<Await resolve={payments}>
|
||||||
{({ hits, page, hitsPerPage, totalHits }) => {
|
{({ hits, page = 1, hitsPerPage, totalHits }) => {
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
sort={[{ id: 'create_date', desc: true }]}
|
sort={[{ id: 'create_date', desc: true }]}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={hits as Order[]}
|
data={hits as Order[]}
|
||||||
pageIndex={page - 1}
|
pageIndex={page - 1}
|
||||||
|
// @ts-ignore
|
||||||
pageSize={hitsPerPage}
|
pageSize={hitsPerPage}
|
||||||
|
// @ts-ignore
|
||||||
rowCount={totalHits}
|
rowCount={totalHits}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { Suspense, useMemo, type ComponentProps } from 'react'
|
|||||||
import { Await, NavLink, useSearchParams } from 'react-router'
|
import { Await, NavLink, useSearchParams } from 'react-router'
|
||||||
|
|
||||||
import type { User } from '@repo/auth/auth'
|
import type { User } from '@repo/auth/auth'
|
||||||
import { userContext } from '@repo/auth/context'
|
import { userContext, cloudflareContext } from '@repo/auth/context'
|
||||||
import { FacetedFilter } from '@repo/ui/components/faceted-filter'
|
import { FacetedFilter } from '@repo/ui/components/faceted-filter'
|
||||||
import { SearchForm } from '@repo/ui/components/search-form'
|
import { SearchForm } from '@repo/ui/components/search-form'
|
||||||
import { Skeleton } from '@repo/ui/components/skeleton'
|
import { Skeleton } from '@repo/ui/components/skeleton'
|
||||||
@@ -60,6 +60,7 @@ export function meta({}: Route.MetaArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loader({ request, context }: Route.ActionArgs) {
|
export async function loader({ request, context }: Route.ActionArgs) {
|
||||||
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext) as User
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const status = searchParams.getAll('status') || []
|
const status = searchParams.getAll('status') || []
|
||||||
@@ -74,7 +75,7 @@ export async function loader({ request, context }: Route.ActionArgs) {
|
|||||||
filter: builder.build(),
|
filter: builder.build(),
|
||||||
sort: ['created_at:desc'],
|
sort: ['created_at:desc'],
|
||||||
hitsPerPage: 100,
|
hitsPerPage: 100,
|
||||||
env: context.cloudflare.env
|
env: cloudflare.env
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import { redirect } from 'react-router'
|
|||||||
import { userContext } from '@repo/auth/context'
|
import { userContext } from '@repo/auth/context'
|
||||||
import type { User } from '@repo/auth/auth'
|
import type { User } from '@repo/auth/auth'
|
||||||
|
|
||||||
|
import { cloudflareContext } from 'workers/app'
|
||||||
|
|
||||||
const konvivaApi = 'https://lms.saladeaula.digital'
|
const konvivaApi = 'https://lms.saladeaula.digital'
|
||||||
|
|
||||||
export async function loader({ context }: Route.LoaderArgs) {
|
export async function loader({ context }: Route.LoaderArgs) {
|
||||||
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext) as User
|
||||||
const secretKey = context.cloudflare.env.KONVIVA_SECRET_KEY
|
const secretKey = cloudflare.env.KONVIVA_SECRET_KEY
|
||||||
const token = await getToken(user.email, secretKey)
|
const token = await getToken(user.email, secretKey)
|
||||||
|
|
||||||
const url = new URL('/action/acessoExterno', konvivaApi)
|
const url = new URL('/action/acessoExterno', konvivaApi)
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import type { Route } from './+types/layout'
|
|||||||
|
|
||||||
import { useToggle } from 'ahooks'
|
import { useToggle } from 'ahooks'
|
||||||
import { MenuIcon } from 'lucide-react'
|
import { MenuIcon } from 'lucide-react'
|
||||||
import { Link, NavLink, Outlet } from 'react-router'
|
import { data, Link, NavLink, Outlet } from 'react-router'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import type { User } from '@repo/auth/auth'
|
||||||
import { Toaster } from '@repo/ui/components/ui/sonner'
|
import { Toaster } from '@repo/ui/components/ui/sonner'
|
||||||
import { userContext } from '@repo/auth/context'
|
import { userContext, cloudflareContext } from '@repo/auth/context'
|
||||||
import { authMiddleware } from '@repo/auth/middleware/auth'
|
import { authMiddleware } from '@repo/auth/middleware/auth'
|
||||||
import { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode'
|
import { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode'
|
||||||
import { NavUser } from '@repo/ui/components/nav-user'
|
import { NavUser } from '@repo/ui/components/nav-user'
|
||||||
@@ -23,16 +25,14 @@ import {
|
|||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetTrigger
|
SheetTrigger
|
||||||
} from '@repo/ui/components/ui/sheet'
|
} from '@repo/ui/components/ui/sheet'
|
||||||
import type { User } from '@repo/auth/auth'
|
|
||||||
import { createSessionStorage } from '@repo/auth/session'
|
import { createSessionStorage } from '@repo/auth/session'
|
||||||
import { data } from 'react-router'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
|
export const middleware: Route.MiddlewareFunction[] = [authMiddleware]
|
||||||
|
|
||||||
export async function loader({ context, request }: Route.ActionArgs) {
|
export async function loader({ context, request }: Route.ActionArgs) {
|
||||||
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext) as User
|
||||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
const sessionStorage = createSessionStorage(cloudflare.env)
|
||||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||||
|
|
||||||
const flash = {
|
const flash = {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { Route } from './+types/proxy'
|
import type { Route } from './+types/proxy'
|
||||||
|
|
||||||
|
import { cloudflareContext } from '@repo/auth/context'
|
||||||
|
|
||||||
// Mapping of extensions to MIME types
|
// Mapping of extensions to MIME types
|
||||||
const mimeTypes: Record<string, string> = {
|
const mimeTypes: Record<string, string> = {
|
||||||
// Images
|
// Images
|
||||||
@@ -25,8 +27,9 @@ const mimeTypes: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||||
const bucketEndpoint = context.cloudflare.env.BUCKET_ENDPOINT
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const bucketName = context.cloudflare.env.BUCKET_NAME
|
const bucketEndpoint = cloudflare.env.BUCKET_ENDPOINT
|
||||||
|
const bucketName = cloudflare.env.BUCKET_NAME
|
||||||
const param = params['*'] ?? ''
|
const param = params['*'] ?? ''
|
||||||
const url = new URL(`${bucketName}/scorm/${param}`, bucketEndpoint)
|
const url = new URL(`${bucketName}/scorm/${param}`, bucketEndpoint)
|
||||||
const upstream = await fetch(url.toString())
|
const upstream = await fetch(url.toString())
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import type { Route } from './+types/verify'
|
|||||||
|
|
||||||
import { redirect } from 'react-router'
|
import { redirect } from 'react-router'
|
||||||
|
|
||||||
import { userContext } from '@repo/auth/context'
|
import { userContext, cloudflareContext } from '@repo/auth/context'
|
||||||
import { createSessionStorage } from '@repo/auth/session'
|
import { createSessionStorage } from '@repo/auth/session'
|
||||||
import { HttpMethod, request as req } from '@repo/util/request'
|
import { HttpMethod, request as req } from '@repo/util/request'
|
||||||
|
|
||||||
export async function loader({ params, request, context }: Route.LoaderArgs) {
|
export async function loader({ params, request, context }: Route.LoaderArgs) {
|
||||||
const { code } = params
|
const { code } = params
|
||||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
|
const sessionStorage = createSessionStorage(cloudflare.env)
|
||||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||||
const user = context.get(userContext)
|
const user = context.get(userContext)!
|
||||||
|
|
||||||
const r = await req({
|
const r = await req({
|
||||||
url: `/users/${user.sub}/emails/${code}/verify`,
|
url: `/users/${user.sub}/emails/${code}/verify`,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { formSchema, type Schema } from './emails/data'
|
|||||||
|
|
||||||
export async function action({ request, context }: Route.ActionArgs) {
|
export async function action({ request, context }: Route.ActionArgs) {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const user = context.get(userContext)
|
const user = context.get(userContext)!
|
||||||
const r = await req({
|
const r = await req({
|
||||||
url: `/users/${user.sub}`,
|
url: `/users/${user.sub}`,
|
||||||
method: HttpMethod.PATCH,
|
method: HttpMethod.PATCH,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { createRequestHandler, RouterContextProvider } from 'react-router'
|
import { createRequestHandler, RouterContextProvider } from 'react-router'
|
||||||
|
import { cloudflareContext } from '@repo/auth/context'
|
||||||
|
|
||||||
declare module 'react-router' {
|
declare module '@repo/auth/context' {
|
||||||
export interface AppLoadContext {
|
interface CloudflareEnv extends Env {}
|
||||||
cloudflare: {
|
interface CloudflareCtx extends ExecutionContext {}
|
||||||
env: Env
|
|
||||||
ctx: ExecutionContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestHandler = createRequestHandler(
|
const requestHandler = createRequestHandler(
|
||||||
@@ -16,11 +13,10 @@ const requestHandler = createRequestHandler(
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request, env, ctx) {
|
async fetch(request, env, ctx) {
|
||||||
const context = new RouterContextProvider()
|
const context = new RouterContextProvider(
|
||||||
|
new Map([[cloudflareContext, { env, ctx }]])
|
||||||
return requestHandler(
|
|
||||||
request,
|
|
||||||
Object.assign(context, { cloudflare: { env, ctx } })
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return requestHandler(request, context)
|
||||||
}
|
}
|
||||||
} satisfies ExportedHandler<Env>
|
} satisfies ExportedHandler<Env>
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ main = "./workers/app.ts"
|
|||||||
[placement]
|
[placement]
|
||||||
mode = "smart"
|
mode = "smart"
|
||||||
|
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
CLIENT_ID = "1a5483ab-4521-4702-9115-5857ac676851"
|
CLIENT_ID = "1a5483ab-4521-4702-9115-5857ac676851"
|
||||||
REDIRECT_URI = "https://scorm.eduseg.workers.dev/login"
|
|
||||||
SCOPE = "openid profile email offline_access"
|
SCOPE = "openid profile email offline_access"
|
||||||
API_URL = "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"
|
API_URL = "https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com"
|
||||||
ISSUER_URL = "https://id.saladeaula.digital"
|
ISSUER_URL = "https://id.saladeaula.digital"
|
||||||
|
|||||||
65
package-lock.json
generated
65
package-lock.json
generated
@@ -4398,6 +4398,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/history": {
|
||||||
|
"version": "4.7.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||||
|
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/js-cookie": {
|
"node_modules/@types/js-cookie": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
||||||
@@ -4465,6 +4472,17 @@
|
|||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-router": {
|
||||||
|
"version": "5.1.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||||
|
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/history": "^4.7.11",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/statuses": {
|
"node_modules/@types/statuses": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz",
|
||||||
@@ -5880,6 +5898,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
@@ -7309,29 +7350,6 @@
|
|||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/auth/node_modules/react-router": {
|
|
||||||
"version": "7.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
|
|
||||||
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": "^1.0.1",
|
|
||||||
"set-cookie-parser": "^2.6.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18",
|
|
||||||
"react-dom": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@repo/ui",
|
"name": "@repo/ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
@@ -7390,6 +7408,7 @@
|
|||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/react-router": "^5.1.20",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
"remix-auth-oauth2": "^3.4.1"
|
"remix-auth-oauth2": "^3.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"react-router": "^7.10.1",
|
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"react-router": "^7.10.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,16 @@ export type User = {
|
|||||||
refreshToken: string
|
refreshToken: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAuth(env, redirectURI = null) {
|
export function createAuth(
|
||||||
|
env: Record<string, any>,
|
||||||
|
redirectURI: string | null = null
|
||||||
|
) {
|
||||||
const authenticator = new Authenticator()
|
const authenticator = new Authenticator()
|
||||||
const strategy = new OAuth2Strategy(
|
const strategy = new OAuth2Strategy(
|
||||||
{
|
{
|
||||||
clientId: env.CLIENT_ID,
|
clientId: env.CLIENT_ID,
|
||||||
clientSecret: env.CLIENT_SECRET,
|
clientSecret: env.CLIENT_SECRET,
|
||||||
redirectURI: redirectURI ?? env.REDIRECT_URI,
|
redirectURI: (env?.REDIRECT_URI ?? redirectURI) || undefined,
|
||||||
authorizationEndpoint: `${env.ISSUER_URL}/authorize`,
|
authorizationEndpoint: `${env.ISSUER_URL}/authorize`,
|
||||||
tokenEndpoint: `${env.ISSUER_URL}/token`,
|
tokenEndpoint: `${env.ISSUER_URL}/token`,
|
||||||
tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`,
|
tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`,
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
import type { User } from '@/auth'
|
|
||||||
import { createContext } from 'react-router'
|
import { createContext } from 'react-router'
|
||||||
|
|
||||||
|
import type { User } from './auth'
|
||||||
|
|
||||||
export const userContext = createContext<User | null>(null)
|
export const userContext = createContext<User | null>(null)
|
||||||
export const requestIdContext = createContext<string | null>(null)
|
export const requestIdContext = createContext<string | null>(null)
|
||||||
|
|
||||||
|
export interface CloudflareEnv {}
|
||||||
|
export interface CloudflareCtx {}
|
||||||
|
|
||||||
|
export type CloudflareContextType = {
|
||||||
|
env: CloudflareEnv extends Record<string, never>
|
||||||
|
? Record<string, unknown>
|
||||||
|
: CloudflareEnv
|
||||||
|
ctx: CloudflareCtx extends Record<string, never> ? unknown : CloudflareCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudflareContext = createContext<CloudflareContextType>()
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { requestIdContext, userContext } from '@/context'
|
|
||||||
import { createSessionStorage } from '@/session'
|
|
||||||
|
|
||||||
import { createAuth, type User } from '@/auth'
|
|
||||||
import { decodeJwt } from 'jose'
|
import { decodeJwt } from 'jose'
|
||||||
import { redirect, type LoaderFunctionArgs } from 'react-router'
|
import { redirect, type LoaderFunctionArgs } from 'react-router'
|
||||||
import type { OAuth2Strategy } from 'remix-auth-oauth2'
|
import type { OAuth2Strategy } from 'remix-auth-oauth2'
|
||||||
|
|
||||||
|
import { requestIdContext, userContext, cloudflareContext } from '../context'
|
||||||
|
import { createSessionStorage } from '../session'
|
||||||
|
import { createAuth, type User } from '../auth'
|
||||||
|
|
||||||
export const authMiddleware = async (
|
export const authMiddleware = async (
|
||||||
{ request, context }: LoaderFunctionArgs,
|
{ request, context }: LoaderFunctionArgs,
|
||||||
next: () => Promise<Response>
|
next: () => Promise<Response>
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const sessionStorage = createSessionStorage(context.cloudflare.env)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
const authenticator = createAuth(context.cloudflare.env)
|
const sessionStorage = createSessionStorage(cloudflare.env)
|
||||||
|
const authenticator = createAuth(cloudflare.env)
|
||||||
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
|
const strategy = authenticator.get<OAuth2Strategy<User>>('oidc')
|
||||||
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
const session = await sessionStorage.getSession(request.headers.get('cookie'))
|
||||||
const requestId = context.get(requestIdContext)
|
const requestId = context.get(requestIdContext)
|
||||||
@@ -51,6 +52,7 @@ export const authMiddleware = async (
|
|||||||
session.set('user', user)
|
session.set('user', user)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// @ts-ignore
|
||||||
console.error(`[${requestId}]`, error?.stack)
|
console.error(`[${requestId}]`, error?.stack)
|
||||||
|
|
||||||
// If refreshing the token fails, remove the user from the current session
|
// If refreshing the token fails, remove the user from the current session
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { requestIdContext } from '@/context'
|
|
||||||
import { type LoaderFunctionArgs } from 'react-router'
|
import { type LoaderFunctionArgs } from 'react-router'
|
||||||
|
|
||||||
|
import { requestIdContext } from '../context'
|
||||||
|
|
||||||
export const loggingMiddleware = async (
|
export const loggingMiddleware = async (
|
||||||
{ request, context }: LoaderFunctionArgs,
|
{ request, context }: LoaderFunctionArgs,
|
||||||
next
|
next: () => Promise<Response>
|
||||||
) => {
|
): Promise<Response> => {
|
||||||
const requestId = crypto.randomUUID()
|
const requestId = crypto.randomUUID()
|
||||||
context.set(requestIdContext, requestId)
|
context.set(requestIdContext, requestId)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createCookieSessionStorage } from 'react-router'
|
import { createCookieSessionStorage } from 'react-router'
|
||||||
|
|
||||||
import { type User } from './auth'
|
import { type User } from './auth'
|
||||||
|
|
||||||
type SessionData = {
|
type SessionData = {
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
27
packages/ui/src/components/currency.tsx
Normal file
27
packages/ui/src/components/currency.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type CurrencyProps = {
|
||||||
|
children: number
|
||||||
|
options?: Intl.NumberFormatOptions
|
||||||
|
locale?: string
|
||||||
|
currency?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Currency({
|
||||||
|
children,
|
||||||
|
options,
|
||||||
|
locale = 'pt-BR',
|
||||||
|
currency = 'BRL'
|
||||||
|
}: CurrencyProps): ReactNode {
|
||||||
|
const optionsInit: Intl.NumberFormatOptions = {
|
||||||
|
style: 'currency',
|
||||||
|
currency
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatter = new Intl.NumberFormat(locale, {
|
||||||
|
...optionsInit,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
return formatter.format(children)
|
||||||
|
}
|
||||||
33
packages/ui/src/components/datetime.tsx
Normal file
33
packages/ui/src/components/datetime.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { ComponentPropsWithoutRef } from 'react'
|
||||||
|
|
||||||
|
type DateTimeProps = {
|
||||||
|
children: string
|
||||||
|
options?: Intl.DateTimeFormatOptions
|
||||||
|
locale?: string
|
||||||
|
} & ComponentPropsWithoutRef<'time'>
|
||||||
|
|
||||||
|
export function DateTime({
|
||||||
|
children,
|
||||||
|
options,
|
||||||
|
locale = 'pt-BR',
|
||||||
|
...props
|
||||||
|
}: DateTimeProps) {
|
||||||
|
const optionsInit: Intl.DateTimeFormatOptions = {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}
|
||||||
|
|
||||||
|
const datetime = new Intl.DateTimeFormat(locale, {
|
||||||
|
...optionsInit,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<time dateTime={children} {...props}>
|
||||||
|
{datetime.format(new Date(children))}
|
||||||
|
</time>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/react-router": "^5.1.20",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { User } from '@repo/auth/auth'
|
import {
|
||||||
import { requestIdContext, userContext } from '@repo/auth/context'
|
cloudflareContext,
|
||||||
|
requestIdContext,
|
||||||
|
userContext
|
||||||
|
} from '@repo/auth/context'
|
||||||
|
|
||||||
import type { LoaderFunctionArgs } from 'react-router'
|
import type { LoaderFunctionArgs } from 'react-router'
|
||||||
|
|
||||||
@@ -28,9 +31,10 @@ export function request({
|
|||||||
request: { signal },
|
request: { signal },
|
||||||
context
|
context
|
||||||
}: RequestArgs): Promise<Response> {
|
}: RequestArgs): Promise<Response> {
|
||||||
const requestId = context.get(requestIdContext) as string
|
const requestId = context.get(requestIdContext)
|
||||||
const user = context.get(userContext) as User
|
const user = context.get(userContext)
|
||||||
const url_ = new URL(url, context.cloudflare.env.API_URL)
|
const cloudflare = context.get(cloudflareContext)
|
||||||
|
const url_ = new URL(url, cloudflare.env.API_URL)
|
||||||
const headers = new Headers({
|
const headers = new Headers({
|
||||||
Authorization: `Bearer ${user.accessToken}`
|
Authorization: `Bearer ${user.accessToken}`
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
Reference in New Issue
Block a user