package api import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" "faculty_media_report/dbi" "github.com/golang-jwt/jwt/v5" ) type jsonRPCRequest struct { Jsonrpc string `json:"jsonrpc"` Method string `json:"method"` Params json.RawMessage `json:"params"` ID *int `json:"id"` } type jsonRPCErrorBody struct { Code int `json:"code"` Message string `json:"message"` } type jsonRPCResponse struct { Jsonrpc string `json:"jsonrpc"` Result interface{} `json:"result,omitempty"` Error *jsonRPCErrorBody `json:"error,omitempty"` ID *int `json:"id"` } func writeJSONRPC(w http.ResponseWriter, resp jsonRPCResponse) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } func jsonRPCErr(w http.ResponseWriter, code int, message string, id *int) { writeJSONRPC(w, jsonRPCResponse{ Jsonrpc: "2.0", Error: &jsonRPCErrorBody{Code: code, Message: message}, ID: id, }) } func HandleAPI(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { http.Error(w, "missing or invalid Authorization header", http.StatusUnauthorized) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") db, err := dbi.GetDbConn() if err != nil { http.Error(w, "internal error - error getting db connection", http.StatusInternalServerError) return } defer db.Close() conn, err := db.Conn(context.Background()) if err != nil { http.Error(w, "internal error - error setting connection background", http.StatusInternalServerError) return } defer conn.Close() var user dbi.User token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } sub, err := t.Claims.GetSubject() if err != nil { return nil, fmt.Errorf("missing sub claim") } user, err = dbi.GetUser(conn, sub) if err != nil { return nil, fmt.Errorf("user not found") } return []byte(user.APIKey), nil }, jwt.WithExpirationRequired()) if err != nil || !token.Valid { if err != nil { http.Error(w, "unauthorized: "+err.Error(), http.StatusUnauthorized) } else { http.Error(w, "unauthorized", http.StatusUnauthorized) } return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { http.Error(w, "invalid token claims", http.StatusUnauthorized) return } iat, err := claims.GetIssuedAt() if err != nil || iat == nil { http.Error(w, "missing iat claim", http.StatusUnauthorized) return } if iat.After(time.Now()) { http.Error(w, "iat is in the future", http.StatusUnauthorized) return } var req jsonRPCRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { jsonRPCErr(w, -32700, "Parse error", nil) return } if req.Jsonrpc != "2.0" { jsonRPCErr(w, -32600, `Invalid Request: jsonrpc must be "2.0"`, req.ID) return } if req.Method == "" { jsonRPCErr(w, -32600, "Invalid Request: method is required", req.ID) return } if req.Params == nil { jsonRPCErr(w, -32600, "Invalid Request: params is required", req.ID) return } if req.ID == nil { jsonRPCErr(w, -32600, "Invalid Request: id is required", req.ID) return } switch req.Method { case "report_activity": ReportActivity(w, conn, req, user) case "mark_posted": MarkPosted(w, conn, req, user) default: jsonRPCErr(w, -32601, "Method not found", req.ID) } }