Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions cmd/insforge-signup-broker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Command insforge-signup-broker runs the signed InsForge signup broker
// (internal/insforgesignup): it provisions a per-user InsForge project under one
// managed master account and returns the project's access key, with no email and
// no browser. Configuration is entirely from the environment — account- and
// host-specifics live in the deployment, not the binary.
//
// INSFORGE_SIGNUP_LISTEN HTTP listen addr (default 127.0.0.1:8092)
// INSFORGE_SIGNUP_TOKEN_URL OAuth token endpoint (https://api.insforge.dev/api/oauth/v1/token)
// INSFORGE_SIGNUP_CLIENT_ID OAuth client id the refresh token was issued to
// INSFORGE_SIGNUP_REFRESH_TOKEN master account refresh token
// INSFORGE_SIGNUP_PLATFORM_API platform API base (https://api.insforge.dev)
// INSFORGE_SIGNUP_ORG_ID org new projects are created under
// INSFORGE_SIGNUP_REGION project region (default us-east)
// INSFORGE_SIGNUP_BACKEND_DOMAIN backend host suffix (default insforge.app)
// INSFORGE_SIGNUP_PROJECT_PREFIX project name prefix (default pilot-)
// INSFORGE_SIGNUP_DB sqlite ledger path
// INSFORGE_SIGNUP_ENC_KEY 64-hex (32-byte) key sealing the project key at rest
// INSFORGE_SIGNUP_MAX_IDS_PER_IP per-IP distinct-caller cap (0 = unlimited)
// INSFORGE_SIGNUP_PATH signed request path (default /signup; set to /insforge/signup behind a proxy)
package main

import (
"log"
"os"
"strconv"

"github.com/pilot-protocol/app-template/internal/insforgesignup"
)

func main() {
maxIP, _ := strconv.Atoi(os.Getenv("INSFORGE_SIGNUP_MAX_IDS_PER_IP"))
b, err := insforgesignup.New(insforgesignup.Config{
Listen: os.Getenv("INSFORGE_SIGNUP_LISTEN"),
TokenURL: os.Getenv("INSFORGE_SIGNUP_TOKEN_URL"),
ClientID: os.Getenv("INSFORGE_SIGNUP_CLIENT_ID"),
RefreshToken: os.Getenv("INSFORGE_SIGNUP_REFRESH_TOKEN"),
PlatformAPI: os.Getenv("INSFORGE_SIGNUP_PLATFORM_API"),
OrgID: os.Getenv("INSFORGE_SIGNUP_ORG_ID"),
Region: os.Getenv("INSFORGE_SIGNUP_REGION"),
BackendDomain: os.Getenv("INSFORGE_SIGNUP_BACKEND_DOMAIN"),
ProjectPrefix: os.Getenv("INSFORGE_SIGNUP_PROJECT_PREFIX"),
DBPath: os.Getenv("INSFORGE_SIGNUP_DB"),
EncKeyHex: os.Getenv("INSFORGE_SIGNUP_ENC_KEY"),
MaxIdentitiesPerIP: maxIP,
SignupPath: os.Getenv("INSFORGE_SIGNUP_PATH"),
})
if err != nil {
log.Fatalf("insforge-signup-broker: %v", err)
}
b.SetLogger(log.New(os.Stderr, "insforgesignup ", log.LstdFlags|log.LUTC))
log.Printf("insforge-signup-broker listening on %s", os.Getenv("INSFORGE_SIGNUP_LISTEN"))
log.Fatal(b.ListenAndServe())
}
32 changes: 32 additions & 0 deletions docs/BROKER-SIGNUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@ compiled in. In broad strokes:

See each package's `Config` for the full environment contract.

## Variant: master-account provisioning (no mailbox)

Some backends have **no email-OTP account signup at all** — the only machine path
to a credential is an OAuth/token exchange under one owner account. There the
mailbox half does not apply: instead of driving `register → OTP → verify`, the
broker holds **one master account's OAuth refresh token** and, per caller,
provisions an **isolated sub-resource** (a project/workspace/tenant) and returns
its scoped key. `internal/insforgesignup` (with `cmd/insforge-signup-broker`) is
this shape for InsForge: refresh the master token headlessly → create a project
→ fetch its access key → return `{api_key, backend_url, project_id}`. It reuses
the same signed handler, per-caller encrypted ledger, idempotency, and per-IP cap
as the OTP broker — only the provider handshake differs, and there is **no mail
server to run**.

Because each user gets a *different* backend endpoint, this variant pairs with two
scaffold primitives:

- **`backend.url_secret`** — names a `secrets.json` key holding a per-user backend
base URL, re-resolved per request (a `BaseURLFunc`, the base-URL analogue of the
per-request `HeaderFunc`). The broker-signup handler caches the returned
`backend_url` there, so every call after signup reaches the user's own backend
with no restart. `base_url` stays the pre-signup default.
- **`body_raw`** — a method param (`in: body_raw`) whose value becomes the entire
request body verbatim (a bare top-level array/scalar), for APIs whose body is
not an object (e.g. a bulk insert takes `[{…}]`). The body-shape analogue of
`path_raw`.

The economics differ from per-user accounts: every provisioned sub-resource lives
under the **one** master account, so its plan limits and billing are shared — this
variant is for a **provider-funded**, metered managed service, not per-user free
tiers. Cap exposure with the per-IP mint cap and the master account's own plan.

## Security notes

- The mailbox holds a **live code for seconds** — treat it as a secret: tmpfs,
Expand Down
Loading
Loading