Files
cyi-box/backend/internal/service/auth_guard.go
T
chiyiandDevin 834ecacdb9 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>
2026-10-01 01:58:47 +08:00

153 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"errors"
"github.com/cyi-cc/fun"
"github.com/valyala/fasthttp"
"github.com/cyi-cc/cyi-box/backend/internal/store"
)
// 认证统一业务错误码:前端响应拦截器据此清理登录态并跳登录页
const authErrCode = 4011
type authPolicy uint8
const (
policyDeny authPolicy = iota
policyPublic
policyAuthenticated
policyAdmin
)
// endpointPolicies 显式端点策略表:缺省拒绝,新端点必须登记
var endpointPolicies = map[string]authPolicy{
"AuthSvc.Login": policyPublic,
"AuthSvc.Me": policyAuthenticated,
"AuthSvc.Logout": policyAuthenticated,
"AuthSvc.ChangePassword": policyAuthenticated,
"UserSvc.ListUsers": policyAdmin,
"UserSvc.SaveUser": policyAdmin,
"UserSvc.DeleteUser": policyAdmin,
"DashboardSvc.Stats": policyAuthenticated,
"ToolSvc.List": policyAuthenticated,
"ToolSvc.AdminList": policyAdmin,
"ToolSvc.SaveTool": policyAdmin,
"ToolSvc.DeleteTool": policyAdmin,
"VaultSvc.List": policyAuthenticated,
"VaultSvc.Save": policyAuthenticated,
"VaultSvc.Reveal": policyAuthenticated,
"VaultSvc.Totp": policyAuthenticated,
"VaultSvc.Delete": policyAuthenticated,
"BookmarkSvc.List": policyAuthenticated,
"BookmarkSvc.Save": policyAuthenticated,
"BookmarkSvc.Delete": policyAuthenticated,
"ServerSvc.List": policyAdmin,
"ServerSvc.Save": policyAdmin,
"ServerSvc.Delete": policyAdmin,
"ServerSvc.Metrics": policyAdmin,
"ServerSvc.SftpList": policyAdmin,
"ServerSvc.SftpMkdir": policyAdmin,
"ServerSvc.SftpRemove": policyAdmin,
"ServerSvc.SftpRename": policyAdmin,
"DbMgrSvc.List": policyAdmin,
"DbMgrSvc.Save": policyAdmin,
"DbMgrSvc.Delete": policyAdmin,
"DbMgrSvc.Test": policyAdmin,
"DbMgrSvc.Databases": policyAdmin,
"DbMgrSvc.Tables": policyAdmin,
"DbMgrSvc.Query": policyAdmin,
"WebSvc.Fetch": policyAuthenticated,
"DiskSvc.List": policyAuthenticated,
"DiskSvc.DeleteFile": policyAuthenticated,
"DiskSvc.CreateShare": policyAuthenticated,
"DiskSvc.ListShares": policyAuthenticated,
"DiskSvc.DeleteShare": policyAuthenticated,
"ProxySvc.List": policyAuthenticated,
"ProxySvc.Regions": policyAuthenticated,
"ProxySvc.Syncs": policyAdmin,
"ProxySvc.Stats": policyAuthenticated,
"ProxySvc.SyncNow": policyAdmin,
"ProxySvc.CheckNow": policyAdmin,
"ProxySvc.ListKeys": policyAdmin,
"ProxySvc.CreateKey": policyAdmin,
"ProxySvc.DeleteKey": policyAdmin,
"SettingSvc.Get": policyAuthenticated,
"SettingSvc.Set": policyAdmin,
"PaySvc.List": policyAdmin,
"PaySvc.Stats": policyAdmin,
"PaySvc.UpstreamStatus": policyAdmin,
"PaySvc.SaveUpstream": policyAdmin,
"PaySvc.TestPay": policyAdmin,
"PaySvc.CashierInfo": policyPublic,
"PaySvc.CashierStatus": policyPublic,
"PaySvc.Watch": policyPublic,
"LicenseSvc.Apps": policyAdmin,
"LicenseSvc.SaveApp": policyAdmin,
"LicenseSvc.DeleteApp": policyAdmin,
"LicenseSvc.List": policyAdmin,
"LicenseSvc.Stats": policyAdmin,
"LicenseSvc.Generate": policyAdmin,
"LicenseSvc.Delete": policyAdmin,
"MemorySvc.Projects": policyAdmin,
"MemorySvc.List": policyAdmin,
"MemorySvc.Get": policyAdmin,
"MemorySvc.Save": policyAdmin,
"MemorySvc.Delete": policyAdmin,
"MemorySvc.McpInfo": policyAdmin,
"MemorySvc.ResetToken": policyAdmin,
}
// AuthGuard 唯一 RPC 鉴权边界:token 由前端请求拦截器写入 ctx.State["token"],
// SQLite 会话表校验有效性,命中后用户经 SetUserValue 传给服务层
type AuthGuard struct {
Store *store.Store
}
func (g *AuthGuard) Guard(ctx fun.Ctx) error {
policy := endpointPolicies[ctx.ServiceName+"."+ctx.MethodName]
switch policy {
case policyPublic:
return nil
case policyDeny:
return fun.Error(4003, "接口未登记鉴权策略")
}
if ctx.RequestCtx == nil {
return errors.New("auth guard: missing request context")
}
token := ctx.State["token"]
if token == "" {
return fun.Error(authErrCode, "请先登录")
}
su, err := g.Store.GetSessionUser(token, sessionTTL, sessionRenewAfter)
if errors.Is(err, store.ErrNotFound) {
return fun.Error(authErrCode, "登录已失效,请重新登录")
}
if err != nil {
return err
}
if su.User.Status != "active" {
return fun.Error(4003, "账号已被禁用")
}
if policy == policyAdmin && su.User.Role != "admin" {
return fun.Error(4003, "仅管理员可访问")
}
ctx.RequestCtx.SetUserValue(sessionKey{}, su)
return nil
}
type sessionKey struct{}
// sessionFrom 取 Guard 校验过的会话;策略表保证进入服务层时必然已设置
func sessionFrom(ctx *fasthttp.RequestCtx) (store.SessionUser, error) {
if ctx == nil {
return store.SessionUser{}, errors.New("auth session: missing request context")
}
su, ok := ctx.UserValue(sessionKey{}).(store.SessionUser)
if !ok || su.User.Id == 0 {
return store.SessionUser{}, errors.New("auth session: validated session missing")
}
return su, nil
}