Commit Graph
7 Commits
Author SHA1 Message Date
mrhid6andClaude Opus 5 aef5811c16 fix(admin): SMTP over implicit TLS, and rollbacks that survive
Server Deploy / deploy (push) Successful in 2m13s
Two bugs, one symptom: signup created an account and a customer_user but
no verification email ever arrived.

net/smtp.SendMail only speaks STARTTLS. Against a port-465 server, which
expects a TLS handshake immediately, it never delivers. The transport now
wraps the connection before speaking SMTP on 465, exactly as
sitesvc/internal/mail already did — the two are duplicated, so change both
or consolidate into shared/. Also adds Date and Message-ID, whose absence
gets a message scored as spam, and a 15s deadline on the conversation.

The rollbacks ran on the HTTP request's context. A stalled mail server
holds the request until the browser gives up, which cancels that context
and turns both rollbacks into silent no-ops — stranding the exact rows
they exist to remove. They now run detached with their own timeout, and
log when they fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 14:26:34 +01:00
mrhid6andClaude Opus 5 cf318470b8 feat(admin): drop the unscoped control-plane login branch
HQ sign-in names no instance, so a lookup of control-plane users by email
alone cannot be scoped — and users.email is no longer globally unique, so
it would return an arbitrary match. Every customer authenticates against
customer_users instead.

Legacy cloud customers get an HQ login from staff via the new
POST /api/staff/accounts/:id/users, alongside the manual instance attach
the spec README already describes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 12:55:44 +01:00
mrhid6andClaude Opus 5 4bb7400b8e feat(admin): session probe, self-hosted signup and the relink cap
Adds GET /auth/me so the admin site's route guards can know who is signed
in, POST /auth/signup for self-hosted customers, and max_relinks on the
account payload so the UI never hardcodes a rule the backend enforces.

Signup follows sitesvc's proven shape: honeypot answered as success, a
generic 201 when the address already exists, and nothing usable until the
emailed link is opened.

Also fixes a lockout found while verifying it. When the verification email
failed, the account was rolled back but the customer_users row survived --
an orphan that can never be signed in to and that holds the unique index on
email, so every later signup with that address got a cheerful 201 and the
customer was locked out of their own address with no visible error.
CreateCustomerUser now undoes its own insert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 20:48:09 +01:00
mrhid6andClaude Opus 5 58c37bf81b feat(admin): support authenticated Redis, and point at the external server
Server Deploy / deploy (push) Successful in 2m14s
Adds REDIS_USERNAME and REDIS_PASSWORD. Both are optional, so an
unauthenticated instance still works unchanged. Redis 6+ ACL auth takes
both; a legacy requirepass instance takes the password with an empty
username, which is what go-redis needs to send single-argument AUTH.

Admin now defaults to the external Redis at 10.10.10.2:6379 rather than the
compose-local one, and no longer declares depends_on: redis -- it is not
starting that container any more. The base stack keeps its own Redis for
`server`, which still has no auth support.

Also fixes SMTP_PASSWORD in the admin block: it read `$SMTP_PASSWORD:-}`
rather than `${SMTP_PASSWORD:-}`, which appended a literal `:-}` to the
password and would have failed SMTP auth at the first verification email.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 19:50:19 +01:00
mrhid6andClaude Opus 5 07a3756b18 feat(admin): self-hosted customer accounts with email verification
Mirrors the pattern sitesvc already proves: 32 random bytes, only the
SHA-256 hash stored, a 24-hour expiry, and the token cleared on use -- so a
leaked database yields no working links.

Unverified login returns a distinct "verify your email address first" rather
than the generic error. The address is already known to be theirs, so there
is nothing to disclose and that is the only useful thing to say.

Licence blobs are emailed inline. A blob is signed public data, not a
secret: it is useless on any instance other than the one it names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 19:07:47 +01:00
mrhid6andClaude Opus 5 769839a70d feat(admin): cloud owner login against the control plane
Cloud customers sign in with the control-plane credentials they already
hold, so there is no second password to manage. Two accepted consequences,
documented at the handler: their control-plane password now also unlocks
billing, and only role owner may sign in -- admin and member are refused
because billing is an owner concern.

A self-hosted customer_users row wins over a control-plane user with the
same address, so the precedence is chosen rather than emergent.

Adds the reads of control users this needs; the write surface is still one
UpdateOne on instances.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 19:06:23 +01:00
mrhid6andClaude Opus 5 480a578deb feat(admin): sessions, staff auth and adminctl
One Redis session store and one cookie for all three identities. Staff
login returns the same error for every failure mode and spends a bcrypt
comparison against a dummy hash when no user exists, so neither the message
nor the timing confirms which addresses have accounts.

Staff users are created only by adminctl. There is no signup endpoint: a
licensing authority that can be joined over the internet is not one.

Pins gin and go-redis to the versions server/ already uses rather than the
latest tidy would pick.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 19:05:28 +01:00