package api import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "io" "net/http" "strings" "browser.local/platform/domain" "browser.local/platform/service" ) const ( runEndpointHeader = "X-Run-Endpoint" runSessionTokenHeader = "X-Run-Session-Token" runTimestampHeader = "X-Run-Timestamp" runNonceHeader = "X-Run-Nonce" runSignatureHeader = "X-Run-Signature" ) type runRequestEnvelope struct { RunEndpointID string `json:"runEndpointId"` SessionToken string `json:"sessionToken"` } func (h *coreHandlers) requireRunSignature(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost || r.Body == nil { next(w, r) return } body, err := io.ReadAll(io.LimitReader(r.Body, 16<<20)) if err != nil { writeDecodeError(w, err) return } r.Body = io.NopCloser(bytes.NewReader(body)) envelope, ok := signedRunRequestEnvelope(r, body) if !ok { next(w, r) return } headerEndpoint := strings.TrimSpace(r.Header.Get(runEndpointHeader)) if headerEndpoint != "" && headerEndpoint != envelope.RunEndpointID { writeServiceError(w, service.ErrUnauthorized) return } bodySum := sha256.Sum256(body) err = h.core.AuthorizeRunRequestSignature(domain.RunRequestSignature{ RunEndpointID: envelope.RunEndpointID, SessionToken: envelope.SessionToken, Method: r.Method, Path: r.URL.Path, Timestamp: strings.TrimSpace(r.Header.Get(runTimestampHeader)), Nonce: strings.TrimSpace(r.Header.Get(runNonceHeader)), BodyHash: hex.EncodeToString(bodySum[:]), Signature: strings.TrimSpace(r.Header.Get(runSignatureHeader)), }) if err != nil { writeServiceError(w, err) return } next(w, r) } } func signedRunRequestEnvelope(r *http.Request, body []byte) (runRequestEnvelope, bool) { if isOctetStream(r.Header.Get("Content-Type")) { return runRequestEnvelope{RunEndpointID: strings.TrimSpace(r.Header.Get(runEndpointHeader)), SessionToken: strings.TrimSpace(r.Header.Get(runSessionTokenHeader))}, true } var envelope runRequestEnvelope if err := json.Unmarshal(body, &envelope); err != nil { return runRequestEnvelope{}, false } return envelope, true } func isOctetStream(contentType string) bool { mediaType := strings.ToLower(strings.TrimSpace(strings.Split(contentType, ";")[0])) return mediaType == "application/octet-stream" }