The Go replacement for DysonNetwork.Padlock (auth, accounts, OIDC, admin,
E2EE/MLS) plus the Passport account/profile domain (accounts, profiles,
relationships, followers/following). Serves the same /api/** routes the C#
services served (the Blade gateway adds the /padlock and /passport
prefixes) with identical JSON shapes. Account board items stay in
Passport (routes + account_board_items table); Stargate only consumes
board updates indirectly via the profile read-model.
cmd/stargate— the service (HTTP on:8080, gRPC on:9090)cmd/stargate-migrate— one-shot data copy from the legacy C# databasesinternal/auth— JWT (RS256), token validation, session/challenge logicinternal/httpserver/*ctl— controller packages per domaininternal/grpcserver— the gRPC surface the C# fleet callsinternal/store— SQL queries (schema ininternal/migrate/0001_initial.sql)internal/migrate— embedded DDL, applied on bootinternal/permission— permission registry + evaluation + seedinternal/grpcclient— outbound clients (wallet, develop, drive, pass, blade, ring)internal/nats— thin wrapper over the shared event bus (src.solsynth.dev/sosys/go/pkg/eventbus): JetStream events (auth.session.revoked,websocket_push)
cp config.example.toml config.toml # set DSNs, keys, service targets
CONFIG_PATH=config.toml make runPostgres is required for the core account and session store. Redis and NATS
are optional: without Redis, cache-dependent features (QR login, passkeys,
one-time codes, and deletion throttling) return 503 while DB-backed
authentication continues; without NATS, event consumers and realtime event
publishing are disabled. Every outbound target in [services] is optional
and disables only its dependent enrichment, lookup, notification, or
realtime feature when unset or unavailable.
Stargate normally reads custom OAuth/OIDC clients from
DysonNetwork.Develop. Deployments that do not run Develop can define the
clients locally instead:
[[oidcProvider.clients]]
id = "00000000-0000-0000-0000-000000000001"
slug = "my-client"
name = "My Client"
clientSecret = "replace-with-a-secret"
status = 2
redirectUris = ["https://client.example.com/oauth/callback"]
allowedScopes = ["openid", "profile", "email"]
isPublicClient = falseLocal entries are checked before Develop. Keep each id stable because it is
stored in OAuth sessions. status = 2 (Production) enforces redirectUris;
public clients should use PKCE and omit clientSecret.
go run ./cmd/stargate-migrate \
--padlock-dsn "postgres://…/dyson_padlock" \
--passport-dsn "postgres://…/dyson_pass" \
--target-dsn "postgres://…/dyson_stargate"Order matters: boot Stargate once (creates the schema + records migrations),
then copy data, then restart (the seed then enrolls accounts in the
permission groups). UUIDs, bcrypt password hashes and E2EE blobs are copied
verbatim; schema drift (e.g. an epoch column the live DB lacks) is
zero-filled per type.
- Point Blade's routes for
/padlock/**(and the moved/passport/**paths) at Stargate. - Point the C# fleet's
services__padlock__grpc__0env at Stargate (the_grpc.padlock/_grpc.passportDNS targets stay; only the service address changes). - Keep Passport serving its remaining paths; its account lookups now hit Stargate.
- Run the migration tool, restart, verify with the compat sweep in the
plan (
local://stargate-user-domain-plan.md).
- JSON is globally snake_case;
ApiError.traceIdis camelCase; enums are ints; times are UTC RFC3339; nulls are omitted. - JWTs use the same RSA keys as the C# fleet (
Keys/), so in-flight tokens keep validating; refresh rotation bumps the session epoch and revokes prior tokens. - Session cache keys (
dyson:auth:session:*), account versions and theauth.session.revokedJetStream events interoperate with the C# fleet and downstream Go services.