This commit is contained in:
parent
5604b81ff8
commit
a6b095ea77
@ -703,6 +703,34 @@ func CompletePublicLinkPasswordRecovery(w http.ResponseWriter, req *http.Request
|
||||
writePublicRecoveryJSON(w, http.StatusOK, map[string]bool{"reset": true})
|
||||
}
|
||||
|
||||
// PublicLinkPasswordRecoveryRequirements 只返回恢复页渲染所需的最小信息。
|
||||
// 一次性令牌不写入日志,也不返回账户、邮箱或角色名称。
|
||||
func PublicLinkPasswordRecoveryRequirements(w http.ResponseWriter, req *http.Request) {
|
||||
if !PublicMailRecoveryEnabled() || req == nil {
|
||||
writePublicRecoveryJSON(w, http.StatusBadRequest, map[string]bool{"available": false})
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&input); err != nil {
|
||||
writePublicRecoveryJSON(w, http.StatusBadRequest, map[string]bool{"available": false})
|
||||
return
|
||||
}
|
||||
runtime := currentSecurityRuntime()
|
||||
requiresRecoveryCode := false
|
||||
if runtime.recovery != nil {
|
||||
var err error
|
||||
requiresRecoveryCode, err = runtime.recovery.PublicLinkRequiresRecoveryCode(input.Token)
|
||||
if err != nil {
|
||||
requiresRecoveryCode = false
|
||||
}
|
||||
}
|
||||
writePublicRecoveryJSON(w, http.StatusOK, map[string]bool{"requires_recovery_code": requiresRecoveryCode})
|
||||
}
|
||||
|
||||
func writePublicRecoveryJSON(w http.ResponseWriter, status int, value any) {
|
||||
if w == nil {
|
||||
return
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
"git.pcux.cn/xuans/Smail/dto/parsemail"
|
||||
"git.pcux.cn/xuans/Smail/services/recovery"
|
||||
"git.pcux.cn/xuans/Smail/services/verification"
|
||||
appcontext "git.pcux.cn/xuans/Smail/utils/context"
|
||||
"git.pcux.cn/xuans/Smail/utils/send"
|
||||
|
||||
@ -69,6 +69,12 @@ func (s *Service) BeginPublicLink(identifier string) error {
|
||||
}
|
||||
for ticketKey, item := range s.publicTickets {
|
||||
if item.UserID == user.ID {
|
||||
// 正在提交的旧链接优先完成,期间不签发会与其竞争的新链接。
|
||||
// 调用方统一隐藏该结果,因此不会泄露账户或链接状态。
|
||||
if item.Busy {
|
||||
s.mu.Unlock()
|
||||
return ErrRecoveryDenied
|
||||
}
|
||||
delete(s.publicTickets, ticketKey)
|
||||
}
|
||||
}
|
||||
@ -89,6 +95,23 @@ func (s *Service) BeginPublicLink(identifier string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublicLinkRequiresRecoveryCode 返回当前一次性链接是否属于管理员。
|
||||
// 此方法只为恢复页决定是否展示恢复码字段;令牌必须由收件人持有,且不会返回账号或角色详情。
|
||||
func (s *Service) PublicLinkRequiresRecoveryCode(token string) (bool, error) {
|
||||
if s == nil || strings.TrimSpace(token) == "" {
|
||||
return false, ErrInvalidRequest
|
||||
}
|
||||
key := ticketKey(token)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.cleanupLocked(s.now())
|
||||
item, ok := s.publicTickets[key]
|
||||
if !ok || item.Busy {
|
||||
return false, ErrRecoveryExpired
|
||||
}
|
||||
return item.Role == access.RoleAdmin, nil
|
||||
}
|
||||
|
||||
// CompletePublicLink 在同一数据库事务中完成管理员恢复码核销、密码更新和旧凭据撤销。
|
||||
func (s *Service) CompletePublicLink(token, newPassword, recoveryCode string) error {
|
||||
defer clearString(&newPassword)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user