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:
@@ -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
|
### 1. Register User
|
||||||
@@ -269,13 +275,16 @@ You can modify multiple fields at once:
|
|||||||
**Method:** GET
|
**Method:** GET
|
||||||
**Auth Required:** Yes (token)
|
**Auth Required:** Yes (token)
|
||||||
|
|
||||||
**Request:**
|
**Request Headers:**
|
||||||
```json
|
|
||||||
{
|
|
||||||
"token": "your_token",
|
|
||||||
"timeStamp": 1704067200000
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
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):**
|
**Response (admin/superuser):**
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -433,14 +433,7 @@ func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
||||||
if err != nil {
|
|
||||||
SendErrorResponse(w, http.StatusBadRequest, "failed to read request body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
userID, _, err := ValidateRequestWithBody(w, r, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
SendErrorResponse(w, http.StatusUnauthorized, err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
+41
@@ -249,6 +249,47 @@ func ValidateRequestWithBody(w http.ResponseWriter, r *http.Request, body []byte
|
|||||||
return userID, token, nil
|
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) {
|
func GetUserType(userID int) (string, error) {
|
||||||
user, err := GetUserByID(userID)
|
user, err := GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,16 +21,24 @@ func setupRoutes() {
|
|||||||
|
|
||||||
remainingPath := path[len("/frpcAct/instanceMgr/"):]
|
remainingPath := path[len("/frpcAct/instanceMgr/"):]
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodGet {
|
||||||
if remainingPath == "create" {
|
if remainingPath == "list" {
|
||||||
CreateInstanceHandler(w, r)
|
ListInstancesHandler(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if remainingPath == "list" {
|
instanceName := strings.Trim(remainingPath, "/")
|
||||||
|
if instanceName != "" {
|
||||||
ListInstancesHandler(w, r)
|
ListInstancesHandler(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
if remainingPath == "create" {
|
||||||
|
CreateInstanceHandler(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(remainingPath, "/delete") {
|
if strings.HasSuffix(remainingPath, "/delete") {
|
||||||
instanceName := strings.TrimSuffix(remainingPath, "/delete")
|
instanceName := strings.TrimSuffix(remainingPath, "/delete")
|
||||||
@@ -47,12 +55,6 @@ func setupRoutes() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceName := strings.Trim(remainingPath, "/")
|
|
||||||
if instanceName != "" {
|
|
||||||
ListInstancesHandler(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SendErrorResponse(w, http.StatusNotFound, "endpoint not found")
|
SendErrorResponse(w, http.StatusNotFound, "endpoint not found")
|
||||||
|
|||||||
Reference in New Issue
Block a user