feat: 完善用户治理与登录白名单
This commit is contained in:
+83
-2
@@ -18,6 +18,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/auth"
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
@@ -79,7 +80,7 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||
}
|
||||
}()
|
||||
if req.Method != http.MethodGet {
|
||||
if req.Method != http.MethodGet && req.Method != http.MethodPost {
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
return
|
||||
}
|
||||
@@ -97,6 +98,10 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if req.Method == http.MethodPost && !(len(parts) == 3 && parts[2] == "users") {
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(parts) == 4 && parts[2] == "dashboard" && parts[3] == "overview":
|
||||
@@ -107,7 +112,9 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
serveInternalAdminResourceList(wrt, req, tenant.ID, "private-messages", "")
|
||||
case len(parts) == 3 && parts[2] == "group-messages":
|
||||
serveInternalAdminResourceList(wrt, req, tenant.ID, "group-messages", "")
|
||||
case len(parts) == 3 && parts[2] == "users":
|
||||
case req.Method == http.MethodPost && len(parts) == 3 && parts[2] == "users":
|
||||
serveInternalUserCreate(wrt, req, tenant.ID)
|
||||
case req.Method == http.MethodGet && len(parts) == 3 && parts[2] == "users":
|
||||
serveInternalAdminResourceList(wrt, req, tenant.ID, "users", "")
|
||||
case len(parts) == 4 && parts[2] == "users":
|
||||
serveInternalUserQuery(wrt, req, tenant.ID, parts[3])
|
||||
@@ -156,6 +163,80 @@ func serveInternalAdminQuery(wrt http.ResponseWriter, req *http.Request, prefix
|
||||
}
|
||||
}
|
||||
|
||||
type internalUserCreateRequest struct {
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func serveInternalUserCreate(wrt http.ResponseWriter, req *http.Request, tenantID types.TenantID) {
|
||||
var body internalUserCreateRequest
|
||||
if err := json.NewDecoder(io.LimitReader(req.Body, 1<<20)).Decode(&body); err != nil {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
body.Username = strings.ToLower(strings.TrimSpace(body.Username))
|
||||
body.Nickname = strings.TrimSpace(body.Nickname)
|
||||
body.Phone = strings.TrimSpace(body.Phone)
|
||||
if body.Username == "" {
|
||||
var err error
|
||||
body.Username, err = randomRegistrationUsername()
|
||||
if err != nil {
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := validateRegistrationUsername(body.Username); err != nil || len([]rune(body.Password)) < 3 || len([]byte(body.Password)) > 72 {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "用户名或密码格式不正确", nil)
|
||||
return
|
||||
}
|
||||
if body.Phone != "" {
|
||||
phone, err := normalizeIMPhone(body.Phone, "CN")
|
||||
if err != nil {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "手机号格式不正确", nil)
|
||||
return
|
||||
}
|
||||
body.Phone = phone
|
||||
}
|
||||
authHandler := store.Store.GetLogicalAuthHandler("basic")
|
||||
if authHandler == nil {
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "basic authentication unavailable", nil)
|
||||
return
|
||||
}
|
||||
authCtx := auth.AuthContext{TenantID: tenantID, RemoteAddr: req.RemoteAddr}
|
||||
secret := []byte(body.Username + ":" + body.Password)
|
||||
if unique, err := authHandler.IsUnique(authCtx, secret); err != nil || !unique {
|
||||
writeIMError(wrt, req, http.StatusConflict, 30001, "DUPLICATE_USER", "用户名已存在", nil)
|
||||
return
|
||||
}
|
||||
user := &types.User{
|
||||
TenantID: tenantID,
|
||||
State: types.StateOK,
|
||||
Username: body.Username,
|
||||
Phone: body.Phone,
|
||||
Public: map[string]any{"fn": firstNonEmpty(body.Nickname, body.Username)},
|
||||
}
|
||||
user.Access.Auth = getDefaultAccess(types.TopicCatP2P, true, false) | getDefaultAccess(types.TopicCatGrp, true, false)
|
||||
user.Access.Anon = getDefaultAccess(types.TopicCatP2P, false, false) | getDefaultAccess(types.TopicCatGrp, false, false)
|
||||
created, err := store.Users.Create(tenantID, user, nil)
|
||||
if err != nil {
|
||||
writeInternalQueryError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if _, err = authHandler.AddRecord(authCtx, &auth.Rec{TenantID: tenantID, Uid: created.Uid()}, secret); err != nil {
|
||||
_ = store.Users.Delete(tenantID, created.Uid(), true)
|
||||
writeInternalQueryError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{
|
||||
"user_id": store.DecodeUid(created.Uid()),
|
||||
"username": created.Username,
|
||||
"nickname": body.Nickname,
|
||||
"phone": created.Phone,
|
||||
})
|
||||
}
|
||||
|
||||
func authorizeInternalAPI(wrt http.ResponseWriter, req *http.Request, capability string) bool {
|
||||
if !internalAPI.config.Enabled {
|
||||
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonMalformedRequest, "resource not found", nil)
|
||||
|
||||
Reference in New Issue
Block a user