fix(env): fix env
This commit is contained in:
@@ -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 的登录 URL(OAuth2:app_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>
|
||||
|
||||
Reference in New Issue
Block a user