208 lines
7.8 KiB
JavaScript
208 lines
7.8 KiB
JavaScript
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>
|
||
);
|
||
}
|