feat: WIP web auth routes

pull/715/head
Travis Fischer 2025-06-15 10:20:08 +07:00
rodzic 7f727bce1e
commit e4b9d08e97
30 zmienionych plików z 1206 dodań i 277 usunięć

Wyświetl plik

@ -1,7 +1,6 @@
import { issuer } from '@agentic/openauth'
import { GithubProvider } from '@agentic/openauth/provider/github'
import { PasswordProvider } from '@agentic/openauth/provider/password'
import { PasswordUI } from '@agentic/openauth/ui/password'
import { assert, pick } from '@agentic/platform-core'
import { isValidPassword } from '@agentic/platform-validators'
@ -15,7 +14,8 @@ import { getGitHubClient } from '@/lib/external/github'
import { resend } from './lib/external/resend'
// Initialize OpenAuth issuer which is a Hono app for all auth routes.
export const authRouter = issuer({
// TODO: fix this type...
export const authRouter: any = issuer({
subjects,
storage: DrizzleAuthStorage(),
ttl: {
@ -25,70 +25,45 @@ export const authRouter = issuer({
// access: 60 * 60 * 24 * 366, // 1 year
// refresh: 60 * 60 * 24 * 365 * 5 // 5 years
},
theme: {
title: 'Agentic',
logo: {
dark: 'https://vercel.com/mktng/_next/static/media/vercel-logotype-dark.e8c0a742.svg',
light:
'https://vercel.com/mktng/_next/static/media/vercel-logotype-light.700a8d26.svg'
},
background: {
dark: 'black',
light: 'white'
},
primary: {
dark: 'white',
light: 'black'
},
font: {
family: 'Geist, sans-serif'
},
css: `
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100;200;300;400;500;600;700;800;900&display=swap');
`
},
providers: {
github: GithubProvider({
clientID: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
scopes: ['user:email']
}),
password: PasswordProvider(
PasswordUI({
copy: {
register_title: 'Welcome to Agentic',
login_title: 'Welcome to Agentic'
},
sendCode: async (email, code) => {
// eslint-disable-next-line no-console
console.log('sending verify code email', { email, code })
password: PasswordProvider({
loginUrl: async () => `${env.WEB_AUTH_BASE_URL}/login`,
registerUrl: async () => `${env.WEB_AUTH_BASE_URL}/signup`,
changeUrl: async () => `${env.WEB_AUTH_BASE_URL}/forgot-password`,
sendCode: async (email, code) => {
// eslint-disable-next-line no-console
console.log('sending verify code email', { email, code })
await resend.sendVerifyCodeEmail({ code, to: email })
},
validatePassword: (password) => {
if (password.length < 3) {
return 'Password must be at least 3 characters'
}
if (password.length > 1024) {
return 'Password must be less than 1024 characters'
}
if (!isValidPassword(password)) {
return 'Invalid password'
}
return undefined
await resend.sendVerifyCodeEmail({ code, to: email })
},
validatePassword: (password) => {
if (password.length < 3) {
return 'Password must be at least 3 characters'
}
})
)
if (password.length > 1024) {
return 'Password must be less than 1024 characters'
}
if (!isValidPassword(password)) {
return 'Invalid password'
}
return undefined
}
})
},
success: async (ctx, value) => {
const { provider } = value
let user: RawUser | undefined
// eslint-disable-next-line no-console
console.log('Auth success', provider, ctx, JSON.stringify(value, null, 2))
console.log('Auth success', provider, JSON.stringify(value, null, 2))
function getPartialOAuthAccount() {
assert(provider === 'github', `Unsupported OAuth provider "${provider}"`)

Wyświetl plik

@ -10,7 +10,7 @@ import type { RawProject } from './types'
*
* This hash is used as the key for the `Project._stripePriceIdMap`.
*/
export function getPricingPlanLineItemHashForStripePrice({
export async function getPricingPlanLineItemHashForStripePrice({
pricingPlan,
pricingPlanLineItem,
project
@ -18,7 +18,7 @@ export function getPricingPlanLineItemHashForStripePrice({
pricingPlan: PricingPlan
pricingPlanLineItem: PricingPlanLineItem
project: RawProject
}) {
}): Promise<string> {
// TODO: use pricingPlan.slug as well here?
// TODO: not sure if this is needed or not...
// With pricing plan slug:
@ -30,7 +30,7 @@ export function getPricingPlanLineItemHashForStripePrice({
// - 'price:base:<hash>'
// - 'price:requests:<hash>'
const hash = hashObject({
const hash = await hashObject({
...pricingPlanLineItem,
projectId: project.id,
stripeAccountId: project._stripeAccountId,
@ -40,7 +40,7 @@ export function getPricingPlanLineItemHashForStripePrice({
return `price:${pricingPlan.slug}:${pricingPlanLineItem.slug}:${hash}`
}
export function getStripePriceIdForPricingPlanLineItem({
export async function getStripePriceIdForPricingPlanLineItem({
pricingPlan,
pricingPlanLineItem,
project
@ -48,12 +48,13 @@ export function getStripePriceIdForPricingPlanLineItem({
pricingPlan: PricingPlan
pricingPlanLineItem: PricingPlanLineItem
project: RawProject
}): string | undefined {
const pricingPlanLineItemHash = getPricingPlanLineItemHashForStripePrice({
pricingPlan,
pricingPlanLineItem,
project
})
}): Promise<string | undefined> {
const pricingPlanLineItemHash =
await getPricingPlanLineItemHashForStripePrice({
pricingPlan,
pricingPlanLineItem,
project
})
return project._stripePriceIdMap[pricingPlanLineItemHash]
}

Wyświetl plik

@ -133,7 +133,7 @@ export async function upsertStripePricing({
}
const pricingPlanLineItemHashForStripePrice =
getPricingPlanLineItemHashForStripePrice({
await getPricingPlanLineItemHashForStripePrice({
pricingPlan,
pricingPlanLineItem,
project

Wyświetl plik

@ -110,9 +110,9 @@ export async function upsertStripeSubscription(
`Unable to update stripe subscription for invalid pricing plan "${plan}"`
)
const items: Stripe.SubscriptionUpdateParams.Item[] =
pricingPlan.lineItems.map((lineItem) => {
const priceId = getStripePriceIdForPricingPlanLineItem({
const items: Stripe.SubscriptionUpdateParams.Item[] = await Promise.all(
pricingPlan.lineItems.map(async (lineItem) => {
const priceId = await getStripePriceIdForPricingPlanLineItem({
pricingPlan,
pricingPlanLineItem: lineItem,
project
@ -136,6 +136,7 @@ export async function upsertStripeSubscription(
}
}
})
)
// Sanity check that LineItems we think should exist are all present in
// the current subscription's items.
@ -221,9 +222,9 @@ export async function upsertStripeSubscription(
`Unable to update stripe subscription for invalid pricing plan "${plan}"`
)
const items: Stripe.SubscriptionCreateParams.Item[] =
pricingPlan.lineItems.map((lineItem) => {
const priceId = getStripePriceIdForPricingPlanLineItem({
const items: Stripe.SubscriptionCreateParams.Item[] = await Promise.all(
pricingPlan.lineItems.map(async (lineItem) => {
const priceId = await getStripePriceIdForPricingPlanLineItem({
pricingPlan,
pricingPlanLineItem: lineItem,
project
@ -251,6 +252,7 @@ export async function upsertStripeSubscription(
}
}
})
)
assert(
items.length,

Wyświetl plik

@ -9,6 +9,7 @@ import { z } from 'zod'
export const envSchema = baseEnvSchema
.extend({
DATABASE_URL: z.string().url(),
WEB_AUTH_BASE_URL: z.string().url(),
PORT: z.number().default(3001),

Wyświetl plik

@ -1,12 +1,12 @@
import { assert, timingSafeCompare } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createMiddleware } from 'hono/factory'
import type { RawUser } from '@/db'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { authClient } from '@/lib/auth/client'
import { subjects } from '@/lib/auth/subjects'
import { env } from '../env'
import { env } from '@/lib/env'
import { timingSafeCompare } from '@/lib/utils'
export const authenticate = createMiddleware<AuthenticatedHonoEnv>(
async function authenticateMiddleware(ctx, next) {

Wyświetl plik

@ -0,0 +1,13 @@
import { timingSafeEqual } from 'node:crypto'
export function timingSafeCompare(a: string, b: string): boolean {
if (typeof a !== 'string' || typeof b !== 'string') {
return false
}
if (a.length !== b.length) {
return false
}
return timingSafeEqual(Buffer.from(a), Buffer.from(b))
}

Wyświetl plik

@ -33,7 +33,7 @@ export async function getRequestCacheKey(
if (contentLength > 0) {
if (type.includes('json')) {
const bodyJson: any = await request.clone().json()
hash = hashObject(bodyJson)
hash = await hashObject(bodyJson)
} else if (type.includes('text/')) {
const bodyString = await request.clone().text()
hash = await sha256(bodyString)

Wyświetl plik

@ -1,4 +1,6 @@
STRIPE_PUBLISHABLE_KEY=
NEXT_PUBLIC_API_BASE_URL=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
# Optional website analytics

Wyświetl plik

@ -28,8 +28,10 @@
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-form": "^1.12.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"ky": "catalog:",
"lucide-react": "^0.515.0",
"motion": "^12.18.1",
"next": "^15.3.3",
@ -45,6 +47,7 @@
"type-fest": "catalog:"
},
"devDependencies": {
"@agentic/openauth": "catalog:",
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/typography": "^0.5.16",
"@types/react": "catalog:",

Wyświetl plik

@ -0,0 +1,77 @@
'use client'
import type { AuthorizeResult } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import { useLocalStorage } from 'react-use'
import { useAgentic } from '@/components/agentic-provider'
// function FieldInfo({ field }: { field: AnyFieldApi }) {
// return (
// <>
// {field.state.meta.isTouched && !field.state.meta.isValid ? (
// <em>{field.state.meta.errors.join(',')}</em>
// ) : null}
// {field.state.meta.isValidating ? 'Validating...' : null}
// </>
// )
// }
export default async function Page({
params
}: {
params: Promise<{ provider: string }>
}) {
const { provider } = await params
assert(provider, 'Missing provider')
return <SuccessPage provider={provider} />
}
function SuccessPage({ provider }: { provider: string }) {
const searchParams = useSearchParams()
const code = searchParams.get('code')
const { api } = useAgentic()
const [authResult] = useLocalStorage<AuthorizeResult | undefined>(
'auth-result'
)
useEffect(() => {
;(async function () {
if (!code) {
// TODO
throw new Error('Missing code or challenge')
}
if (!authResult) {
// TODO
throw new Error('Missing auth-result')
}
if (!authResult.challenge) {
// TODO
throw new Error('Missing challenge')
}
const authUser = await api.exchangeAuthCode({
code,
redirectUri: new URL(
`/auth/${provider}/success`,
globalThis.window.location.origin
).toString(),
verifier: authResult.challenge?.verifier
})
console.log('AUTH SUCCESS', {
authUser,
authTokens: api.authTokens
})
})()
}, [code, api, authResult, provider])
// TODO: show a loading state
return null
}

Wyświetl plik

@ -0,0 +1,129 @@
'use client'
import type {
PasswordChangeError,
PasswordChangeState
} from '@agentic/openauth/provider/password'
import { useState } from 'react'
import { authCopy } from '@/lib/auth-copy'
export default function Page() {
// TODO
const [error, setError] = useState<PasswordChangeError | undefined>(undefined)
const [state, setState] = useState<PasswordChangeState>({
type: 'start',
redirect: '/' // TODO
})
const [form, setForm] = useState<FormData | undefined>(undefined)
const passwordError = [
'invalid_password',
'password_mismatch',
'validation_error'
].includes(error?.type || '')
return (
<>
<section>
<h1 className='my-0! text-center text-balance leading-snug md:leading-none'>
{authCopy.change_prompt}
</h1>
<form data-component='form' method='post'>
{/* <FormAlert
message={
error?.type
? error.type === 'validation_error'
? (error.message ?? authCopy?.[`error_${error.type}`])
: authCopy?.[`error_${error.type}`]
: undefined
}
/> */}
{state.type === 'start' && (
<>
<input type='hidden' name='action' value='code' />
<input
data-component='input'
autoFocus
type='email'
name='email'
required
value={form?.get('email')?.toString()}
placeholder={authCopy.input_email}
/>
</>
)}
{state.type === 'code' && (
<>
<input type='hidden' name='action' value='verify' />
<input
data-component='input'
autoFocus
name='code'
minLength={6}
maxLength={6}
required
placeholder={authCopy.input_code}
autoComplete='one-time-code'
/>
</>
)}
{state.type === 'update' && (
<>
<input type='hidden' name='action' value='update' />
<input
data-component='input'
autoFocus
type='password'
name='password'
placeholder={authCopy.input_password}
required
value={!passwordError ? form?.get('password')?.toString() : ''}
autoComplete='new-password'
/>
<input
data-component='input'
type='password'
name='repeat'
required
value={!passwordError ? form?.get('password')?.toString() : ''}
placeholder={authCopy.input_repeat}
autoComplete='new-password'
/>
</>
)}
<button data-component='button'>{authCopy.button_continue}</button>
</form>
{state.type === 'code' && (
<form method='post'>
<input type='hidden' name='action' value='code' />
<input type='hidden' name='email' value={state.email} />
{state.type === 'code' && (
<div data-component='form-footer'>
<span>
{authCopy.code_return}{' '}
<a data-component='link' href='/login'>
{authCopy.login.toLowerCase()}
</a>
</span>
<button data-component='link'>{authCopy.code_resend}</button>
</div>
)}
</form>
)}
</section>
</>
)
}

Wyświetl plik

@ -5,6 +5,7 @@ import cs from 'clsx'
import { Geist } from 'next/font/google'
import { Toaster } from 'sonner'
import { AgenticProvider } from '@/components/agentic-provider'
import { Bootstrap } from '@/components/bootstrap'
import { Footer } from '@/components/footer'
import { Header } from '@/components/header'
@ -50,22 +51,24 @@ export default function RootLayout({
<html lang='en' suppressHydrationWarning>
<body className={`${geist.variable} antialiased`}>
<PostHogProvider>
<ThemeProvider
attribute='class'
defaultTheme='dark'
disableTransitionOnChange
>
<div className={styles.root}>
<Header />
<AgenticProvider>
<ThemeProvider
attribute='class'
defaultTheme='dark'
disableTransitionOnChange
>
<div className={styles.root}>
<Header />
<main className={cs(styles.main, 'pt-8 pb-16 px-4 md:px-0')}>
{children}
</main>
<main className={cs(styles.main, 'pt-8 pb-16 px-4 md:px-0')}>
{children}
</main>
<Toaster richColors />
<Footer />
</div>
</ThemeProvider>
<Toaster richColors />
<Footer />
</div>
</ThemeProvider>
</AgenticProvider>
</PostHogProvider>
<Bootstrap />

Wyświetl plik

@ -0,0 +1,199 @@
'use client'
import type { PasswordLoginError } from '@agentic/openauth/provider/password'
import type { AuthorizeResult } from '@agentic/platform-api-client'
import { isValidEmail, isValidPassword } from '@agentic/platform-validators'
import { useForm } from '@tanstack/react-form'
import ky from 'ky'
import { useEffect, useState } from 'react'
import { useCookie, useLocalStorage } from 'react-use'
import { z } from 'zod'
import { useAgentic } from '@/components/agentic-provider'
import { authCopy } from '@/lib/auth-copy'
// function FieldInfo({ field }: { field: AnyFieldApi }) {
// return (
// <>
// {field.state.meta.isTouched && !field.state.meta.isValid ? (
// <em>{field.state.meta.errors.join(',')}</em>
// ) : null}
// {field.state.meta.isValidating ? 'Validating...' : null}
// </>
// )
// }
export default function Page() {
const [error] = useState<PasswordLoginError | undefined>(undefined)
const { api } = useAgentic()
const [localAuthResult, setLocalAuthResult] = useState<
AuthorizeResult | undefined
>(undefined)
const [_, setAuthResult] = useLocalStorage<AuthorizeResult | undefined>(
'auth-result'
)
const [authorizationCookie] = useCookie('authorization')
useEffect(() => {
;(async function () {
if (api && !localAuthResult && !authorizationCookie) {
const authResult = await api.initAuthFlow({
provider: 'password',
redirectUri: new URL(
`/auth/password/success`,
globalThis.window.location.origin
).toString()
})
console.log('authResult', authResult, {
provider: 'password',
redirectUri: new URL(
`/auth/password/success`,
globalThis.window.location.origin
).toString()
})
const res2 = await ky.get(authResult.url)
console.log('authResult2', res2)
console.log('authorizationCookie', res2.headers.get('Set-Cookie'))
setLocalAuthResult(authResult)
setAuthResult(authResult)
}
})()
}, [
localAuthResult,
setLocalAuthResult,
setAuthResult,
api,
authorizationCookie
])
const form = useForm({
defaultValues: {
email: '',
password: ''
},
validators: {
onBlurAsync: z.object({
email: z
.string()
.email()
.refine((email) => isValidEmail(email)),
password: z.string().refine((password) => isValidPassword(password))
})
},
onSubmit: async ({ value }) => {
try {
const body = new FormData()
body.append('email', value.email)
body.append('password', value.password)
console.log('authorizationCookie', authorizationCookie)
const res = await api.ky
.post('password/authorize', { body })
.json<any>()
if (res.error) {
console.error('login error', res.error)
} else {
console.log('login success', res)
}
} catch (err) {
console.error('login error', err)
}
}
})
return (
<>
<section>
<h1 className='my-0! text-center text-balance leading-snug md:leading-none'>
Log In
</h1>
<form
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
{/* <FormAlert
message={error?.type && authCopy?.[`error_${error.type}`]}
/> */}
<form.Field
name='email'
children={(field) => (
<>
{/* <label htmlFor={field.name}>Email:</label> */}
<input
id={field.name}
name={field.name}
type='email'
required
placeholder={authCopy.input_email}
autoFocus={!error}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{/* <FieldInfo field={field} /> */}
</>
)}
/>
<form.Field
name='password'
children={(field) => (
<>
{/* <label htmlFor={field.name}>Password:</label> */}
<input
id={field.name}
name={field.name}
type='password'
required
placeholder={authCopy.input_password}
autoFocus={error?.type === 'invalid_password'}
autoComplete='current-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{/* <FieldInfo field={field} /> */}
</>
)}
/>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type='submit' disabled={!canSubmit}>
{isSubmitting ? '...' : authCopy.button_continue}
</button>
)}
/>
<div data-component='form-footer'>
<span>
{authCopy.register_prompt}{' '}
<a data-component='link' href='/signup'>
{authCopy.register}
</a>
</span>
<a data-component='link' href='/forgot-password'>
{authCopy.change_prompt}
</a>
</div>
</form>
</section>
</>
)
}

Wyświetl plik

@ -0,0 +1,121 @@
'use client'
import type {
PasswordRegisterError,
PasswordRegisterState
} from '@agentic/openauth/provider/password'
import { useState } from 'react'
import { authCopy } from '@/lib/auth-copy'
export default function Page() {
// TODO
const [error, setError] = useState<PasswordRegisterError | undefined>(
undefined
)
const [state, setState] = useState<PasswordRegisterState>({ type: 'start' })
const [form, setForm] = useState<FormData | undefined>(undefined)
const emailError = ['invalid_email', 'email_taken'].includes(
error?.type || ''
)
const passwordError = [
'invalid_password',
'password_mismatch',
'validation_error'
].includes(error?.type || '')
return (
<>
<section>
<h1 className='my-0! text-center text-balance leading-snug md:leading-none'>
{authCopy.register}
</h1>
<form data-component='form' method='post'>
{/* <FormAlert
message={
error?.type
? error.type === 'validation_error'
? (error.message ?? authCopy?.[`error_${error.type}`])
: authCopy?.[`error_${error.type}`]
: undefined
}
/> */}
{state.type === 'start' && (
<>
<input type='hidden' name='action' value='register' />
<input
data-component='input'
autoFocus={!error || emailError}
type='email'
name='email'
value={!emailError ? form?.get('email')?.toString() : ''}
required
placeholder={authCopy.input_email}
/>
<input
data-component='input'
autoFocus={passwordError}
type='password'
name='password'
placeholder={authCopy.input_password}
required
value={!passwordError ? form?.get('password')?.toString() : ''}
autoComplete='new-password'
/>
<input
data-component='input'
type='password'
name='repeat'
required
autoFocus={passwordError}
placeholder={authCopy.input_repeat}
autoComplete='new-password'
/>
<button data-component='button'>
{authCopy.button_continue}
</button>
<div data-component='form-footer'>
<span>
{authCopy.login_prompt}{' '}
<a data-component='link' href='/login'>
{authCopy.login}
</a>
</span>
</div>
</>
)}
{state.type === 'code' && (
<>
<input type='hidden' name='action' value='verify' />
<input
data-component='input'
autoFocus
name='code'
minLength={6}
maxLength={6}
required
placeholder={authCopy.input_code}
autoComplete='one-time-code'
/>
<button data-component='button'>
{authCopy.button_continue}
</button>
</>
)}
</form>
</section>
</>
)
}

Wyświetl plik

@ -0,0 +1,38 @@
'use client'
import { AgenticApiClient } from '@agentic/platform-api-client'
import React from 'react'
import * as config from '@/lib/config'
type AgenticContextType = {
api: AgenticApiClient
}
const defaultAgenticContext: AgenticContextType = {
api: new AgenticApiClient({
apiBaseUrl: config.apiBaseUrl
})
}
const AgenticContext = React.createContext<AgenticContextType | undefined>(
undefined
)
export function AgenticProvider({ children }: { children: React.ReactNode }) {
return (
<AgenticContext.Provider value={defaultAgenticContext}>
{children}
</AgenticContext.Provider>
)
}
export function useAgentic() {
const agenticContext = React.useContext(AgenticContext)
if (!agenticContext) {
throw new Error('useAgentic must be used within an AgenticProvider')
}
return agenticContext
}

Wyświetl plik

@ -10,7 +10,7 @@ export function Header() {
<header className={styles.header}>
<div className={styles.headerContent}>
<ActiveLink className={styles.logo} href='/'>
Lumon
AGENTIC
</ActiveLink>
<div className='md:hidden'>

Wyświetl plik

@ -0,0 +1,128 @@
import type {
PasswordChangeError,
PasswordLoginError,
PasswordRegisterError
} from '@agentic/openauth/provider/password'
export const authCopy = {
/**
* Error message when email is already taken.
*/
error_email_taken: 'There is already an account with this email.',
/**
* Error message when the confirmation code is incorrect.
*/
error_invalid_code: 'Code is incorrect.',
/**
* Error message when the email is invalid.
*/
error_invalid_email: 'Email is not valid.',
/**
* Error message when the password is incorrect.
*/
error_invalid_password: 'Password is incorrect.',
/**
* Error message when the passwords do not match.
*/
error_password_mismatch: 'Passwords do not match.',
/**
* Error message when the user enters a password that fails validation.
*/
error_validation_error: 'Password does not meet requirements.',
/**
* Title of the register page.
*/
register_title: 'Welcome to Agentic',
/**
* Description of the register page.
*/
register_description: 'Sign in with your email',
/**
* Title of the login page.
*/
login_title: 'Welcome to Agentic',
/**
* Description of the login page.
*/
login_description: 'Sign in with your email',
/**
* Copy for the register button.
*/
register: 'Sign Up',
/**
* Copy for the register link.
*/
register_prompt: "Don't have an account?",
/**
* Copy for the login link.
*/
login_prompt: 'Already have an account?',
/**
* Copy for the login button.
*/
login: 'Login',
/**
* Copy for the forgot password link.
*/
change_prompt: 'Forgot password?',
/**
* Copy for the resend code button.
*/
code_resend: 'Resend code',
/**
* Copy for the "Back to" link.
*/
code_return: 'Back to',
/**
* Copy for the logo.
* @internal
*/
logo: 'A',
/**
* Copy for the email input.
*/
input_email: 'Email',
/**
* Copy for the password input.
*/
input_password: 'Password',
/**
* Copy for the code input.
*/
input_code: 'Code',
/**
* Copy for the repeat password input.
*/
input_repeat: 'Repeat password',
/**
* Copy for the continue button.
*/
button_continue: 'Continue'
} satisfies {
[key in `error_${
| PasswordLoginError['type']
| PasswordRegisterError['type']
| PasswordChangeError['type']}`]: string
} & Record<string, string>

Wyświetl plik

@ -50,7 +50,8 @@ export const prodUrl = `https://${domain}`
export const url = isDev ? `http://localhost:${port}` : prodUrl
export const vercelUrl =
process.env.VERCEL_URL ?? process.env.NEXT_PUBLIC_VERCEL_URL
export const apiBaseUrl = isDev || !vercelUrl ? url : `https://${vercelUrl}`
// export const webBaseUrl = isDev || !vercelUrl ? url : `https://${vercelUrl}`
export const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL!
export const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY!
export const posthogHost =

Wyświetl plik

@ -58,5 +58,9 @@
"prettier --ignore-unknown --write",
"eslint --cache --fix"
]
},
"dependencies": {
"@agentic/openauth": "link:../../temp/openauth/packages/openauth",
"openauthjs": "link:../../temp/openauth"
}
}

Wyświetl plik

@ -22,8 +22,11 @@
"test:unit": "vitest run"
},
"dependencies": {
"hash-object": "catalog:",
"decircular": "^1.0.0",
"is-obj": "^3.0.0",
"parse-json": "catalog:",
"sort-keys": "catalog:",
"type-fest": "catalog:",
"zod": "catalog:",
"zod-validation-error": "catalog:"
},

Wyświetl plik

@ -0,0 +1,33 @@
import { describe, expect, test } from 'vitest'
import { hashObject } from './hash-object'
describe('hashObject', () => {
test('basic', async () => {
await expect(hashObject({ unicorn: 'rainbow' })).resolves.toBe(
'0bdeed89f3fbb21d7c4fa488992470030e98387c4ad3f4e18cebb70d7dac59dd'
)
await expect(hashObject({ a: 0, b: { a: 0, b: 0 } })).resolves.toBe(
await hashObject({ b: { b: 0, a: 0 }, a: 0 })
)
await expect(hashObject({ a: 'b' })).resolves.not.toBe(
await hashObject({ a: 'c' })
)
})
test('handles circular references', async () => {
const object = {
a: {
b: {}
}
}
object.a.b = object // Create a circular reference.
await expect(hashObject(object)).resolves.toBe(
'fe15b32f1f303e18ac292a995c18e0560c1031d0a7a5999a1c5cacea06cead87'
)
})
})

Wyświetl plik

@ -0,0 +1,52 @@
import type { LiteralUnion } from 'type-fest'
import decircular from 'decircular'
import isObject from 'is-obj'
import sortKeys from 'sort-keys'
import { sha256 } from './utils'
export type Algorithm = LiteralUnion<
'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512',
string
>
export type HashObjectOptions = {
/** @default 'SHA-256' */
readonly algorithm?: Algorithm
}
function normalizeObject(object: any): any {
if (typeof object === 'string') {
return object.normalize('NFD')
}
if (Array.isArray(object)) {
return object.map((element) => normalizeObject(element))
}
if (isObject(object)) {
return Object.fromEntries(
Object.entries(object).map(([key, value]) => [
key.normalize('NFD'),
normalizeObject(value)
])
)
}
return object
}
/**
* Returns a stable, deterministic hash of the given object, defaulting to
* using `sha256` as the hashing algorithm and `hex` as the encoding.
*/
export async function hashObject(object: Record<string, any>): Promise<string> {
if (!isObject(object)) {
throw new TypeError('Expected an object')
}
const normalizedObject = normalizeObject(decircular(object))
const input = JSON.stringify(sortKeys(normalizedObject, { deep: true }))
return sha256(input)
}

Wyświetl plik

@ -1,4 +1,5 @@
export * from './errors'
export * from './hash-object'
export * from './rate-limit-headers'
export type * from './types'
export * from './utils'

Wyświetl plik

@ -1,7 +1,4 @@
import { timingSafeEqual } from 'node:crypto'
import type { z, ZodType } from 'zod'
import hashObjectImpl, { type Options as HashObjectOptions } from 'hash-object'
import { HttpError, ZodValidationError } from './errors'
@ -101,11 +98,6 @@ export function parseZodSchema<TSchema extends ZodType<any, any, any>>(
}
}
// import { createHash, randomUUID } from 'node:crypto'
// export function sha256Node(input: string = randomUUID()) {
// return createHash('sha256').update(input).digest('hex')
// }
export async function sha256(
input: string | ArrayBuffer | ArrayBufferView = crypto.randomUUID()
) {
@ -125,21 +117,6 @@ export async function sha256(
return hashHex
}
/**
* Returns a stable, deterministic hash of the given object, defaulting to
* using `sha256` as the hashing algorithm and `hex` as the encoding.
*/
export function hashObject(
object: Record<string, any>,
options?: HashObjectOptions
): string {
return hashObjectImpl(object, {
algorithm: 'sha256',
encoding: 'hex',
...options
})
}
export function getEnv(name: string): string | undefined {
try {
return typeof process !== 'undefined'
@ -303,15 +280,3 @@ export function pruneEmptyDeep<T>(
return value as any
}
export function timingSafeCompare(a: string, b: string): boolean {
if (typeof a !== 'string' || typeof b !== 'string') {
return false
}
if (a.length !== b.length) {
return false
}
return timingSafeEqual(Buffer.from(a), Buffer.from(b))
}

Wyświetl plik

@ -18,7 +18,7 @@ export const accessLogger = unless(
// Ignore health check route
'/v1/health',
// Ignore openauth routes
'/authorize',
// '/authorize',
'/userinfo',
'/token',
'/.well-known/jwks.json',

Wyświetl plik

@ -29,7 +29,7 @@ addFormats(globalAjv)
*
* @see https://github.com/ajv-validator/ajv/issues/2318
*/
export function validateJsonSchema<T = unknown>({
export async function validateJsonSchema<T = unknown>({
schema,
data,
ajv = globalAjv,
@ -39,7 +39,7 @@ export function validateJsonSchema<T = unknown>({
data: unknown
ajv?: Ajv
errorMessage?: string
}): T {
}): Promise<T> {
assert(schema, 400, '`schema` is required')
const isSchemaObject =
typeof schema === 'object' &&
@ -67,7 +67,7 @@ export function validateJsonSchema<T = unknown>({
}
}
const schemaHashKey = hashObject(schema)
const schemaHashKey = await hashObject(schema)
let validate = ajv.getSchema(schemaHashKey) as ValidateFunction<T>
if (!validate) {
validate = ajv.compile<T>(schema)

Wyświetl plik

@ -6,9 +6,24 @@ settings:
catalogs:
default:
'@agentic/openauth':
specifier: ^0.4.3
version: 0.4.3
'@apideck/better-ajv-errors':
specifier: ^0.3.6
version: 0.3.6
'@clack/prompts':
specifier: ^0.11.0
version: 0.11.0
'@cloudflare/workers-types':
specifier: ^4.20250610.0
version: 4.20250610.0
'@commander-js/extra-typings':
specifier: ^14.0.0
version: 14.0.0
'@edge-runtime/vm':
specifier: ^5.0.0
version: 5.0.0
'@fisch0920/config':
specifier: ^1.1.2
version: 1.1.2
'@fisch0920/drizzle-orm':
specifier: ^0.43.7
version: 0.43.7
@ -18,68 +33,250 @@ catalogs:
'@hono/node-server':
specifier: ^1.14.4
version: 1.14.4
'@hono/sentry':
specifier: ^1.2.2
version: 1.2.2
'@hono/zod-openapi':
specifier: ^0.19.8
version: 0.19.8
'@hono/zod-validator':
specifier: ^0.7.0
version: 0.7.0
'@modelcontextprotocol/sdk':
specifier: ^1.12.1
version: 1.12.1
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@react-email/components':
specifier: ^0.0.42
version: 0.0.42
'@redocly/openapi-core':
specifier: ^1.34.3
version: 1.34.3
'@sentry/cli':
specifier: ^2.46.0
version: 2.46.0
'@sentry/cloudflare':
specifier: ^9.28.1
version: 9.28.1
'@sentry/core':
specifier: ^9.28.1
version: 9.28.1
'@sentry/node':
specifier: ^9.28.1
version: 9.28.1
'@types/ms':
specifier: ^2.1.0
version: 2.1.0
'@types/node':
specifier: ^22.15.29
version: 22.15.31
'@types/react':
specifier: ^19.1.8
version: 19.1.8
'@types/react-dom':
specifier: ^19.1.6
version: 19.1.6
'@types/semver':
specifier: ^7.7.0
version: 7.7.0
agents:
specifier: ^0.0.95
version: 0.0.95
ajv:
specifier: ^8.17.1
version: 8.17.1
ajv-formats:
specifier: ^3.0.1
version: 3.0.1
camelcase:
specifier: ^8.0.0
version: 8.0.0
commander:
specifier: ^14.0.0
version: 14.0.0
conf:
specifier: ^14.0.0
version: 14.0.0
decamelize:
specifier: ^6.0.0
version: 6.0.0
del-cli:
specifier: ^6.0.0
version: 6.0.0
drizzle-kit:
specifier: ^0.31.1
version: 0.31.1
drizzle-orm:
specifier: ^0.44.2
version: 0.44.2
email-validator:
specifier: ^2.0.4
version: 2.0.4
eslint:
specifier: ^9.28.0
version: 9.28.0
eslint-plugin-drizzle:
specifier: ^0.2.3
version: 0.2.3
eventid:
specifier: ^2.0.1
version: 2.0.1
exit-hook:
specifier: ^4.0.0
version: 4.0.0
fast-content-type-parse:
specifier: ^3.0.0
version: 3.0.0
fast-uri:
specifier: ^3.0.6
version: 3.0.6
fastmcp:
specifier: ^3.1.1
version: 3.1.1
get-port:
specifier: ^7.1.0
version: 7.1.0
hono:
specifier: ^4.7.11
version: 4.7.11
knip:
specifier: ^5.60.2
version: 5.60.2
ky:
specifier: ^1.8.1
version: 1.8.1
lint-staged:
specifier: ^16.1.0
version: 16.1.0
ms:
specifier: ^2.1.3
version: 2.1.3
npm-run-all2:
specifier: ^8.0.4
version: 8.0.4
octokit:
specifier: ^5.0.3
version: 5.0.3
only-allow:
specifier: ^1.2.1
version: 1.2.1
open:
specifier: ^10.1.2
version: 10.1.2
openapi-typescript:
specifier: ^7.8.0
version: 7.8.0
ora:
specifier: ^8.2.0
version: 8.2.0
p-all:
specifier: ^5.0.0
version: 5.0.0
p-map:
specifier: ^7.0.3
version: 7.0.3
parse-json:
specifier: ^8.3.0
version: 8.3.0
plur:
specifier: ^5.1.0
version: 5.1.0
postgres:
specifier: ^3.4.7
version: 3.4.7
prettier:
specifier: ^3.5.3
version: 3.5.3
react:
specifier: ^19.1.0
version: 19.1.0
react-dom:
specifier: ^19.1.0
version: 19.1.0
react-email:
specifier: ^4.0.16
version: 4.0.16
resend:
specifier: ^4.5.2
version: 4.5.2
restore-cursor:
specifier: ^5.1.0
version: 5.1.0
semver:
specifier: ^7.7.2
version: 7.7.2
simple-git-hooks:
specifier: ^2.13.0
version: 2.13.0
sort-keys:
specifier: ^5.1.0
version: 5.1.0
stripe:
specifier: ^18.2.1
version: 18.2.1
tsup:
specifier: ^8.5.0
version: 8.5.0
tsx:
specifier: ^4.20.1
version: 4.20.1
turbo:
specifier: ^2.5.4
version: 2.5.4
type-fest:
specifier: ^4.41.0
version: 4.41.0
typescript:
specifier: ^5.8.3
version: 5.8.3
unconfig:
specifier: ^7.3.2
version: 7.3.2
vite-tsconfig-paths:
specifier: ^5.1.4
version: 5.1.4
vitest:
specifier: ^3.2.3
version: 3.2.3
wrangler:
specifier: ^4.19.2
version: 4.19.2
zod:
specifier: ^3.25.62
version: 3.25.62
zod-to-json-schema:
specifier: ^3.24.5
version: 3.24.5
zod-validation-error:
specifier: ^3.4.1
version: 3.4.1
overrides:
openauthjs: link:../../temp/openauth
'@agentic/openauth': link:../../temp/openauth/packages/openauth
importers:
.:
dependencies:
'@agentic/openauth':
specifier: link:../../temp/openauth/packages/openauth
version: link:../../temp/openauth/packages/openauth
openauthjs:
specifier: link:../../temp/openauth
version: link:../../temp/openauth
devDependencies:
'@dotenvx/dotenvx':
specifier: ^1.44.2
version: 1.44.2
'@fisch0920/config':
specifier: 'catalog:'
version: 1.1.2(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3)(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
version: 1.1.2(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3)(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
'@types/node':
specifier: 'catalog:'
version: 22.15.29
version: 22.15.31
del-cli:
specifier: 'catalog:'
version: 6.0.0
@ -91,7 +288,7 @@ importers:
version: 0.2.3(eslint@9.28.0(jiti@2.4.2))
knip:
specifier: 'catalog:'
version: 5.60.2(@types/node@22.15.29)(typescript@5.8.3)
version: 5.60.2(@types/node@22.15.31)(typescript@5.8.3)
lint-staged:
specifier: 'catalog:'
version: 16.1.0
@ -121,10 +318,10 @@ importers:
version: 5.8.3
vite-tsconfig-paths:
specifier: 'catalog:'
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
vitest:
specifier: 'catalog:'
version: 3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
version: 3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
zod:
specifier: 'catalog:'
version: 3.25.62
@ -132,8 +329,8 @@ importers:
apps/api:
dependencies:
'@agentic/openauth':
specifier: 'catalog:'
version: 0.4.3(arctic@2.3.4)(hono@4.7.11)
specifier: link:../../../../temp/openauth/packages/openauth
version: link:../../../../temp/openauth/packages/openauth
'@agentic/platform':
specifier: workspace:*
version: link:../../packages/platform
@ -338,12 +535,18 @@ importers:
'@radix-ui/react-tooltip':
specifier: ^1.2.7
version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tanstack/react-form':
specifier: ^1.12.3
version: 1.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
ky:
specifier: 'catalog:'
version: 1.8.1
lucide-react:
specifier: ^0.515.0
version: 0.515.0(react@19.1.0)
@ -384,6 +587,9 @@ importers:
specifier: 'catalog:'
version: 4.41.0
devDependencies:
'@agentic/openauth':
specifier: link:../../../../temp/openauth/packages/openauth
version: link:../../../../temp/openauth/packages/openauth
'@tailwindcss/postcss':
specifier: ^4.1.10
version: 4.1.10
@ -412,8 +618,8 @@ importers:
packages/api-client:
dependencies:
'@agentic/openauth':
specifier: 'catalog:'
version: 0.4.3(arctic@2.3.4)(hono@4.7.11)
specifier: link:../../../../temp/openauth/packages/openauth
version: link:../../../../temp/openauth/packages/openauth
'@agentic/platform-core':
specifier: workspace:*
version: link:../core
@ -484,12 +690,21 @@ importers:
packages/core:
dependencies:
hash-object:
specifier: 'catalog:'
version: 5.0.1
decircular:
specifier: ^1.0.0
version: 1.0.0
is-obj:
specifier: ^3.0.0
version: 3.0.0
parse-json:
specifier: 'catalog:'
version: 8.3.0
sort-keys:
specifier: 'catalog:'
version: 5.1.0
type-fest:
specifier: 'catalog:'
version: 4.41.0
zod:
specifier: 'catalog:'
version: 3.25.62
@ -736,12 +951,6 @@ importers:
packages:
'@agentic/openauth@0.4.3':
resolution: {integrity: sha512-zMJPLazXY1AIDAYDqKwXpQdqICgBuqSEuw1ZxyW0us9+E61sk9sdmiFzLvuLB8uBHSqf5rtlPQAvx8ZFQAIWLw==}
peerDependencies:
arctic: ^2.2.2
hono: ^4.0.0
'@ai-sdk/provider-utils@2.2.8':
resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==}
engines: {node: '>=18'}
@ -2204,24 +2413,6 @@ packages:
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@oslojs/asn1@1.0.0':
resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
'@oslojs/binary@1.0.0':
resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
'@oslojs/crypto@1.0.1':
resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
'@oslojs/encoding@0.4.1':
resolution: {integrity: sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==}
'@oslojs/encoding@1.1.0':
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
'@oslojs/jwt@0.2.0':
resolution: {integrity: sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg==}
'@oxc-resolver/binding-darwin-arm64@11.1.0':
resolution: {integrity: sha512-n9y3Lb1+BwsOtm3BmXSUPu3iDtTq7Sf0gX4e+izFTfNrj+u6uTKqbmlq8ggV8CRdg1zGUaCvKNvg/9q3C/19gg==}
cpu: [arm64]
@ -2945,9 +3136,6 @@ packages:
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
'@standard-schema/spec@1.0.0-beta.3':
resolution: {integrity: sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==}
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
@ -3047,6 +3235,30 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@tanstack/form-core@1.12.3':
resolution: {integrity: sha512-H59XYP8Jxg8vT4IYIZa1BHkYiyiZqFcLSD2HpxefHP/vlG06/spCySVe/vGAP7IJgHHSAlEqBhQoy1Mg2ruTRA==}
'@tanstack/react-form@1.12.3':
resolution: {integrity: sha512-IlWLVKizjV+CzMgzaSac61bS4/UeSL2gceGOVIv+gKs2SriDIyylJd8AcqsKE/kNplm1K0NYXIF2Vk/re+JfOg==}
peerDependencies:
'@tanstack/react-start': ^1.112.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
vinxi: ^0.5.0
peerDependenciesMeta:
'@tanstack/react-start':
optional: true
vinxi:
optional: true
'@tanstack/react-store@0.7.1':
resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/store@0.7.1':
resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==}
'@tokenizer/inflate@0.2.7':
resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==}
engines: {node: '>=18'}
@ -3096,9 +3308,6 @@ packages:
'@types/mysql@2.15.26':
resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==}
'@types/node@22.15.29':
resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
'@types/node@22.15.31':
resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==}
@ -3331,9 +3540,6 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
arctic@2.3.4:
resolution: {integrity: sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@ -3405,9 +3611,6 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
aws4fetch@1.0.20:
resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==}
axe-core@4.10.3:
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
engines: {node: '>=4'}
@ -3736,10 +3939,13 @@ packages:
resolution: {integrity: sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
decircular@0.1.1:
resolution: {integrity: sha512-V2Vy+QYSXdgxRPmOZKQWCDf1KQNTUP/Eqswv/3W20gz7+6GB1HTosNrWqK3PqstVpFw/Dd/cGTmXSTKPeOiGVg==}
decircular@1.0.0:
resolution: {integrity: sha512-YhCtYW0jQs9+gzL2vDLxanRhMHeKa55kw5z2oheI6D+MQA7KafrqtiGKhhpKCLZQurm2a9h0LkP9T5z5gy+A0A==}
engines: {node: '>=18'}
decode-formdata@0.9.0:
resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==}
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
@ -3798,6 +4004,9 @@ packages:
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
devalue@5.1.1:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
@ -4523,10 +4732,6 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hash-object@5.0.1:
resolution: {integrity: sha512-iaRY4jYOow1caHkXW7wotYRjZDQk2nq4U7904anGJj8l4x1SLId+vuR8RpGoywZz9puD769hNFVFLFH9t+baJw==}
engines: {node: '>=18'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@ -4810,9 +5015,6 @@ packages:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
jose@5.9.6:
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@ -6799,14 +7001,6 @@ packages:
snapshots:
'@agentic/openauth@0.4.3(arctic@2.3.4)(hono@4.7.11)':
dependencies:
'@standard-schema/spec': 1.0.0-beta.3
arctic: 2.3.4
aws4fetch: 1.0.20
hono: 4.7.11
jose: 5.9.6
'@ai-sdk/provider-utils@2.2.8(zod@3.25.62)':
dependencies:
'@ai-sdk/provider': 1.1.3
@ -7274,11 +7468,11 @@ snapshots:
'@fastify/busboy@2.1.1': {}
'@fisch0920/config@1.1.2(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3)(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
'@fisch0920/config@1.1.2(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3)(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
dependencies:
'@eslint/js': 9.28.0
'@total-typescript/ts-reset': 0.6.1
'@vitest/eslint-plugin': 1.2.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
'@vitest/eslint-plugin': 1.2.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
eslint: 9.28.0(jiti@2.4.2)
eslint-config-prettier: 10.1.5(eslint@9.28.0(jiti@2.4.2))
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))
@ -8028,25 +8222,6 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@oslojs/asn1@1.0.0':
dependencies:
'@oslojs/binary': 1.0.0
'@oslojs/binary@1.0.0': {}
'@oslojs/crypto@1.0.1':
dependencies:
'@oslojs/asn1': 1.0.0
'@oslojs/binary': 1.0.0
'@oslojs/encoding@0.4.1': {}
'@oslojs/encoding@1.1.0': {}
'@oslojs/jwt@0.2.0':
dependencies:
'@oslojs/encoding': 0.4.1
'@oxc-resolver/binding-darwin-arm64@11.1.0':
optional: true
@ -8699,8 +8874,6 @@ snapshots:
'@standard-schema/spec@1.0.0': {}
'@standard-schema/spec@1.0.0-beta.3': {}
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15':
@ -8787,6 +8960,29 @@ snapshots:
postcss-selector-parser: 6.0.10
tailwindcss: 4.1.10
'@tanstack/form-core@1.12.3':
dependencies:
'@tanstack/store': 0.7.1
'@tanstack/react-form@1.12.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/form-core': 1.12.3
'@tanstack/react-store': 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
decode-formdata: 0.9.0
devalue: 5.1.1
react: 19.1.0
transitivePeerDependencies:
- react-dom
'@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@tanstack/store': 0.7.1
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
use-sync-external-store: 1.5.0(react@19.1.0)
'@tanstack/store@0.7.1': {}
'@tokenizer/inflate@0.2.7':
dependencies:
debug: 4.4.1(supports-color@10.0.0)
@ -8812,11 +9008,11 @@ snapshots:
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.15.29
'@types/node': 22.15.31
'@types/cors@2.8.18':
dependencies:
'@types/node': 22.15.29
'@types/node': 22.15.31
'@types/deep-eql@4.0.2': {}
@ -8834,31 +9030,25 @@ snapshots:
'@types/mysql@2.15.26':
dependencies:
'@types/node': 22.15.29
'@types/node@22.15.29':
dependencies:
undici-types: 6.21.0
'@types/node': 22.15.31
'@types/node@22.15.31':
dependencies:
undici-types: 6.21.0
optional: true
'@types/pg-pool@2.0.6':
dependencies:
'@types/pg': 8.6.1
'@types/pg': 8.15.4
'@types/pg@8.15.4':
dependencies:
'@types/node': 22.15.31
pg-protocol: 1.10.0
pg-types: 2.2.0
optional: true
'@types/pg@8.6.1':
dependencies:
'@types/node': 22.15.29
'@types/node': 22.15.31
pg-protocol: 1.10.0
pg-types: 2.2.0
@ -8876,7 +9066,7 @@ snapshots:
'@types/tedious@4.0.14':
dependencies:
'@types/node': 22.15.29
'@types/node': 22.15.31
'@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
@ -8970,13 +9160,13 @@ snapshots:
'@typescript-eslint/types': 8.33.1
eslint-visitor-keys: 4.2.0
'@vitest/eslint-plugin@1.2.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
'@vitest/eslint-plugin@1.2.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
dependencies:
'@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)
eslint: 9.28.0(jiti@2.4.2)
optionalDependencies:
typescript: 5.8.3
vitest: 3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vitest: 3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@ -8988,13 +9178,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.3(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
'@vitest/mocker@3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.3
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
'@vitest/pretty-format@3.2.3':
dependencies:
@ -9123,12 +9313,6 @@ snapshots:
any-promise@1.3.0: {}
arctic@2.3.4:
dependencies:
'@oslojs/crypto': 1.0.1
'@oslojs/encoding': 1.1.0
'@oslojs/jwt': 0.2.0
argparse@2.0.1: {}
aria-hidden@1.2.6:
@ -9231,8 +9415,6 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
aws4fetch@1.0.20: {}
axe-core@4.10.3: {}
axobject-query@4.1.0: {}
@ -9524,10 +9706,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
debug@4.4.1(supports-color@10.0.0):
dependencies:
ms: 2.1.3
@ -9536,7 +9714,9 @@ snapshots:
decamelize@6.0.0: {}
decircular@0.1.1: {}
decircular@1.0.0: {}
decode-formdata@0.9.0: {}
deep-eql@5.0.2: {}
@ -9589,6 +9769,8 @@ snapshots:
detect-node-es@1.1.0: {}
devalue@5.1.1: {}
diff-match-patch@1.0.5: {}
doctrine@2.1.0:
@ -9671,7 +9853,7 @@ snapshots:
engine.io@6.6.4:
dependencies:
'@types/cors': 2.8.18
'@types/node': 22.15.29
'@types/node': 22.15.31
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2
@ -9801,7 +9983,7 @@ snapshots:
esbuild-register@3.6.0(esbuild@0.25.5):
dependencies:
debug: 4.4.1
debug: 4.4.1(supports-color@10.0.0)
esbuild: 0.25.5
transitivePeerDependencies:
- supports-color
@ -10489,13 +10671,6 @@ snapshots:
dependencies:
has-symbols: 1.1.0
hash-object@5.0.1:
dependencies:
decircular: 0.1.1
is-obj: 3.0.0
sort-keys: 5.1.0
type-fest: 4.41.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
@ -10765,8 +10940,6 @@ snapshots:
jiti@2.4.2: {}
jose@5.9.6: {}
joycon@3.1.1: {}
js-cookie@2.2.1: {}
@ -10824,10 +10997,10 @@ snapshots:
dependencies:
json-buffer: 3.0.1
knip@5.60.2(@types/node@22.15.29)(typescript@5.8.3):
knip@5.60.2(@types/node@22.15.31)(typescript@5.8.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@types/node': 22.15.29
'@types/node': 22.15.31
fast-glob: 3.3.3
formatly: 0.2.4
jiti: 2.4.2
@ -11712,7 +11885,7 @@ snapshots:
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.1
debug: 4.4.1(supports-color@10.0.0)
module-details-from-path: 1.0.4
resolve: 1.22.10
transitivePeerDependencies:
@ -12547,13 +12720,13 @@ snapshots:
vary@1.1.2: {}
vite-node@3.2.3(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@10.0.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@ -12568,18 +12741,18 @@ snapshots:
- tsx
- yaml
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)):
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)):
dependencies:
debug: 4.4.1(supports-color@10.0.0)
globrex: 0.1.2
tsconfck: 3.1.5(typescript@5.8.3)
optionalDependencies:
vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.4(picomatch@4.0.2)
@ -12588,7 +12761,7 @@ snapshots:
rollup: 4.43.0
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 22.15.29
'@types/node': 22.15.31
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
@ -12596,11 +12769,11 @@ snapshots:
tsx: 4.20.1
yaml: 2.8.0
vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
vitest@3.2.3(@edge-runtime/vm@5.0.0)(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.3
'@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
'@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.3
'@vitest/runner': 3.2.3
'@vitest/snapshot': 3.2.3
@ -12618,12 +12791,12 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.0
tinyrainbow: 2.0.0
vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite-node: 3.2.3(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
vite-node: 3.2.3(@types/node@22.15.31)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(tsx@4.20.1)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@edge-runtime/vm': 5.0.0
'@types/node': 22.15.29
'@types/node': 22.15.31
transitivePeerDependencies:
- jiti
- less

Wyświetl plik

@ -2,8 +2,6 @@ packages:
- packages/*
- apps/*
- packages/fixtures/valid/*
onlyBuiltDependencies:
- '@sentry/cli'
catalog:
'@agentic/openauth': ^0.4.3
'@ai-sdk/openai': ^1.3.22
@ -94,3 +92,8 @@ catalog:
zod: ^3.25.62
zod-to-json-schema: ^3.24.5
zod-validation-error: ^3.4.1
onlyBuiltDependencies:
- '@sentry/cli'
overrides:
openauthjs: link:../../temp/openauth
'@agentic/openauth': link:../../temp/openauth/packages/openauth

Wyświetl plik

@ -20,6 +20,8 @@
- stripe billing portal
- auth
- custom auth pages for `openauth`
- **need to ditch openauth**; too opinionated about auth flow and rendering pages
- simple email/password login, for example
- **API gateway**
- oauth flow
- https://docs.scalekit.com/guides/mcp/oauth