cyi-box: Memphis-style toolbox admin platform
Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway (Epay-compatible + CDK stock), license/card system (per-app appid, domain binding), AI memory bank with built-in MCP server (/mcp, FTS5 search, signed read links), server SSH monitor + terminal + SFTP, proxy pool with protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL autocomplete + import/export), disk share, vault (AES-GCM + TOTP), bookmarks, dev tools. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cyi-cc/fun"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/cyi-cc/cyi-box/backend/internal/dto"
|
||||
"github.com/cyi-cc/cyi-box/backend/internal/store"
|
||||
)
|
||||
|
||||
// 登录态:24h 滑动过期;距上次续期超过 2h 的请求自动把有效期顺延回 24h
|
||||
const sessionTTL = 24 * time.Hour
|
||||
const sessionRenewAfter = 2 * time.Hour
|
||||
|
||||
type AuthSvc struct {
|
||||
fun.Ctx
|
||||
Store *store.Store
|
||||
}
|
||||
|
||||
// Login 用户名+密码 → 签发会话 token(前端存 localStorage,随 state 透传)
|
||||
func (s *AuthSvc) Login(d dto.LoginDto) (dto.SessionView, error) {
|
||||
name := strings.TrimSpace(d.Name)
|
||||
if name == "" || d.Password == "" {
|
||||
return dto.SessionView{}, fun.Error(4001, "请输入用户名和密码")
|
||||
}
|
||||
user, err := s.Store.GetUserByName(name)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return dto.SessionView{}, fun.Error(4001, "用户名或密码错误")
|
||||
}
|
||||
if err != nil {
|
||||
return dto.SessionView{}, fmt.Errorf("auth: query user failed: %w", err)
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(d.Password)) != nil {
|
||||
return dto.SessionView{}, fun.Error(4001, "用户名或密码错误")
|
||||
}
|
||||
if user.Status != "active" {
|
||||
return dto.SessionView{}, fun.Error(4003, "账号已被禁用")
|
||||
}
|
||||
token, err := newToken()
|
||||
if err != nil {
|
||||
return dto.SessionView{}, err
|
||||
}
|
||||
if err := s.Store.CreateSession(token, user.Id, sessionTTL); err != nil {
|
||||
return dto.SessionView{}, fmt.Errorf("auth: create session failed: %w", err)
|
||||
}
|
||||
log.Printf("auth: user %q logged in from %s", user.Name, s.Ip)
|
||||
view := userView(user)
|
||||
return dto.SessionView{User: &view, Token: &token}, nil
|
||||
}
|
||||
|
||||
// Me 返回当前登录用户(不重复签发 token)
|
||||
func (s *AuthSvc) Me() (dto.SessionView, error) {
|
||||
su, err := sessionFrom(s.RequestCtx)
|
||||
if err != nil {
|
||||
return dto.SessionView{}, err
|
||||
}
|
||||
view := userView(su.User)
|
||||
return dto.SessionView{User: &view}, nil
|
||||
}
|
||||
|
||||
// Logout 幂等:删掉当前会话即可
|
||||
func (s *AuthSvc) Logout() (dto.OkView, error) {
|
||||
su, err := sessionFrom(s.RequestCtx)
|
||||
if err != nil {
|
||||
return dto.OkView{Message: "已退出"}, nil
|
||||
}
|
||||
if err := s.Store.DeleteSession(su.Token); err != nil {
|
||||
return dto.OkView{}, fmt.Errorf("auth: logout failed: %w", err)
|
||||
}
|
||||
return dto.OkView{Message: "已退出"}, nil
|
||||
}
|
||||
|
||||
// ChangePassword 校验旧密码 → 改密 → 吊销其他会话(当前会话保留)
|
||||
func (s *AuthSvc) ChangePassword(d dto.ChangePasswordDto) (dto.OkView, error) {
|
||||
su, err := sessionFrom(s.RequestCtx)
|
||||
if err != nil {
|
||||
return dto.OkView{}, err
|
||||
}
|
||||
if len(d.NewPassword) < 6 {
|
||||
return dto.OkView{}, fun.Error(4001, "新密码至少 6 位")
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(su.User.PasswordHash), []byte(d.OldPassword)) != nil {
|
||||
return dto.OkView{}, fun.Error(4001, "旧密码错误")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(d.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return dto.OkView{}, err
|
||||
}
|
||||
h := string(hash)
|
||||
if err := s.Store.UpdateUser(su.User.Id, su.User.Name, su.User.Role, su.User.Status, &h); err != nil {
|
||||
return dto.OkView{}, fmt.Errorf("auth: update password failed: %w", err)
|
||||
}
|
||||
if err := s.Store.DeleteOtherSessions(su.User.Id, su.Token); err != nil {
|
||||
return dto.OkView{}, fmt.Errorf("auth: revoke sessions failed: %w", err)
|
||||
}
|
||||
return dto.OkView{Message: "密码已修改"}, nil
|
||||
}
|
||||
|
||||
func newToken() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func userView(u store.User) dto.UserView {
|
||||
return dto.UserView{
|
||||
Id: u.Id,
|
||||
Name: u.Name,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user