fix(env): fix env

This commit is contained in:
zhangheng
2026-07-14 15:21:28 +08:00
parent d0234effd6
commit c6ab525140
8 changed files with 269 additions and 109 deletions

View File

@@ -31,17 +31,41 @@ interface UserInfo {
roles: string[]
}
export default function DashboardPage({ user }: { user: UserInfo | null }) {
// 未登录 -> 跳转 SSO
useEffect(() => {
if (!user) {
const appId = 'app_demo_001'
const callbackUrl = encodeURIComponent('http://localhost:4000/callback')
window.location.href = `http://localhost:5501/login?app_id=${appId}&app_url=${callbackUrl}`
}
}, [user])
// 构建跳转 sso-portal 的登录 URLOAuth2app_id 即 client_id
const SSO_APP_ID = process.env.NEXT_PUBLIC_SSO_APP_ID || ''
const SSO_URL = (process.env.NEXT_PUBLIC_SSO_URL || 'http://localhost:5501').replace(/\/+$/, '')
const APP_URL = (process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:4000').replace(/\/+$/, '')
const SSO_STATE_STORAGE_KEY = 'sso_login_state'
if (!user) {
function buildSsoLoginUrl(): string {
const bytes = new Uint8Array(32)
window.crypto.getRandomValues(bytes)
const state = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
const loginUrl = new URL(`${SSO_URL}/login`)
window.sessionStorage.setItem(SSO_STATE_STORAGE_KEY, state)
loginUrl.searchParams.set('app_id', SSO_APP_ID)
loginUrl.searchParams.set('app_url', `${APP_URL}/callback`)
loginUrl.searchParams.set('state', state)
return loginUrl.toString()
}
export default function DashboardPage({
user,
authenticated,
}: {
user: UserInfo | null
// 登录态以 token 为准(拿到 access_token 即算成功user 可能为 null
authenticated: boolean
}) {
// 未登录(无 token-> 跳转 SSO
useEffect(() => {
if (!authenticated) {
window.location.href = buildSsoLoginUrl()
}
}, [authenticated])
if (!authenticated) {
return (
<Stack align="center" justify="center" h="100vh">
<Text size="lg" c="dimmed"> SSO ...</Text>
@@ -51,10 +75,8 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
const handleLogout = async () => {
await fetch('/api/logout', { method: 'POST' })
// 跳到 SSO 登录页(带上 app_id 让 SSO 知道是哪个应用)
const appId = 'app_demo_001'
const callbackUrl = encodeURIComponent('http://localhost:4000/callback')
window.location.href = `http://localhost:5501/login?app_id=${appId}&app_url=${callbackUrl}`
// 跳到 SSO 登录页;每次登录都生成新的 state 防止登录 CSRF。
window.location.href = buildSsoLoginUrl()
}
return (
@@ -70,13 +92,15 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
<Menu.Target>
<Group gap="sm" style={{ cursor: 'pointer' }}>
<Avatar color="brand" radius="xl" size="sm">
{user.name?.[0] || 'U'}
{user?.name?.[0] || 'U'}
</Avatar>
<Text size="sm" fw={500}>{user.name}</Text>
<Text size="sm" fw={500}>{user?.name || '已登录用户'}</Text>
</Group>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item leftSection={<IconUser size={14} />}>{user.account}</Menu.Item>
<Menu.Item leftSection={<IconUser size={14} />}>
{user?.account || '(未获取到账号)'}
</Menu.Item>
<Menu.Divider />
<Menu.Item leftSection={<IconLogout size={14} />} color="red" onClick={handleLogout}>
退
@@ -89,7 +113,9 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
<AppShell.Main>
<Stack maw={800} mx="auto" pt="xl">
<Text size="xl" fw={700}></Text>
<Text c="dimmed"> Mock SSO </Text>
<Text c="dimmed">
SSO OAuth2 access_token
</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} mt="md">
<Paper withBorder p="lg" radius="md">
@@ -97,24 +123,30 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
<IconUser size={20} color="#0080FF" />
<Text fw={600}></Text>
</Group>
<Stack gap="xs">
<Group>
<Text size="sm" c="dimmed" w={80}>ID</Text>
<Code>{user.id}</Code>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.account}</Text>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.name}</Text>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.phone}</Text>
</Group>
</Stack>
{user ? (
<Stack gap="xs">
<Group>
<Text size="sm" c="dimmed" w={80}>ID</Text>
<Code>{user.id}</Code>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.account}</Text>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.name}</Text>
</Group>
<Group>
<Text size="sm" c="dimmed" w={80}></Text>
<Text size="sm">{user.phone}</Text>
</Group>
</Stack>
) : (
<Text size="sm" c="dimmed">
access_token
</Text>
)}
</Paper>
<Paper withBorder p="lg" radius="md">
@@ -122,11 +154,15 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
<IconShield size={20} color="#20C997" />
<Text fw={600}></Text>
</Group>
<Group gap="xs">
{user.roles?.map((role: string) => (
<Badge key={role} variant="light">{role}</Badge>
))}
</Group>
{user?.roles?.length ? (
<Group gap="xs">
{user.roles.map((role: string) => (
<Badge key={role} variant="light">{role}</Badge>
))}
</Group>
) : (
<Text size="sm" c="dimmed"></Text>
)}
</Paper>
</SimpleGrid>
@@ -142,16 +178,16 @@ export default function DashboardPage({ user }: { user: UserInfo | null }) {
<strong>1.</strong> <Code>http://localhost:4000</Code>
</Text>
<Text size="sm">
<strong>2.</strong> Mock SSO<Code>http://localhost:5501/login</Code>
<strong>2.</strong> SSO<Code>http://localhost:5501/login</Code>
</Text>
<Text size="sm">
<strong>3.</strong> SSO SSO <Code>sso_code</Code>
</Text>
<Text size="sm">
<strong>4.</strong> <Code>sso_code</Code> <Code>POST /api/auth/verify</Code>
<strong>4.</strong> <Code>sso_code</Code> <Code>/api/v1/basis/sso/access_token</Code> access_token
</Text>
<Text size="sm">
<strong>5.</strong> Cookie
<strong>5.</strong> access_token httpOnly Cookie userinfo
</Text>
</Stack>
</Paper>