Authentication

Section: Warlot Go SDK

Authentication

This page describes authentication mechanics for the Warlot Go SDK: header requirements, API key issuance, lifecycle, and recommended handling patterns.


Header model

All database operations require the following HTTP headers. The SDK attaches them automatically when the corresponding fields are configured on Client.

HeaderSource in SDKPurpose
x-api-keyClient.APIKeyAuthenticates database operations for a project.
x-holder-idClient.HolderIDIdentifies the holder address.
x-project-nameClient.ProjectNameIdentifies the human-readable project name.
Content-TypeSDK (fixed)Always application/json.
User-AgentClient.UserAgentIdentifies the SDK (customizable).

Notes • Header names are case-insensitive over HTTP. • API key redaction is applied by the SDK in optional logs to prevent leakage.


Key issuance flow

API keys are issued per project via the /auth/issue endpoint. Typical lifecycle:

MERMAID
%%{init: {"theme": "base"}}%%
flowchart TD
  A[Resolve project] -->|found| B["project_id available"]
  A -->|not found| C[Init project]
  C --> B
  B --> D["Issue API key (/auth/issue)"]
  D --> E[Store key securely]
  E --> F["Attach key in client and perform DB ops"]


Minimal example

GO
import (
	"context"
	"github.com/steven3002/warlot-golang-sdk/warlot-go/warlot"
)

func example(ctx context.Context) error {
	cl := warlot.New(
		warlot.WithHolderID("0xHOLDER..."),
		warlot.WithProjectName("project_name"),
	)

	// Resolve or initialize
	r, _ := cl.ResolveProject(ctx, warlot.ResolveProjectRequest{
		HolderID: "0xHOLDER...", ProjectName: "project_name",
	})
	projectID := r.ProjectID
	if projectID == "" {
		initRes, err := cl.InitProject(ctx, warlot.InitProjectRequest{
			HolderID: "0xHOLDER...", ProjectName: "project_name",
			OwnerAddress: "0xOWNER...", IncludePass: true, Deletable: true,
		})
		if err != nil { return err }
		projectID = initRes.ProjectID
	}

	// Issue API key
	iss, err := cl.IssueAPIKey(ctx, warlot.IssueKeyRequest{
		ProjectID: projectID,
		ProjectHolder: "0xHOLDER...",
		ProjectName: "project_name",
		User: "0xOWNER...",
	})
	if err != nil { return err }

	// Attach key for subsequent calls
	cl.APIKey = iss.APIKey

	// Auth headers are now auto-applied for SQL/table/status/commit calls.
	_, err = cl.ExecSQL(ctx, projectID, warlot.SQLRequest{
		SQL: "SELECT 1", Params: nil,
	})
	return err
}

Error semantics

The API returns standard HTTP statuses; the SDK exposes a structured APIError for non-2xx responses.

StatusMeaning (auth context)Typical cause
401 UnauthorizedMissing or invalid x-api-keyKey not issued or expired/invalid
403 ForbiddenAccess denied for project scopeHolder/project mismatch
429 Too Many RequestsRate limit exceededBackoff required; SDK retries with jitter
5xxTransient server errorRetry advisable; SDK retries with jitter

Programmatic handling example:

GO
if err != nil {
	if e, ok := err.(*warlot.APIError); ok {
		switch e.StatusCode {
		case 401, 403:
			// Re-issue key, confirm holder/project headers, or halt.
		case 429:
			// Request was retried; consider backoff tuning or idempotency keys for writes.
		}
	}
}

Types (definition)

Key issuance request/response and relevant client fields/options.

GO
// Client fields relevant to auth.
type Client struct {
	BaseURL     string
	APIKey      string     // sent as x-api-key
	HolderID    string     // sent as x-holder-id
	ProjectName string     // sent as x-project-name
	// ...
}

// Construction-time options.
type Option func(*Client)
func WithAPIKey(k string) Option
func WithHolderID(h string) Option
func WithProjectName(n string) Option
func WithUserAgent(ua string) Option

