refactor(api): switch to header-based auth for GET requests

- Replace body parsing with header validation for GET endpoints
- Update router to properly handle GET vs POST requests
- Add new ValidateRequestWithHeader function
- Update README to document header requirements
This commit is contained in:
2026-02-27 23:17:18 +08:00
parent af5af2a04f
commit 72eb90957c
4 changed files with 69 additions and 24 deletions
+15 -6
View File
@@ -68,6 +68,12 @@ All API responses are returned in JSON format:
}
```
**Important Notes:**
- For **POST** requests: All data is sent in the request body as JSON
- For **GET** requests: All data is sent in HTTP headers (prefixed with `X-`)
- All requests require authentication (except `/register` and `/login`)
- Timestamps are in milliseconds (Unix timestamp)
---
### 1. Register User
@@ -269,13 +275,16 @@ You can modify multiple fields at once:
**Method:** GET
**Auth Required:** Yes (token)
**Request:**
```json
{
"token": "your_token",
"timeStamp": 1704067200000
}
**Request Headers:**
```
X-Token: your_token
X-Timestamp: 1704067200000
```
| Header | Type | Required | Description |
|--------|------|----------|-------------|
| X-Token | string | Yes | Authentication token |
| X-Timestamp | int64 | Yes | Client timestamp in milliseconds |
**Response (admin/superuser):**
```json
+1 -8
View File
@@ -433,14 +433,7 @@ func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
return
}
defer r.Body.Close()
userID, _, err := ValidateRequestWithBody(w, r, body)
userID, _, err := ValidateRequestWithHeader(w, r)
if err != nil {
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
return
+41
View File
@@ -249,6 +249,47 @@ func ValidateRequestWithBody(w http.ResponseWriter, r *http.Request, body []byte
return userID, token, nil
}
func ValidateRequestWithHeader(w http.ResponseWriter, r *http.Request, requiredFields ...string) (int, string, error) {
token := r.Header.Get("X-Token")
if token == "" {
return 0, "", errors.New("token is required in header")
}
timeStampStr := r.Header.Get("X-Timestamp")
timeStamp := int64(0)
if timeStampStr != "" {
var err error
timeStamp, err = strconv.ParseInt(timeStampStr, 10, 64)
if err != nil {
return 0, "", errors.New("invalid timestamp format in header")
}
} else if !globalConfig.Debug {
return 0, "", errors.New("timestamp is required in header")
}
if err := ValidateTimeStamp(timeStamp); err != nil {
return 0, "", err
}
userID, err := extractUserIDFromToken(token)
if err != nil {
return 0, "", err
}
if err := ValidateToken(userID, token); err != nil {
return 0, "", err
}
for _, field := range requiredFields {
headerValue := r.Header.Get(fmt.Sprintf("X-%s", field))
if headerValue == "" {
return 0, "", fmt.Errorf("required field %s is missing in header", field)
}
}
return userID, token, nil
}
func GetUserType(userID int) (string, error) {
user, err := GetUserByID(userID)
if err != nil {
+12 -10
View File
@@ -21,16 +21,24 @@ func setupRoutes() {
remainingPath := path[len("/frpcAct/instanceMgr/"):]
if r.Method == http.MethodPost {
if remainingPath == "create" {
CreateInstanceHandler(w, r)
if r.Method == http.MethodGet {
if remainingPath == "list" {
ListInstancesHandler(w, r)
return
}
if remainingPath == "list" {
instanceName := strings.Trim(remainingPath, "/")
if instanceName != "" {
ListInstancesHandler(w, r)
return
}
}
if r.Method == http.MethodPost {
if remainingPath == "create" {
CreateInstanceHandler(w, r)
return
}
if strings.HasSuffix(remainingPath, "/delete") {
instanceName := strings.TrimSuffix(remainingPath, "/delete")
@@ -47,12 +55,6 @@ func setupRoutes() {
return
}
}
instanceName := strings.Trim(remainingPath, "/")
if instanceName != "" {
ListInstancesHandler(w, r)
return
}
}
SendErrorResponse(w, http.StatusNotFound, "endpoint not found")