Opening a shop is an API call; the broker learns of it in the same request
The last step of onboarding that needed a shell: provision site printed a broker password and a person typed it into Mosquitto's passwd file on the host - mounted read-only in the container, so the first attempt failed silently and the password was re-rolled. No tenant could open a second branch without us. The server now drives Mosquitto's dynamic-security plugin over its own broker login: POST /api/sites (owner) writes the row and the sealed password, registers the login and a per-site role with literal topics (the 2.0 plugin does not substitute %u - measured), and removes the row again if the broker refuses, so a shop cannot exist in the database and not on the broker. provision site goes through the same path. The head-office Shops screen gets 'Open a new shop'. broker-init converts the existing passwd file into the plugin's store with every hash intact - PBKDF2-SHA512 both sides - so the cutover re-claims no shop PC. Rehearsed locally: old logins keep working, isolation holds, the health probe works, and a PC claiming a shop opened through the API connects as that shop. run-local.sh now brings the broker up the same way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -28,6 +28,15 @@ import (
|
||||
type Provisioner struct {
|
||||
Pool *pgxpool.Pool
|
||||
Secrets *secret.Box
|
||||
// Broker registers a site's login with Mosquitto when set. Without it the
|
||||
// command prints the password for a hand edit of the passwd file, which
|
||||
// is the pre-dynamic-security fallback and nothing more.
|
||||
Broker SiteBroker
|
||||
}
|
||||
|
||||
// SiteBroker mirrors internal/broker's interface without importing it.
|
||||
type SiteBroker interface {
|
||||
EnsureSite(ctx context.Context, username, password string) error
|
||||
}
|
||||
|
||||
func (p *Provisioner) CreateClient(ctx context.Context, slug, name string) (string, error) {
|
||||
@@ -48,6 +57,9 @@ type SiteResult struct {
|
||||
AgentID string
|
||||
Username string
|
||||
Password string
|
||||
// BrokerRegistered is true when the login was pushed to Mosquitto here,
|
||||
// so nothing remains for a person to type.
|
||||
BrokerRegistered bool
|
||||
}
|
||||
|
||||
// CreateSite makes a site, its agent row, and the broker password.
|
||||
@@ -120,7 +132,16 @@ func (p *Provisioner) CreateSite(ctx context.Context, clientSlug, siteSlug, name
|
||||
out.AgentID, sealed); err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, tx.Commit(ctx)
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return out, err
|
||||
}
|
||||
if p.Broker != nil {
|
||||
if err := p.Broker.EnsureSite(ctx, out.Username, out.Password); err != nil {
|
||||
return out, fmt.Errorf("site %s created, but the broker did not accept its login: %w", out.Username, err)
|
||||
}
|
||||
out.BrokerRegistered = true
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) CreateUser(ctx context.Context, clientSlug, email, role,
|
||||
|
||||
Reference in New Issue
Block a user