用户要求:内测的固定验证码 123456 等于"知道手机号就能进任何账号",先关掉; 登录改用账号密码,并多一种"邮箱当账号名"的方式;发信邮箱要能在后台改。 短信那条路(默认关): - `SMS_LOGIN_ENABLED=false`,`/auth/sms/send` 与 `/auth/login` 统一回 2017,不是静默失败; - 生产校验改成"开着短信登录就必须配真通道",不开时 provider 是 dev 也不拦; - 新增 `GET /auth/methods`,登录页据此渲染,关着的路不出现(少一次"点进去才发现不能用")。 邮箱登录: - `app_user.email` + `uq_app_user_email`(`lower(email)` 部分索引),登录与绑定都归一成小写; - `POST /auth/login/password` 收 `phone` 或 `email`(二选一),失败统一 2013、连错 10 次锁 15 分钟; - 绑定在 `PATCH /auth/me`:格式错 2018、被占用 2019、留空串表示解绑。 系统设置(跨租户,只有平台管理员能改): - 新表 `system_setting`(不挂租户、无 RLS,写权限由 `deps.platform_admin` 把关); - `GET/PUT /settings/smtp`、`POST /settings/smtp/test`,授权码只写不读,留空表示沿用旧值; - `services/mail.py` 只用标准库 smtplib:465 走 SSL、587 走 STARTTLS,认证/拒收/网络失败都归 2020 但消息不同; - Web 新增「系统设置」页(状态卡 + 测试发信),侧栏只对平台管理员显示入口;账号页补「邮箱」。 验证:pytest 130 passed、ruff check/format 全绿、tsc 通过、vite build 通过; `web/e2e/login-email-smtp.mjs` 15 项全绿(含用 126 授权码真发一封测试邮件)。 顺带记下的两个坑:`platform_admin_phones` 是 list,pydantic-settings 会按 JSON 解析, 要用 `NoDecode` 交给 before-validator 拆逗号;迁移号写成 0009 会撞既有的 0009,一并改成 0010。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAuth } from "../App";
|
||||
import { Logout, Logo, Settings } from "./icons";
|
||||
import { Logout, Logo, Settings, Sliders } from "./icons";
|
||||
|
||||
/**
|
||||
* 工作区里侧栏长什么样,工作区外面(工作区列表、账号设置)就长什么样。
|
||||
@@ -64,6 +64,16 @@ export function SideRail({ activeId }: { activeId?: string | null }) {
|
||||
<Settings size={14} className="text-ink-400" />
|
||||
账号设置
|
||||
</Link>
|
||||
{/* 系统设置是平台级配置(全站共用一份),只给平台管理员看 */}
|
||||
{user?.platform_admin ? (
|
||||
<Link
|
||||
to="/settings"
|
||||
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs text-ink-500 transition hover:bg-paper-100 hover:text-ink-800"
|
||||
>
|
||||
<Sliders size={14} className="text-ink-400" />
|
||||
系统设置
|
||||
</Link>
|
||||
) : null}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-line-200 px-3 py-3">
|
||||
@@ -94,6 +104,15 @@ export function SideRail({ activeId }: { activeId?: string | null }) {
|
||||
<span className="text-sm font-semibold text-ink-900">达道Task</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1">
|
||||
{user?.platform_admin ? (
|
||||
<Link
|
||||
to="/settings"
|
||||
className="rounded-md p-1.5 text-ink-400 hover:bg-ink-100"
|
||||
aria-label="系统设置"
|
||||
>
|
||||
<Sliders size={16} />
|
||||
</Link>
|
||||
) : null}
|
||||
<Link
|
||||
to="/account"
|
||||
className="rounded-md p-1.5 text-ink-400 hover:bg-ink-100"
|
||||
|
||||
@@ -227,6 +227,14 @@ export const Settings = (p: IconProps) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Sliders = (p: IconProps) => (
|
||||
<svg {...base(p)}>
|
||||
<path d="M4 7.5h9M17 7.5h3M4 16.5h3M11 16.5h9" />
|
||||
<circle cx="15" cy="7.5" r="2" />
|
||||
<circle cx="9" cy="16.5" r="2" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const User = (p: IconProps) => (
|
||||
<svg {...base(p)}>
|
||||
<circle cx="12" cy="8.5" r="3.5" />
|
||||
|
||||
Reference in New Issue
Block a user