Add logic to check if registering user is the first one and assign 'superuser' role instead of default 'visitor' role. This ensures proper initial admin setup for new deployments.
488 lines
17 KiB
Go
488 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"super-frpc/postLog"
|
|
)
|
|
|
|
type RegisterRequest struct {
|
|
Username string `json:"username"`
|
|
Passwd string `json:"passwd"`
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Username string `json:"username"`
|
|
Passwd string `json:"passwd"`
|
|
}
|
|
|
|
type CreateUserRequest struct {
|
|
Username string `json:"username"`
|
|
Passwd string `json:"passwd"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type ModifyUserRequest struct {
|
|
UserID int `json:"userID"`
|
|
Username string `json:"username"`
|
|
Passwd string `json:"passwd"`
|
|
}
|
|
|
|
type ModifyUserTypeRequest struct {
|
|
UserID int `json:"userID"`
|
|
Type string `json:"newType"`
|
|
}
|
|
|
|
type RemoveUserRequest struct {
|
|
TargetUserID int `json:"targetUserID"`
|
|
}
|
|
|
|
type RemoveSessionRequest struct {
|
|
SessionID string `json:"sessionID"`
|
|
}
|
|
|
|
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Warning(fmt.Sprintf("[RegisterHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
if !ValidateTimeStamp(r.Header) {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid or missing X-Timestamp in header")
|
|
postLog.Warning(fmt.Sprintf("[RegisterHandler] Invalid or missing X-Timestamp in header: %s", r.Header.Get("X-Timestamp")))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[RegisterHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req RegisterRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[RegisterHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.Username == "" || req.Passwd == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Username and password are required")
|
|
postLog.Warning("[RegisterHandler] New user registration failed: username or password is empty")
|
|
return
|
|
}
|
|
|
|
if !isValidInput(req.Username) || !isValidInput(req.Passwd) {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid input: contains illegal characters")
|
|
postLog.Debug(fmt.Sprintf("[RegisterHandler] New user registration failed: username or password contains illegal characters \"%s\":\"%s\"", req.Username, req.Passwd))
|
|
return
|
|
}
|
|
|
|
if !isValidPassword(req.Passwd) {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Password does not meet complexity requirements (must contain uppercase, lowercase, digit, and special character)")
|
|
postLog.Debug(fmt.Sprintf("[RegisterHandler] New user registration failed: password \"%s\" does not meet complexity requirements", req.Passwd))
|
|
return
|
|
}
|
|
|
|
// Check weather the user is the first user, if it is, give the superuser level
|
|
userList, err := DBListUsers()
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to list users: %v", err))
|
|
return
|
|
}
|
|
newUserType := ""
|
|
if len(userList) == 0 {
|
|
newUserType = "superuser"
|
|
} else {
|
|
newUserType = "visitor"
|
|
}
|
|
|
|
userID, err := AddUser(req.Username, req.Passwd, newUserType)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to register user \"%s\": %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to retrieve user after registration")
|
|
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to retrieve user \"%s\" after registration: %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "user registered successfully", map[string]interface{}{
|
|
"userID": user.UserID,
|
|
"username": user.Username,
|
|
"type": user.Type,
|
|
})
|
|
}
|
|
|
|
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
if !ValidateTimeStamp(r.Header) {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid or missing X-Timestamp in header")
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req LoginRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.Username == "" || req.Passwd == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Username and password are required")
|
|
postLog.Warning("[LoginHandler] Login failed: username or password is empty")
|
|
return
|
|
}
|
|
|
|
if !isValidInput(req.Username) || !isValidInput(req.Passwd) {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid input: contains illegal characters")
|
|
postLog.Debug(fmt.Sprintf("[LoginHandler] Login failed: username or password contains illegal characters \"%s\":\"%s\"", req.Username, req.Passwd))
|
|
return
|
|
}
|
|
|
|
user, err := GetUserByUsername(req.Username)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, "User not exist")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: User not exist \"%s\"", req.Username))
|
|
return
|
|
}
|
|
|
|
if !verifyPassword(req.Passwd, user.Passwd) {
|
|
SendErrorResponse(w, http.StatusUnauthorized, "Invalid password")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: invalid password for user \"%s\"", req.Username))
|
|
return
|
|
}
|
|
|
|
existingTokenInfo, err := GetTokenInfo(user.UserID)
|
|
if err == nil && existingTokenInfo != nil {
|
|
SendErrorResponse(w, http.StatusConflict, "User is already logged in")
|
|
postLog.Warning(fmt.Sprintf("[LoginHandler] Login failed: user \"%s\" is already logged in", req.Username))
|
|
return
|
|
}
|
|
|
|
token, err := GenerateToken(user.UserID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to generate token")
|
|
postLog.Error(fmt.Sprintf("[LoginHandler] Failed to generate token for user \"%s\": %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
if err := JoinSession(user.UserID, user.Username, token); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create session")
|
|
postLog.Error(fmt.Sprintf("[LoginHandler] Failed to create session for user \"%s\": %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "Login successful", map[string]interface{}{
|
|
"token": token,
|
|
"userID": user.UserID,
|
|
"username": user.Username,
|
|
"type": user.Type,
|
|
})
|
|
postLog.Info(fmt.Sprintf("[LoginHandler] User \"%s\" Login successful", req.Username))
|
|
}
|
|
|
|
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, err := Auth(w, r, http.MethodGet)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[LogoutHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
sessionTokenMux.RLock()
|
|
sessionID := ""
|
|
for sid, token := range sessionTokenMap {
|
|
if token == r.Header.Get("X-Token") {
|
|
sessionID = sid
|
|
break
|
|
}
|
|
}
|
|
sessionTokenMux.RUnlock()
|
|
|
|
if sessionID == "" {
|
|
SendErrorResponse(w, http.StatusNotFound, "Session not found for token")
|
|
postLog.Warning(fmt.Sprintf("[LogoutHandler] Session not found for token from user [%d]%s", userID, GetUsernameByID(userID)))
|
|
return
|
|
}
|
|
|
|
if err := RemoveSession(sessionID); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to logout")
|
|
postLog.Error(fmt.Sprintf("[LogoutHandler] Failed to logout user [%d]%s: %v", userID, GetUsernameByID(userID), err))
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "Logout successful", nil)
|
|
postLog.Info(fmt.Sprintf("[LogoutHandler] User [%d]%s Logout successful", userID, GetUsernameByID(userID)))
|
|
}
|
|
|
|
func RemoveSessionHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, err := Auth(w, r, http.MethodPost, "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[RemoveSessionHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[RemoveSessionHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req RemoveSessionRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[RemoveSessionHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.SessionID == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "SessionID is required")
|
|
postLog.Warning("[RemoveSessionHandler] SessionID is empty")
|
|
return
|
|
}
|
|
|
|
if err := RemoveSession(req.SessionID); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to remove session: %v", err))
|
|
postLog.Error(fmt.Sprintf("[RemoveSessionHandler] Failed to remove session %s: %v", req.SessionID, err))
|
|
return
|
|
}
|
|
|
|
postLog.Info(fmt.Sprintf("[RemoveSessionHandler] User [%d]%s removed session %s", userID, GetUsernameByID(userID), req.SessionID))
|
|
SendSuccessResponse(w, "Session removed successfully", nil)
|
|
}
|
|
|
|
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
_, err := Auth(w, r, http.MethodPost, "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[CreateUserHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[CreateUserHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req CreateUserRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[CreateUserHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.Username == "" || req.Passwd == "" || req.Type == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Username, password, and type are required")
|
|
postLog.Warning("[CreateUserHandler] CreateUser failed: username, password, or type is empty")
|
|
return
|
|
}
|
|
|
|
if req.Type != "admin" && req.Type != "user" && req.Type != "superuser" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid type: must be 'admin' or 'user' or 'superuser'")
|
|
postLog.Warning(fmt.Sprintf("[CreateUserHandler] CreateUser failed: invalid type: %s", req.Type))
|
|
return
|
|
}
|
|
|
|
userID, err := AddUser(req.Username, req.Passwd, req.Type)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[RegisterHandler] Failed to register user \"%s\": %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
user, err := GetUserByID(userID)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to retrieve user after creation")
|
|
postLog.Error(fmt.Sprintf("[CreateUserHandler] Failed to retrieve user \"%s\" after creation: %v", req.Username, err))
|
|
return
|
|
}
|
|
|
|
SendSuccessResponse(w, "User created successfully", map[string]interface{}{
|
|
"userID": user.UserID,
|
|
"username": user.Username,
|
|
"type": user.Type,
|
|
})
|
|
postLog.Info(fmt.Sprintf("[CreateUserHandler] User \"%s\" created successfully with ID: %d", req.Username, userID))
|
|
}
|
|
|
|
func ModifyUserHandler (w http.ResponseWriter, r *http.Request) {
|
|
_, err := Auth(w, r, http.MethodPost, "user", "admin", "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, fmt.Sprintf("Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req ModifyUserRequest
|
|
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.UserID == 0 {
|
|
SendErrorResponse(w, http.StatusBadRequest, "UserID is required")
|
|
postLog.Warning("[ModifyUserHandler] ModifyUser failed: UserID is empty")
|
|
return
|
|
}
|
|
|
|
if req.Username == "" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Username is required")
|
|
postLog.Warning("[ModifyUserHandler] ModifyUser failed: username is empty")
|
|
return
|
|
}
|
|
|
|
if err := DBUpdateUser(req.UserID, req.Username, req.Passwd); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[ModifyUserHandler] Failed to update user [%d]: %v", req.UserID, err))
|
|
return
|
|
}
|
|
SendSuccessResponse(w, "User updated successfully", nil)
|
|
postLog.Info(fmt.Sprintf("[ModifyUserHandler] User [%d]%s updated successfully to: %s", req.UserID, GetUsernameByID(req.UserID), req.Username))
|
|
}
|
|
|
|
func ModifyUserTypeHandler (w http.ResponseWriter, r *http.Request) {
|
|
_, err := Auth(w, r, http.MethodPost, "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserTypeHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserTypeHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req ModifyUserTypeRequest
|
|
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserTypeHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
if req.UserID == 0 {
|
|
SendErrorResponse(w, http.StatusBadRequest, "UserID is required")
|
|
postLog.Warning("[ModifyUserTypeHandler] ModifyUserType failed: UserID is empty")
|
|
return
|
|
}
|
|
if req.Type != "admin" && req.Type != "visitor" && req.Type != "superuser" {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid type: must be 'admin' or 'visitor' or 'superuser'")
|
|
postLog.Warning(fmt.Sprintf("[ModifyUserTypeHandler] ModifyUserType failed: invalid type: %s", req.Type))
|
|
return
|
|
}
|
|
if err := DBUpdateUserType(req.UserID, req.Type); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[ModifyUserTypeHandler] Failed to update user type [%d]: %v", req.UserID, err))
|
|
return
|
|
}
|
|
SendSuccessResponse(w, "User type updated successfully", nil)
|
|
postLog.Info(fmt.Sprintf("[ModifyUserTypeHandler] User [%d]%s type updated successfully to: %s", req.UserID, GetUsernameByID(req.UserID), req.Type))
|
|
}
|
|
|
|
func RemoveUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
_, err := Auth(w, r, http.MethodPost, "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[RemoveUserHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
|
postLog.Warning(fmt.Sprintf("[RemoveUserHandler] Failed to read request body: %v", err))
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req RemoveUserRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
|
postLog.Warning(fmt.Sprintf("[RemoveUserHandler] Invalid request format: %v", err))
|
|
return
|
|
}
|
|
if req.TargetUserID == 0 {
|
|
SendErrorResponse(w, http.StatusBadRequest, "TargetUserID is required")
|
|
postLog.Warning("[RemoveUserHandler] RemoveUser failed: TargetUserID is empty")
|
|
return
|
|
}
|
|
if err := RemoveUser(req.TargetUserID); err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, err.Error())
|
|
postLog.Error(fmt.Sprintf("[RemoveUserHandler] Failed to remove user [%d]: %v", req.TargetUserID, err))
|
|
return
|
|
}
|
|
SendSuccessResponse(w, "User removed successfully", nil)
|
|
}
|
|
|
|
func ListUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
_, err := Auth(w, r, http.MethodGet, "superuser")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[ListUserHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
userList, err := DBListUsers()
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to list users")
|
|
postLog.Error(fmt.Sprintf("[ListUserHandler] Failed to list users: %v", err))
|
|
return
|
|
}
|
|
SendSuccessResponse(w, "User list retrieved successfully", userList)
|
|
}
|
|
|
|
func ListActiveSessionsHandler(w http.ResponseWriter, r *http.Request) {
|
|
userID, err := Auth(w, r, http.MethodGet, "superuser", "admin")
|
|
if err != nil {
|
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
|
postLog.Warning(fmt.Sprintf("[ListActiveSessionsHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
sessions := ListActiveSessions()
|
|
postLog.Debug(fmt.Sprintf("[ListActiveSessionsHandler] User [%d]%s listed %d active sessions", userID, GetUsernameByID(userID), len(sessions)))
|
|
SendSuccessResponse(w, "Active sessions listed", sessions)
|
|
}
|