feat: 更新SproutGate前后端代码
This commit is contained in:
207
sproutgate-frontend/src/components/OAuthConsentScreen.jsx
Normal file
207
sproutgate-frontend/src/components/OAuthConsentScreen.jsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {
|
||||
buildAuthDenyCallbackUrl,
|
||||
formatOAuthRedirectLabel,
|
||||
formatWebsiteLabel
|
||||
} from "../config";
|
||||
import icons from "../icons";
|
||||
import { MailtoEmail } from "./common";
|
||||
import AvatarImg from "./AvatarImg";
|
||||
|
||||
const DEFAULT_PERMISSIONS = [
|
||||
{
|
||||
title: "账户标识",
|
||||
desc: "获取账户名(account),用于在该应用中唯一识别你的萌芽身份。"
|
||||
},
|
||||
{
|
||||
title: "基本资料",
|
||||
desc: "获取昵称、头像、邮箱、等级、萌芽币、个人主页与简介等(以「验证令牌」「当前用户」等接口实际返回为准)。"
|
||||
},
|
||||
{
|
||||
title: "访问令牌",
|
||||
desc: "向你选择的应用颁发 JWT;应用仅在令牌有效期内、经你同意后代表你请求萌芽账户认证中心接口,不会获得你的登录密码。"
|
||||
},
|
||||
{
|
||||
title: "应用接入记录",
|
||||
desc: "在「应用接入」中记录该应用的 client_id 与名称,便于你在用户中心查看已与哪些应用建立关联。"
|
||||
}
|
||||
];
|
||||
|
||||
function scopeLabel(s) {
|
||||
const lower = String(s || "").toLowerCase().trim();
|
||||
const map = {
|
||||
openid: "OpenID(账户标识)",
|
||||
profile: "个人资料(昵称、头像等展示信息)",
|
||||
email: "邮箱地址",
|
||||
offline_access: "离线访问(延长令牌有效期的扩展能力,若应用支持)"
|
||||
};
|
||||
return map[lower] || s;
|
||||
}
|
||||
|
||||
export default function OAuthConsentScreen({
|
||||
authFlow,
|
||||
user,
|
||||
onAllow,
|
||||
onSwitchAccount,
|
||||
onPreviewImage
|
||||
}) {
|
||||
const [permsOpen, setPermsOpen] = useState(true);
|
||||
const appLabel = useMemo(() => {
|
||||
const name = (authFlow?.clientName || "").trim();
|
||||
const id = (authFlow?.clientId || "").trim();
|
||||
if (name) return name;
|
||||
if (id) return id;
|
||||
return "第三方应用";
|
||||
}, [authFlow]);
|
||||
|
||||
const redirectLabel = formatOAuthRedirectLabel(authFlow?.redirectUri);
|
||||
const redirectUrl = (authFlow?.redirectUri || "").trim();
|
||||
|
||||
const extraScopes = useMemo(() => {
|
||||
const raw = authFlow?.scopes || [];
|
||||
if (!raw.length) return [];
|
||||
return raw.map(scopeLabel);
|
||||
}, [authFlow]);
|
||||
|
||||
const handleDeny = () => {
|
||||
const url = buildAuthDenyCallbackUrl(authFlow?.redirectUri, authFlow?.state || "");
|
||||
if (url) window.location.replace(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="oauth-consent">
|
||||
<div className="oauth-consent-brand">
|
||||
<div className="oauth-consent-icon-wrap" aria-hidden>
|
||||
<svg className="oauth-consent-lock" viewBox="0 0 24 24" width="28" height="28">
|
||||
<rect x="5" y="10" width="14" height="11" rx="2" fill="none" stroke="currentColor" strokeWidth="1.8" />
|
||||
<path d="M8 10V8a4 4 0 0 1 8 0v2" fill="none" stroke="currentColor" strokeWidth="1.8" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="oauth-consent-app-name">{appLabel}</h1>
|
||||
<p className="oauth-consent-lead">请求访问你的<strong>萌芽统一账户</strong></p>
|
||||
</div>
|
||||
|
||||
<div className="oauth-consent-card oauth-consent-card--identity">
|
||||
<div className="oauth-consent-identity">
|
||||
<AvatarImg
|
||||
user={user}
|
||||
placeholderSize={56}
|
||||
alt="avatar"
|
||||
className="oauth-consent-avatar previewable-image"
|
||||
previewable
|
||||
onPreviewImage={onPreviewImage}
|
||||
previewLabel={user?.username || user?.account || "avatar"}
|
||||
/>
|
||||
<div className="oauth-consent-id-text">
|
||||
<div className="oauth-consent-display">{user?.username || user?.account || "用户"}</div>
|
||||
<div className="oauth-consent-as">
|
||||
以 <span className="mono oauth-consent-account">@{user?.account}</span> 的身份授权
|
||||
</div>
|
||||
{user?.email ? (
|
||||
<div className="oauth-consent-email">
|
||||
<MailtoEmail address={user.email} className="profile-external-link">{user.email}</MailtoEmail>
|
||||
</div>
|
||||
) : (
|
||||
<div className="muted oauth-consent-email">未绑定邮箱</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="oauth-consent-card oauth-consent-card--detail">
|
||||
<div className="oauth-consent-section-label">应用信息</div>
|
||||
<ul className="oauth-consent-meta">
|
||||
<li>
|
||||
<span className="icon oauth-consent-meta-icon">{icons.link}</span>
|
||||
<div>
|
||||
<div className="oauth-consent-meta-key">回跳地址</div>
|
||||
{redirectUrl.match(/^https?:\/\//i) ? (
|
||||
<a
|
||||
href={redirectUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="oauth-consent-meta-link"
|
||||
>
|
||||
{redirectLabel}
|
||||
</a>
|
||||
) : (
|
||||
<span className="mono oauth-consent-meta-val">{redirectLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
{(authFlow?.clientId || "").trim() ? (
|
||||
<li>
|
||||
<span className="icon oauth-consent-meta-icon">{icons.apps}</span>
|
||||
<div>
|
||||
<div className="oauth-consent-meta-key">应用 ID(client_id)</div>
|
||||
<span className="mono oauth-consent-meta-val">{authFlow.clientId.trim()}</span>
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
{(authFlow?.clientName || "").trim() ? (
|
||||
<li>
|
||||
<span className="icon oauth-consent-meta-icon">{icons.username}</span>
|
||||
<div>
|
||||
<div className="oauth-consent-meta-key">应用名称</div>
|
||||
<span className="oauth-consent-meta-val">{authFlow.clientName.trim()}</span>
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
|
||||
<div className="oauth-consent-section-label oauth-consent-section-label--perm">将获取以下信息与权限</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`oauth-consent-perm-toggle${permsOpen ? " is-open" : ""}`}
|
||||
onClick={() => setPermsOpen((v) => !v)}
|
||||
aria-expanded={permsOpen}
|
||||
>
|
||||
<span className="oauth-consent-perm-toggle-text">查看详情</span>
|
||||
<span className="oauth-consent-chevron" aria-hidden>▾</span>
|
||||
</button>
|
||||
{permsOpen && (
|
||||
<ul className="oauth-consent-perms">
|
||||
{DEFAULT_PERMISSIONS.map((p) => (
|
||||
<li key={p.title}>
|
||||
<span className="oauth-consent-perm-icon">{icons.token}</span>
|
||||
<div>
|
||||
<div className="oauth-consent-perm-title">{p.title}</div>
|
||||
<div className="oauth-consent-perm-desc">{p.desc}</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{extraScopes.length > 0 ? (
|
||||
<li>
|
||||
<span className="oauth-consent-perm-icon">{icons.level}</span>
|
||||
<div>
|
||||
<div className="oauth-consent-perm-title">应用请求的 scope</div>
|
||||
<ul className="oauth-consent-scope-list">
|
||||
{extraScopes.map((s) => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="oauth-consent-actions">
|
||||
<button type="button" className="primary oauth-consent-allow" onClick={onAllow}>
|
||||
允许
|
||||
</button>
|
||||
<button type="button" className="ghost oauth-consent-deny" onClick={handleDeny}>
|
||||
拒绝
|
||||
</button>
|
||||
<button type="button" className="oauth-consent-switch text-button" onClick={onSwitchAccount}>
|
||||
使用其他账号登录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="oauth-consent-footnote">
|
||||
点击「允许」后,你将返回应用 <span className="mono">{formatWebsiteLabel(redirectUrl) || redirectLabel}</span>,并在浏览器地址的 Hash 中携带访问令牌。
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user