mirror of
https://github.com/MirageNetwork/MirageServer.git
synced 2026-04-23 08:00:14 +08:00
3533cbe560
Signed-off-by: Chenyang Gao <gps949@outlook.com>
182 lines
5.8 KiB
Go
182 lines
5.8 KiB
Go
package controller
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"tailscale.com/tailcfg"
|
|
)
|
|
|
|
type UsersData struct {
|
|
Users []UserData `json:"users"`
|
|
ExternalUsers []UserData `json:"externalUsers"`
|
|
CurrentUserID int64 `json:"currentUserID"`
|
|
OwnerID int64 `json:"ownerID"`
|
|
DomainHasOwner bool `json:"domainHasOwner"`
|
|
}
|
|
type UserData struct {
|
|
Id string `json:"id"`
|
|
StableId string `json:"stableId"`
|
|
DisplayName string `json:"displayName"`
|
|
LoginName string `json:"loginName"`
|
|
DomainName string `json:"domainName"` // company
|
|
SharedDomain bool `json:"sharedDomain"` // ? false
|
|
ProfilePicURL string `json:"profilePicURL"`
|
|
Created time.Time `json:"created"` //timestamp
|
|
Role string `json:"role"`
|
|
IsAdmin bool `json:"isAdmin"`
|
|
IsOwner bool `json:"isOwner"`
|
|
Status string `json:"status"` // "active", 是否suspend
|
|
DeviceCount int `json:"deviceCount"` // ? 0
|
|
CanEditBilling bool `json:"canEditBilling"`
|
|
NeedsOnboarding bool `json:"needsOnboarding"` // 待审核?
|
|
LastSeen time.Time `json:"lastSeen"` // timestamp
|
|
CurrentlyConnected bool `json:"currentlyConnected"`
|
|
}
|
|
|
|
// 接受/admin/api/users的Get请求,用于查询用户
|
|
func (h *Mirage) CAPIGetUsers(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
) {
|
|
user, err := h.verifyTokenIDandGetUser(w, r)
|
|
if err != nil || user.CheckEmpty() {
|
|
h.doAPIResponse(w, "用户信息核对失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
resData := UsersData{
|
|
ExternalUsers: []UserData{},
|
|
CurrentUserID: user.ID,
|
|
OwnerID: user.ID, // TODO: 后续区分Admin和Owner后不再直接赋值当前用户ID而需要查询
|
|
DomainHasOwner: true, // TODO: 在不确定该项何时为false的情况前先默认为true
|
|
}
|
|
users, err := h.ListOrgUsers(user.OrganizationID)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "用户列表获取失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
for _, u := range users {
|
|
devCount := 0
|
|
lastSeen := u.CreatedAt
|
|
currentlyConnected := false
|
|
|
|
userMachines, err := h.ListMachinesByUser(u.ID)
|
|
if err == nil {
|
|
devCount = len(userMachines)
|
|
for _, m := range userMachines {
|
|
if m.LastSeen.After(lastSeen) {
|
|
lastSeen = *m.LastSeen
|
|
}
|
|
if !currentlyConnected && m.isOnline() {
|
|
currentlyConnected = true
|
|
}
|
|
}
|
|
}
|
|
resData.Users = append(resData.Users, UserData{
|
|
Id: strconv.FormatInt(u.ID, 10),
|
|
StableId: u.StableID,
|
|
DisplayName: u.Display_Name,
|
|
LoginName: u.Name,
|
|
DomainName: u.Organization.Name,
|
|
SharedDomain: false, //???
|
|
ProfilePicURL: "", // TODO: 我们暂时没存头像
|
|
Created: u.CreatedAt.UTC(),
|
|
Role: RoleStr[u.Role],
|
|
IsAdmin: u.Role == RoleOwner, // TODO: 后续区分Admin和Owner后需要变为不等比较
|
|
IsOwner: u.Role == RoleOwner,
|
|
Status: "active", // TODO: 在添加suspend功能后需要变为判断
|
|
DeviceCount: devCount, // TODO: 不知为啥官方目前是0,我们做下统计
|
|
CanEditBilling: u.Role == RoleOwner, // TODO: 后续有更多角色时需要变更
|
|
NeedsOnboarding: false, // TODO: 后续增加新成员需approve后需要使用
|
|
LastSeen: lastSeen.UTC().Round(time.Second),
|
|
CurrentlyConnected: currentlyConnected,
|
|
})
|
|
}
|
|
h.doAPIResponse(w, "", resData)
|
|
}
|
|
|
|
// 请求报文:
|
|
type UserActionREQ struct {
|
|
UserID string `json:"userID"`
|
|
Action string `json:"action"` //"restore_user", "suspend_user", "delete_user", "set_owner","set_member"
|
|
}
|
|
|
|
// 接受/admin/api/users的Post请求,用于对用户操作
|
|
func (h *Mirage) CAPIPostUsers(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
) {
|
|
user, err := h.verifyTokenIDandGetUser(w, r)
|
|
if err != nil || user.CheckEmpty() {
|
|
h.doAPIResponse(w, "用户信息核对失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
err = r.ParseForm()
|
|
if err != nil {
|
|
h.doAPIResponse(w, "用户请求解析失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
reqData := UserActionREQ{}
|
|
json.NewDecoder(r.Body).Decode(&reqData)
|
|
|
|
switch reqData.Action {
|
|
case "set_owner":
|
|
if user.Role != RoleOwner {
|
|
h.doAPIResponse(w, "权限不足", nil)
|
|
return
|
|
}
|
|
targetUID, err := strconv.ParseInt(reqData.UserID, 10, 64)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户ID解析失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
err = h.TransferOwner(tailcfg.UserID(user.ID), tailcfg.UserID(targetUID))
|
|
if err != nil {
|
|
h.doAPIResponse(w, "修改用户角色失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
h.doAPIResponse(w, "", nil)
|
|
case "delete_user":
|
|
targetUID, err := strconv.ParseInt(reqData.UserID, 10, 64)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户ID解析失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
targetUser, err := h.GetUserByID(tailcfg.UserID(targetUID))
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户信息获取失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
if targetUser.Role == RoleOwner {
|
|
h.doAPIResponse(w, "无法删除Owner,请联系我们", nil)
|
|
return
|
|
}
|
|
mlist, err := h.ListMachinesByUser(targetUID)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户设备列表获取失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
for _, m := range mlist {
|
|
if m.ForcedTags != nil && len([]string(m.ForcedTags)) > 0 {
|
|
continue
|
|
}
|
|
err = h.HardDeleteMachine(&m)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户设备删除失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
h.NotifyNaviOrgNodesChange(user.OrganizationID, "", m.NodeKey)
|
|
}
|
|
err = h.DestroyUser(targetUser.Name, targetUser.Organization.Name, targetUser.Organization.Provider)
|
|
if err != nil {
|
|
h.doAPIResponse(w, "目标用户删除失败:"+err.Error(), nil)
|
|
return
|
|
}
|
|
h.doAPIResponse(w, "", nil)
|
|
}
|
|
}
|