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:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -261,6 +262,32 @@ func (f *fakeStore) Conversion(_ context.Context, q ReportQuery) (SalesReport, e
|
||||
return f.sales, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) CreateSite(_ context.Context, clientID, slug, name, tz string) (NewSite, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, s := range f.sites {
|
||||
if s.Slug == slug {
|
||||
return NewSite{}, &pgconn.PgError{Code: "23505"}
|
||||
}
|
||||
}
|
||||
id := "site-" + slug
|
||||
f.sites = append(f.sites, SiteHealth{SiteID: id, Slug: slug, Name: name, Timezone: tz})
|
||||
return NewSite{SiteID: id, Slug: slug, Name: name, Timezone: tz, Username: "acme." + slug, Password: "pw-" + slug}, nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) DeleteNewSite(_ context.Context, _ string, siteID string) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
kept := f.sites[:0]
|
||||
for _, s := range f.sites {
|
||||
if s.SiteID != siteID {
|
||||
kept = append(kept, s)
|
||||
}
|
||||
}
|
||||
f.sites = kept
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) SiteHealth(_ context.Context, _ string) ([]SiteHealth, error) {
|
||||
return f.sites, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user