// Issuance request/response.
type IssueKeyRequest struct {
	ProjectID     string `json:"projectId"`
	ProjectHolder string `json:"projectHolder"`
	ProjectName   string `json:"projectName"`
	User          string `json:"user"`
}
type IssueKeyResponse struct {
	APIKey string `json:"apiKey"`
	URL    string `json:"url"`
}

Security guidance (summary)

  • Secret storage: API keys should be stored in a secure secret manager or environment variable with least-privilege access at runtime.
  • Redaction: the SDK redacts x-api-key values in log hooks; avoid printing raw keys.
  • Rotation: re-issue keys when compromised or per rotation policy; update Client.APIKey immediately.
  • Idempotency: apply WithIdempotencyKey for write operations to prevent duplicate effects during retries.

Unit test definition (headers applied)

A compact test that asserts correct auth headers are forwarded. Uses a stub server.

GO
// authentication_headers_test.go (package warlot)
package warlot

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
)

func Test_AuthHeaders_Applied(t *testing.T) {
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("x-api-key") == "" { t.Errorf("missing x-api-key") }
		if r.Header.Get("x-holder-id") == "" { t.Errorf("missing x-holder-id") }
		if r.Header.Get("x-project-name") == "" { t.Errorf("missing x-project-name") }
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(200)
		_, _ = w.Write([]byte(`{"ok":true,"row_count":0}`))
	}))
	defer s.Close()

	cl := New(
		WithBaseURL(s.URL),
		WithAPIKey("k"),
		WithHolderID("h"),
		WithProjectName("p"),
	)
	_, err := cl.ExecSQL(context.Background(), "proj", SQLRequest{SQL: "CREATE TABLE t(x)", Params: nil})
	if err != nil {
		t.Fatalf("ExecSQL failed: %v", err)
	}
}

E2E test definition (issuance)

Skips unless explicitly enabled via environment.

GO
// authentication_e2e_test.go (package e2e)
package e2e

import (
	"context"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/steven3002/warlot-golang-sdk/warlot-go/warlot"
)

func Test_IssueKey_And_SQL_Live(t *testing.T) {
	if os.Getenv("WARLOT_E2E") != "1" {
		t.Skip("enable with WARLOT_E2E=1")
	}
	holder := mustEnv(t, "WARLOT_HOLDER")
	owner := mustEnv(t, "WARLOT_OWNER")
	pname := mustEnv(t, "WARLOT_PNAME")

	cl := warlot.New(
		warlot.WithHolderID(holder),
		warlot.WithProjectName(pname),
		warlot.WithHTTPClient(&http.Client{Timeout: 120 * time.Second}),
		warlot.WithRetries(6),
	)

	ctx := context.Background()
	// resolve or init
	r, _ := cl.ResolveProject(ctx, warlot.ResolveProjectRequest{HolderID: holder, ProjectName: pname})
	projectID := r.ProjectID
	if projectID == "" {
		ir, err := cl.InitProject(ctx, warlot.InitProjectRequest{
			HolderID: holder, ProjectName: pname, OwnerAddress: owner,
			IncludePass: true, Deletable: true,
		})
		if err != nil { t.Fatal(err) }
		projectID = ir.ProjectID
	}

	iss, err := cl.IssueAPIKey(ctx, warlot.IssueKeyRequest{
		ProjectID: projectID, ProjectHolder: holder, ProjectName: pname, User: owner,
	})
	if err != nil { t.Fatal(err) }

	cl.APIKey = iss.APIKey
	if _, err := cl.ExecSQL(ctx, projectID, warlot.SQLRequest{SQL: "SELECT 1", Params: nil}); err != nil {
		t.Fatal(err)
	}
}

func mustEnv(t *testing.T, k string) string {
	t.Helper()
	v := os.Getenv(k)
	if v == "" { t.Fatalf("missing %s", k) }
	return v
}

CLI note

The warlotctl issue-key command mirrors the issuance flow and can be used to validate credentials outside application code. See 11-cli for command flags and environment variables.