refactor(auth): replace dashboard sessions with stateless tokens and session control (#6329)

* refactor(auth): replace dashboard sessions with stateless tokens

* feat(auth): harden session issuance and distributed enforcement

* fix(proxy): preserve trusted proxy compatibility defaults

* refactor: address dashboard auth review feedback

* refactor: remove classic frontend and flatten web app
This commit is contained in:
Calcium-Ion
2026-07-20 16:48:43 +08:00
committed by GitHub
parent 5a6c53d496
commit 31d70fca39
1605 changed files with 17511 additions and 147913 deletions
+97
View File
@@ -0,0 +1,97 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useTranslation } from 'react-i18next'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import { cn } from '@/lib/utils'
import type { SystemStatus } from '../types'
interface LegalConsentProps {
status: SystemStatus | null
checked: boolean
onCheckedChange: (nextValue: boolean) => void
className?: string
}
export function LegalConsent({
status,
checked,
onCheckedChange,
className,
}: LegalConsentProps) {
const { t } = useTranslation()
const hasUserAgreement = Boolean(status?.user_agreement_enabled)
const hasPrivacyPolicy = Boolean(status?.privacy_policy_enabled)
if (!hasUserAgreement && !hasPrivacyPolicy) {
return null
}
const handleChange = (value: boolean) => {
onCheckedChange(value === true)
}
return (
<div
className={cn(
'border-border/60 bg-muted/40 flex items-start gap-3 rounded-md border p-3',
className
)}
>
<Checkbox
id='legal-consent'
checked={checked}
onCheckedChange={handleChange}
className='mt-0.5'
/>
<Label
htmlFor='legal-consent'
className='text-muted-foreground items-start gap-1 text-left text-xs leading-5 font-normal'
>
<span>
{t('I have read and agree to the')}{' '}
{hasUserAgreement && (
<a
href='/user-agreement'
target='_blank'
rel='noopener noreferrer'
className='text-primary hover:underline'
>
{t('User Agreement')}
</a>
)}
{hasUserAgreement && hasPrivacyPolicy && ' and the '}
{hasPrivacyPolicy && (
<a
href='/privacy-policy'
target='_blank'
rel='noopener noreferrer'
className='text-primary hover:underline'
>
{t('Privacy Policy')}
</a>
)}
.
</span>
</Label>
</div>
)
}
@@ -0,0 +1,125 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { Loader2, Send, Shield, UserRound, type LucideIcon } from 'lucide-react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { SiGithub, SiLinux, SiWechat } from 'react-icons/si'
import { AuthLayout } from '../auth-layout'
type OAuthCallbackScreenProps = {
provider: string
mode: 'login' | 'bind'
}
type ProviderMeta = {
label: string
Icon: LucideIcon | ((props: { className?: string }) => React.JSX.Element)
}
const providerDictionary: Record<string, ProviderMeta> = {
github: {
label: 'GitHub',
Icon: (props: { className?: string }) => (
<SiGithub className={props.className} focusable='false' />
),
},
oidc: { label: 'OIDC', Icon: Shield },
linuxdo: {
label: 'LinuxDO',
Icon: (props: { className?: string }) => (
<SiLinux className={props.className} focusable='false' />
),
},
telegram: { label: 'Telegram', Icon: Send },
wechat: {
label: 'WeChat',
Icon: (props: { className?: string }) => (
<SiWechat className={props.className} focusable='false' />
),
},
}
export function OAuthCallbackScreen({
provider,
mode,
}: OAuthCallbackScreenProps) {
const { t } = useTranslation()
const { label, Icon } = useMemo(() => {
const normalized = provider?.toLowerCase() ?? ''
return (
providerDictionary[normalized] || {
label: 'account',
Icon: UserRound,
}
)
}, [provider])
const providerLabel = t(label)
const isBindMode = mode === 'bind'
const headline = isBindMode
? t('Binding your {{provider}} account', { provider: providerLabel })
: t('Signing you in with {{provider}}', { provider: providerLabel })
const description = isBindMode
? t('Hang tight while we securely link this account to your profile.')
: t('Hang tight while we finish connecting your account.')
const secondaryNote = isBindMode
? t(
'You can close this tab once the binding completes or a success message appears in the original window.'
)
: t(
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds."
)
return (
<AuthLayout>
<div className='w-full space-y-8'>
<div className='flex flex-col items-center space-y-4 text-center'>
<div className='bg-muted flex h-16 w-16 items-center justify-center rounded-2xl'>
<Icon className='h-8 w-8' />
</div>
<div className='space-y-2'>
<h2 className='text-center text-2xl font-semibold tracking-tight'>
{headline}
</h2>
<p className='text-muted-foreground text-sm sm:text-base'>
{description}
</p>
</div>
</div>
<div className='space-y-4 text-center'>
<div className='flex items-center justify-center gap-2 text-sm font-medium'>
<Loader2 className='h-4 w-4 animate-spin' />
<span>{t('Processing OAuth response...')}</span>
</div>
<p className='text-muted-foreground text-sm'>{secondaryNote}</p>
<p className='text-muted-foreground text-xs'>
{t(
'This may take a few moments while we validate the request and update your session.'
)}
</p>
</div>
</div>
</AuthLayout>
)
}
+191
View File
@@ -0,0 +1,191 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import {
IconDiscord,
IconGithub,
IconLinuxDo,
IconTelegram,
IconWeChat,
} from '@/assets/brand-icons'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { useOAuthLogin } from '../hooks/use-oauth-login'
import type { SystemStatus } from '../types'
import { TelegramLoginDialog } from './telegram-login-dialog'
type OAuthProvidersProps = {
status: SystemStatus | null
disabled?: boolean
className?: string
onWeChatLogin?: () => void
isWeChatLoading?: boolean
redirectTo?: string
}
type ProviderButton = {
key: string
label: string
onClick: () => void
icon?: ReactNode
disabled?: boolean
}
export function OAuthProviders({
status,
disabled = false,
className,
onWeChatLogin,
isWeChatLoading = false,
redirectTo,
}: OAuthProvidersProps) {
const { t } = useTranslation()
const {
isLoading,
githubButtonText,
githubButtonDisabled,
handleGitHubLogin,
handleDiscordLogin,
handleOIDCLogin,
handleLinuxDOLogin,
handleTelegramLogin,
handleCustomOAuthLogin,
isTelegramDialogOpen,
isTelegramPending,
handleTelegramAuthorization,
setIsTelegramDialogOpen,
} = useOAuthLogin(status, redirectTo)
const providerButtons: ProviderButton[] = []
if (status?.wechat_login && onWeChatLogin) {
providerButtons.push({
key: 'wechat',
label: t('Continue with WeChat'),
onClick: onWeChatLogin,
icon: <IconWeChat className='h-4 w-4' />,
disabled: isWeChatLoading,
})
}
if (status?.github_oauth) {
providerButtons.push({
key: 'github',
label: githubButtonText || t('Continue with GitHub'),
onClick: handleGitHubLogin,
icon: <IconGithub className='h-4 w-4' />,
disabled: githubButtonDisabled,
})
}
if (status?.discord_oauth) {
providerButtons.push({
key: 'discord',
label: t('Continue with Discord'),
onClick: handleDiscordLogin,
icon: <IconDiscord className='h-4 w-4' />,
})
}
if (status?.oidc_enabled) {
providerButtons.push({
key: 'oidc',
label: t('Continue with OIDC'),
onClick: handleOIDCLogin,
})
}
if (status?.linuxdo_oauth) {
providerButtons.push({
key: 'linuxdo',
label: t('Continue with LinuxDO'),
onClick: handleLinuxDOLogin,
icon: <IconLinuxDo className='h-4 w-4' />,
})
}
if (status?.telegram_oauth) {
providerButtons.push({
key: 'telegram',
label: t('Continue with Telegram'),
onClick: handleTelegramLogin,
icon: <IconTelegram data-icon='inline-start' />,
})
}
// Custom OAuth providers
const customProviders = status?.custom_oauth_providers
if (customProviders && customProviders.length > 0) {
for (const provider of customProviders) {
providerButtons.push({
key: `custom-${provider.slug}`,
label: t('Continue with {{name}}', { name: provider.name }),
onClick: () => handleCustomOAuthLogin(provider),
})
}
}
if (providerButtons.length === 0) return null
return (
<>
<div className={cn('space-y-3', className)}>
<div className='relative'>
<div className='absolute inset-0 flex items-center'>
<span className='w-full border-t' />
</div>
<div className='relative flex justify-center text-xs uppercase'>
<span className='bg-background text-muted-foreground px-2'>
{t('Or continue with')}
</span>
</div>
</div>
<div className='flex flex-col gap-2'>
{providerButtons.map(
({ key, label, onClick, icon, disabled: extraDisabled }) => (
<Button
key={key}
variant='outline'
type='button'
disabled={disabled || isLoading || extraDisabled}
onClick={onClick}
className='h-11 w-full justify-center gap-2 rounded-lg'
>
{icon}
{label}
</Button>
)
)}
</div>
</div>
<TelegramLoginDialog
open={isTelegramDialogOpen}
botName={status?.telegram_bot_name ?? ''}
pending={isTelegramPending}
onOpenChange={setIsTelegramDialogOpen}
onAuthorization={handleTelegramAuthorization}
/>
</>
)
}
@@ -0,0 +1,110 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Dialog } from '@/components/dialog'
import { Spinner } from '@/components/ui/spinner'
type TelegramLoginDialogProps = {
open: boolean
botName: string
pending: boolean
onOpenChange: (open: boolean) => void
onAuthorization: (authorization: unknown) => void
}
let telegramCallbackSequence = 0
export function TelegramLoginDialog(props: TelegramLoginDialogProps) {
const { t } = useTranslation()
const widgetContainer = useRef<HTMLDivElement | null>(null)
const authorizationHandler = useRef(props.onAuthorization)
const [callbackName] = useState(
() => `newApiTelegramLogin${++telegramCallbackSequence}`
)
const [widgetState, setWidgetState] = useState<
'idle' | 'loading' | 'ready' | 'failed'
>('idle')
useEffect(() => {
authorizationHandler.current = props.onAuthorization
}, [props.onAuthorization])
useEffect(() => {
const container = widgetContainer.current
const botName = props.botName.trim()
if (!props.open || !container || !botName) return
setWidgetState('loading')
const callback = (authorization: unknown) => {
authorizationHandler.current(authorization)
}
const browserWindow = window as unknown as Record<string, unknown>
browserWindow[callbackName] = callback
const script = document.createElement('script')
script.async = true
script.src = 'https://telegram.org/js/telegram-widget.js?22'
script.dataset.telegramLogin = botName
script.dataset.size = 'large'
script.dataset.radius = '8'
script.dataset.onauth = `${callbackName}(user)`
const handleLoad = () => setWidgetState('ready')
const handleError = () => setWidgetState('failed')
script.addEventListener('load', handleLoad)
script.addEventListener('error', handleError)
container.replaceChildren(script)
return () => {
script.removeEventListener('load', handleLoad)
script.removeEventListener('error', handleError)
container.replaceChildren()
delete browserWindow[callbackName]
}
}, [callbackName, props.botName, props.open])
return (
<Dialog
open={props.open}
onOpenChange={props.onOpenChange}
title={t('Telegram Login Widget')}
description={t('Continue with Telegram')}
contentClassName='max-w-sm'
contentHeight='auto'
bodyClassName='space-y-4'
>
<div
className='flex min-h-12 items-center justify-center'
aria-busy={widgetState === 'loading' || props.pending}
>
{(widgetState === 'loading' || props.pending) && <Spinner />}
{widgetState === 'failed' && (
<p className='text-destructive text-sm'>{t('Login failed')}</p>
)}
<div
ref={widgetContainer}
className={
widgetState === 'ready' && !props.pending ? 'block' : 'hidden'
}
/>
</div>
</Dialog>
)
}
+94
View File
@@ -0,0 +1,94 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import type { SystemStatus } from '../types'
interface TermsFooterProps {
variant?: 'sign-in' | 'sign-up'
className?: string
status?: SystemStatus | null
}
export function TermsFooter({
variant = 'sign-in',
className,
status,
}: TermsFooterProps) {
const { t } = useTranslation()
const text =
variant === 'sign-in'
? 'By clicking sign in, you agree to our'
: 'By creating an account, you agree to our'
const hasUserAgreement = Boolean(status?.user_agreement_enabled)
const hasPrivacyPolicy = Boolean(status?.privacy_policy_enabled)
if (!hasUserAgreement && !hasPrivacyPolicy) {
return null
}
const agreementLink = {
label: 'User Agreement',
href: '/user-agreement',
}
const privacyLink = {
label: 'Privacy Policy',
href: '/privacy-policy',
}
const activeLinks =
hasUserAgreement || hasPrivacyPolicy
? ([
hasUserAgreement ? agreementLink : null,
hasPrivacyPolicy ? privacyLink : null,
].filter(Boolean) as Array<{ label: string; href: string }>)
: [agreementLink, privacyLink]
const [firstLink, secondLink] = activeLinks
return (
<p className={cn('text-muted-foreground text-center text-xs', className)}>
{text}{' '}
{firstLink && (
<a
href={firstLink.href}
className='hover:text-primary underline underline-offset-4'
>
{firstLink.label}
</a>
)}
{secondLink && (
<>
{' '}
{t('and')}{' '}
<a
href={secondLink.href}
className='hover:text-primary underline underline-offset-4'
>
{secondLink.label}
</a>
</>
)}
.
</p>
)
}