系统设置迁入后台:站点地址 / OAuth 凭据 / R2 存储 / 管理员账号,保存即生效无需重启

- 生效规则统一为「后台填了用后台的,没填回落环境变量」,老部署不改 env 照常跑
- config.Resolver:短缓存解析有效配置,存储后端按配置签名热重建;后台保存主动失效
- 秘密项(client secret / bot token / R2 密钥 / 管理员密码)接口永不回显明文,
  只报「是否已配置、来自哪里」;留空保存 = 保持现值
- 管理员密码 bcrypt 入库,DB 哈希优先、显式设置的 env 密码作解锁后路;
  后台改过密码后 admin/admin 开发模式立即失效
- 设置页新增「登录与存储」标签,基础信息加站点地址;秘密项带来源提示
- 监听地址 / 数据库 / 目录 / ONE_SECRET / Passkey 仍留环境变量(启动期依赖)
This commit is contained in:
Sakurasan committed 2026-10-01 12:14:10 +08:00
1 parent bf3ce934dd
commit e68400b389
23 files changed
+957 -126

No files matched your search

+27 -16
View File
@@ -34,12 +34,20 @@ type accountView struct {
}
type passwordInfo struct {
// 站主密码由环境变量管理,不进库也不做哈希 —— 这条退路保证
// 「解绑所有身份 + 删光 passkey」也不会把自已锁在门外。
// 密码现在有两个可能的家:settings 表里的 bcrypt 哈希(后台改的),
// 或环境变量 ONE_ADMIN_PASSWORD(未迁移时的兜底)。
ManagedBy string `json:"managed_by"`
Username string `json:"username"`
}
// passwordSource 报告当前密码存哪。前端只作展示,不参与逻辑。
func passwordSource(hash string) string {
if hash != "" {
return "settings"
}
return "env:ONE_ADMIN_PASSWORD"
}
func (a *API) account(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
httpx.Error(w, http.StatusMethodNotAllowed, "GET required")
@@ -54,7 +62,7 @@ func (a *API) account(w http.ResponseWriter, r *http.Request) {
}
func (a *API) buildAccount() (accountView, error) {
owner, err := a.Store.EnsureOwner(a.Cfg.AdminUser)
owner, err := a.Store.EnsureOwner(a.cfg().AdminUser)
if err != nil {
return accountView{}, err
}
@@ -72,12 +80,14 @@ func (a *API) buildAccount() (accountView, error) {
}
v := accountView{
// 昵称以站主行的 name 为准;老数据里它是空的,回落到站点设置的作者名。
Name: firstNonEmptyStr(owner.Name, st.AuthorName),
Bio: st.AuthorBio,
AvatarKey: st.AuthorAvatarKey,
AvatarURL: a.avatarURL(st.AuthorAvatarKey),
Handle: owner.Handle,
Password: passwordInfo{ManagedBy: "env:ONE_ADMIN_PASSWORD", Username: a.Cfg.AdminUser},
Name: firstNonEmptyStr(owner.Name, st.AuthorName),
Bio: st.AuthorBio,
AvatarKey: st.AuthorAvatarKey,
AvatarURL: a.avatarURL(st.AuthorAvatarKey),
Handle: owner.Handle,
// 密码可迁到哪:后台「管理员账号」里改过就是 DB(bcrypt 哈希),
// 否则回落 env。
Password: passwordInfo{ManagedBy: passwordSource(st.AdminPasswordHash), Username: a.cfg().AdminUser},
Identities: ids,
Passkeys: pks,
}
@@ -92,15 +102,16 @@ func (a *API) buildAccount() (accountView, error) {
}
// providerEnabled 判断某个第三方平台是否配了凭据。绑定入口只列已配置的,
// 否则点了必然报错。
// 否则点了必然报错。凭据按有效配置算(后台系统设置优先于环境变量)。
func (a *API) providerEnabled(name string) bool {
c := a.cfg()
switch name {
case "github":
return a.Cfg.GitHubClientID != "" && a.Cfg.GitHubClientSecret != ""
return c.GitHubClientID != "" && c.GitHubClientSecret != ""
case "google":
return a.Cfg.GoogleClientID != "" && a.Cfg.GoogleClientSecret != ""
return c.GoogleClientID != "" && c.GoogleClientSecret != ""
case "telegram":
return a.Cfg.TelegramBot != "" && a.Cfg.TelegramToken != ""
return c.TelegramBot != "" && c.TelegramToken != ""
}
return false
}
@@ -115,7 +126,7 @@ func (a *API) avatarURL(key string) string {
if err != nil {
return ""
}
return storage.FileURL(f.Store, f.Key, a.Cfg.UploadsPublicBase)
return storage.FileURL(f.Store, f.Key, a.cfg().UploadsPublicBase)
}
type patchAccountRequest struct {
@@ -132,7 +143,7 @@ func (a *API) patchAccount(w http.ResponseWriter, r *http.Request) {
httpx.BadRequest(w, "invalid body")
return
}
owner, err := a.Store.EnsureOwner(a.Cfg.AdminUser)
owner, err := a.Store.EnsureOwner(a.cfg().AdminUser)
if err != nil {
httpx.ServerError(w, err)
return
@@ -228,7 +239,7 @@ func (a *API) unbindIdentity(w http.ResponseWriter, r *http.Request) {
httpx.BadRequest(w, "bad provider")
return
}
owner, err := a.Store.EnsureOwner(a.Cfg.AdminUser)
owner, err := a.Store.EnsureOwner(a.cfg().AdminUser)
if err != nil {
httpx.ServerError(w, err)
return