package auth import ( "net/url" "strings" "time" "server/models" authsvc "server/services/auth" beego "github.com/beego/beego/v2/server/web" ) // AuthSessionController 会话管理(用户自助:查看在线设备、踢下线) type AuthSessionController struct { beego.Controller } func (c *AuthSessionController) serveJSON(data map[string]interface{}) { c.Data["json"] = data _ = c.ServeJSON() } // DevicesPage 在线设备页(浏览器访问,依赖认证中心会话 Cookie) // GET /auth/devices func (c *AuthSessionController) DevicesPage() { sid := strings.TrimSpace(c.Ctx.GetCookie(sessionCookieName)) if sid == "" { c.Redirect("/auth/login", 302) return } session, err := authsvc.GetSession(sid) if err != nil { c.Redirect("/auth/login", 302) return } list, err := authsvc.ListActiveSessions(session.IdentityID) if err != nil { list = nil } items := make([]map[string]interface{}, 0, len(list)) now := time.Now() for _, s := range list { device := "未知设备" if s.DeviceName != nil && strings.TrimSpace(*s.DeviceName) != "" { device = *s.DeviceName } ip := "-" if s.IP != nil { ip = *s.IP } client := s.ClientID if client == "" { client = "-" } items = append(items, map[string]interface{}{ "Sid": s.Sid, "Current": s.Sid == sid, "Device": device, "IP": ip, "Client": client, "LoginAt": s.LoginAt.Format("2006-01-02 15:04"), "LastAt": s.LastAccessAt.Format("2006-01-02 15:04"), "Expired": s.ExpiresAt.Before(now), "Tid": s.Tid, }) } c.Data["Sessions"] = items c.Data["Success"] = c.GetString("success") c.Data["Error"] = c.GetString("error") c.TplName = "auth/devices.tpl" } // KickPage 踢下线指定设备(页面入口,踢完后回到设备页) // GET /auth/devices/kick?sid= func (c *AuthSessionController) KickPage() { target := strings.TrimSpace(c.GetString("sid")) current := strings.TrimSpace(c.Ctx.GetCookie(sessionCookieName)) if current == "" { c.Redirect("/auth/login", 302) return } session, err := authsvc.GetSession(current) if err != nil { c.Redirect("/auth/login", 302) return } if target == "" { c.Redirect("/auth/devices?error="+url.QueryEscape("参数错误"), 302) return } // 只允许操作自己名下的会话 owned := false if list, e := authsvc.ListActiveSessions(session.IdentityID); e == nil { for _, s := range list { if s.Sid == target { owned = true break } } } if !owned { c.Redirect("/auth/devices?error="+url.QueryEscape("无权操作该设备"), 302) return } if target == current { c.Redirect("/auth/devices?error="+url.QueryEscape("不能踢掉当前设备,请直接登出"), 302) return } if err := authsvc.RevokeSession(target, models.RevokeReasonAdmin); err != nil { c.Redirect("/auth/devices?error="+url.QueryEscape("操作失败"), 302) return } // 同步吊销该会话的刷新令牌,确保对方无法续期 _, _ = models.Orm.QueryTable(new(models.AuthRefreshToken)). Filter("sid", target). Update(map[string]interface{}{"revoked": 1}) c.Redirect("/auth/devices?success="+url.QueryEscape("已将该设备下线"), 302) }