Skip to content

manyrows/manyrows-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

manyrows-go

Go libraries for integrating with a ManyRows install.

The examples below assume a self-hosted deployment at https://manyrows.example.com. Swap in whatever host your install runs on (http://localhost:3000 for local development, your own domain in production).

Install

go get github.com/manyrows/manyrows-go

Client

The client wraps the ManyRows Server API. Requires an API key.

import manyrows "github.com/manyrows/manyrows-go"

client := manyrows.NewClient(
    "https://manyrows.example.com", // base URL of your install
    "your-workspace",               // workspace slug
    "your-app-id",                  // app ID
    "mr_a1b2c3d4_yourSecretKey",    // API key
)

Delivery (config + feature flags)

delivery, err := client.GetDelivery()
// delivery.Config.Public, delivery.Config.Private, delivery.Config.Secrets
// delivery.Flags.Client, delivery.Flags.Server

Decrypt secrets

Secret values come back as encrypted envelopes under delivery.Config.Secrets[i].Envelope. Use the secrets package with your workspace private key (the JWK you downloaded when you generated the keypair in your install's admin UI) to decrypt server-side:

import (
    "encoding/json"
    "github.com/manyrows/manyrows-go/secrets"
)

// Load once at startup from a secret manager / env var. Never commit it.
privateKeyJWK := []byte(os.Getenv("MANYROWS_WORKSPACE_PRIVATE_KEY"))

for _, sec := range delivery.Config.Secrets {
    if !sec.IsSet || len(sec.Envelope) == 0 {
        continue
    }
    plaintext, err := secrets.Decrypt(sec.Envelope, privateKeyJWK)
    if err != nil {
        log.Fatal(err)
    }
    // plaintext is JSON-encoded — for a string secret you'll get
    // `"hello"` (with quotes). Unmarshal into the typed value:
    var v string
    _ = json.Unmarshal(plaintext, &v)
}

Algorithm: ECDH P-256 → HKDF-SHA256 → AES-256-GCM. The browser encrypts with the workspace public key on save; only the holder of the private key can decrypt. The server stores the envelope as-is and never has access to the plaintext.

Check permission

allowed, err := client.HasPermission(userID, "posts:edit")

// Or get the full result:
result, err := client.CheckPermission(userID, "posts:edit")
// result.Allowed, result.Permission, result.AccountID

User lookup

// By ID
user, err := client.GetUser(userID)
// user.User.Email, user.Roles, user.Permissions, user.Fields

// By email
user, err := client.GetUserByEmail("user@example.com")

Members

result, err := client.ListMembers(0, 50)
// result.Members, result.Total, result.Page, result.PageSize

// Filter by email
result, err := client.ListMembersByEmail("alice", 0, 50)

User fields

fields, err := client.ListUserFields()
// fields[0].Key, fields[0].ValueType, fields[0].Label

Auth Middleware

HTTP middleware that verifies the user's JWT locally against the install's JWKS. Fetches ${baseURL}/.well-known/jwks.json once on first verify, caches the keys in-process, and refetches on a kid mismatch — no per-request round trip to ManyRows. Falls back to the mr_at HttpOnly cookie when no Authorization: Bearer header is present (cookie-mode AppKit deploys).

import "github.com/manyrows/manyrows-go/auth"

Middleware

r.Use(auth.Middleware(manyrowsBaseURL, workspaceSlug, appID))

The middleware accepts the JWT from either:

  1. Authorization: Bearer <jwt> (local mode / Tier 1)
  2. mr_at cookie (cookie-mode AppKit, when your auth host and app host share a registrable domain)

Set MANYROWS_BASE_URL to the URL the SDK should use for JWKS lookup (your install's host, e.g. https://manyrows.example.com or http://localhost:3000 in development). The workspaceSlug and appID parameters are accepted for forward-compat (a future audience check); they're not currently used by the verifier.

UserIDFromContext

Extracts the user ID (the JWT's sub claim) from the request context. Returns false if not present.

userID, ok := auth.UserIDFromContext(r.Context())

MustUserID

Same as UserIDFromContext but panics if the user ID is absent. Use in handlers behind Middleware.

userID := auth.MustUserID(r.Context())

Full example

package main

import (
    "fmt"
    "net/http"
    "os"

    manyrows "github.com/manyrows/manyrows-go"
    "github.com/manyrows/manyrows-go/auth"
)

func main() {
    client := manyrows.NewClient(
        "https://manyrows.example.com",
        "my-workspace",
        "my-app-id",
        os.Getenv("MANYROWS_API_KEY"),
    )

    mux := http.NewServeMux()

    // Protected routes
    protected := http.NewServeMux()
    protected.HandleFunc("/api/profile", func(w http.ResponseWriter, r *http.Request) {
        userID := auth.MustUserID(r.Context())

        // Look up user details from ManyRows
        user, err := client.GetUser(userID)
        if err != nil {
            http.Error(w, "Failed to get user", 500)
            return
        }

        fmt.Fprintf(w, "Email: %s, Roles: %v", user.User.Email, user.Roles)
    })

    protected.HandleFunc("/api/admin", func(w http.ResponseWriter, r *http.Request) {
        userID := auth.MustUserID(r.Context())

        allowed, _ := client.HasPermission(userID, "admin:access")
        if !allowed {
            http.Error(w, "Forbidden", 403)
            return
        }

        w.Write([]byte("Welcome, admin"))
    })

    mux.Handle("/api/", auth.Middleware(
        "https://manyrows.example.com",
        "my-workspace",
        "my-app-id",
    )(protected))

    http.ListenAndServe(":3000", mux)
}

Webhook verification

ManyRows signs every outbound webhook delivery. Use the webhook package to verify the signature + timestamp on your receiver:

import "github.com/manyrows/manyrows-go/webhook"

http.HandleFunc("/webhooks/manyrows", func(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "bad request", http.StatusBadRequest)
        return
    }
    if err := webhook.Verify(secret, r.Header, body); err != nil {
        http.Error(w, "invalid signature", http.StatusUnauthorized)
        return
    }
    // body is verified — parse JSON and process.
})

Verify checks both the HMAC-SHA256 signature (over <timestamp>.<body>) and that the X-Webhook-Timestamp is within ±5 minutes of now. Pass webhook.VerifyOptions{Tolerance: ...} to widen or tighten the window.

Read the request body before verifying — the HMAC covers the raw bytes exactly as transmitted; re-serializing parsed JSON will change whitespace and break the check.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages