You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
3.5 KiB
Go

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)
}
}