The behaviour suite in internal/behaviour installs the application into an empty database through the web installer, signs in, walks every flow, and then replaces the process to confirm the state was persisted rather than held in memory. It found four defects, each fixed here or in the preceding commit: - Migrate was not safe to run from two instances at once. CREATE TABLE IF NOT EXISTS is not race free in PostgreSQL and collided on the pg_type unique index, and two replicas could apply the same file. The run now holds a session-level advisory lock, so a rolling deployment waits instead of failing. - Bootstrap refused ownership whenever a tenant held any member, so installing with the demonstration seed and then setting a local administrator password produced an account that could never reach the console. It now grants ownership only while the tenant has no owner, which still never takes a tenant over from one that does. Three of the fixes are covered by store-level regression tests in the same commit: the ownership rule, the duplicate tenant slug conflict, and concurrent migration runs. The suite is skipped by go test ./... because it needs a scratch database and the Google credentials for the installer. Run it with make behaviour-test, which also resets the database afterwards. make test no longer inherits the database variables, so unit runs need nothing. Completes the earlier correction to the docs: the role matrix lives in the database rather than in internal/role, and a local administrator claims the system administrator role when no Google account has.
6.1 KiB
First-run installation and demo data
Clean installation
Start Go Loose with PostgreSQL and a stable GO_LOOSE_SESSION_SECRET of at least 32 characters. No OIDC environment variables or bootstrap account are required:
docker compose up --build
Start the shared Traefik and certificate setup documented in
/home/dnoulet/go/infrasctruture/docs/APPLICATIONS.md, add
127.0.0.1 auth.dev nmbs.auth.dev ypto.auth.dev to /etc/hosts, then open
https://auth.dev/install. The installer asks for a Google OAuth client ID and
client secret. In Google Cloud Console, configure this authorized redirect URI
before submitting:
https://auth.dev/auth/callback
The supplied CMS Demo client ID is prefilled:
554628171917-forvpfs44pqajv77dfhl503prfqnhor3.apps.googleusercontent.com
The Google client secret is not included in the repository and must be copied from Google Cloud Console. Go Loose validates Google OIDC discovery, encrypts the secret with AES-GCM using a key derived from GO_LOOSE_SESSION_SECRET, and stores only the ciphertext in PostgreSQL. Keep the session secret stable: losing it makes the installed OIDC secret undecryptable.
Installation is a one-time operation. Submitting the installer immediately starts Google login. Google redirects to /auth/callback, where Go Loose validates the ID token, creates a signed HTTP-only session cookie, and redirects to the management console. Browser redirects cannot carry a caller-defined Authorization header, so no bearer token is exposed in the URL or to frontend JavaScript.
Later requests to /install redirect to login, and duplicate submissions are rejected.
First administrator and waiting room
The first user who completes Google SSO becomes the system administrator. A PostgreSQL advisory transaction lock makes this first-user claim atomic when multiple users sign in concurrently. The system administrator receives owner access to every tenant that exists at that moment.
Owning a tenant is what lets the administrator administer it; it does not let them sign in to the applications running inside it. Access to a client application is always an explicit grant, so an administrator who wants to use one attaches themselves from Users & access like anybody else. The grant is visible, attributable and revocable, and revoking it signs them out of that application without affecting their console access.
Later first-time SSO users are created without tenant membership. Their management page shows only the waiting room. A system administrator approves a waiting user from Users & access, selecting:
- the tenant;
- tenant role (
viewer,admin, orowner); - the applications the user may log in to.
Tenant owners and administrators can manage users, applications, API keys, client login, and OpenAPI contracts only inside their assigned tenants. A user assigned to multiple tenants can switch the active tenant from the console header. Database membership checks—not the selector—enforce isolation.
Optional Guess demonstration seed
The installer selects the demonstration seed by default. It creates the nmbs and ypto tenants, one guess application in each tenant, and these local database users:
| Tenant | Password | Role | |
|---|---|---|---|
| NMBS | interview@nmbs.auth.dev |
admin123 |
Tenant administrator |
| YPTO | interview@ypto.auth.dev |
admin123 |
Tenant administrator |
| NMBS | reviewer@nmbs.auth.dev |
Demo-Nmbs-2026! |
Tenant administrator |
| YPTO | reviewer@ypto.auth.dev |
Demo-Ypto-2026! |
Tenant administrator |
All four users receive access only to their tenant's Guess application. Passwords are stored as Argon2id hashes; plaintext credentials are never written to PostgreSQL.
These accounts and especially admin123 are intentionally non-production credentials. Disable the seed for production, or delete/rotate every demo account before exposing the service.
For local hosted-login testing, add:
127.0.0.1 auth.dev nmbs.auth.dev ypto.auth.dev
The tenant login origins are then:
https://nmbs.auth.dev
https://ypto.auth.dev
An application must first have exact redirect URIs and a generated client secret configured in the management console before /connect/authorize can start its login flow.
Environment-configured compatibility mode
Existing deployments may continue to supply all three GO_LOOSE_OIDC_ISSUER, GO_LOOSE_OIDC_CLIENT_ID, and GO_LOOSE_OIDC_CLIENT_SECRET variables. Go Loose treats that as an already configured deployment and bypasses the installer. Supplying GO_LOOSE_LOCAL_LOGIN=true together with a non-empty GO_LOOSE_LOCAL_ADMIN_PASSWORD does the same for a database-only development deployment.
For new installations, prefer the web installer. GO_LOOSE_LOCAL_LOGIN=true without a bootstrap password enables seeded and invited database users but does not bypass installation.
Two rules make that second mode usable rather than merely possible:
- The local administrator claims the system administrator role when the installation has none. Creating tenants and approving waiting-room users both require that role, and only a first Google sign-in otherwise grants it, so a database-only deployment would otherwise have nobody able to do either. The claim is a no-op once any system administrator exists, so an installation already owned by a Google account is untouched.
- Bootstrapping grants ownership of a tenant only while that tenant has no owner. It deliberately does not care whether the tenant has other members, because the demonstration seed creates tenants holding administrators but no owner; refusing ownership there would leave a local administrator bootstrapped afterwards with an account that can never reach the console. A tenant that already has an owner is never taken over.
Supplying GO_LOOSE_LOCAL_ADMIN_PASSWORD after a Google account has already claimed the role changes nothing.
Verifying an installation
make behaviour-test installs the application into an empty database, signs in, walks every flow, and then restarts the process to confirm that everything was persisted rather than held in memory. See the README for what it needs.