Half of this binary's js.Publish calls were bound to no stream at all.
The streams were declared only by an external Python script on another
machine (Birock/doormile-bookings/setup_jetstream.py) and had drifted
from the code: booking.cancelled, booking.outcome and
booking.assignment_failed had no stream, and CHAT declared the literal
"chat.room.closed" while chat.go publishes "chat.room.closed.<id>",
which it does not match. Every publish site is best-effort
(`if db.Js != nil` + warn-log), so those events were failing and being
dropped silently — every cancellation, delivery outcome and assignment
failure since the streams were created.
db.EnsureStreams now declares the streams at startup from a map that
sits next to the code that publishes, so the contract cannot drift
again. It only ever adds: existing streams keep their storage type,
retention, limits and every subject they already have. Nothing is
deleted. Losing the create race against a sibling replica is expected
and reconciles rather than erroring.
Alongside that, /internal/miler/* ingests rider telemetry still arriving
over the jupiter NATS chain. The forwarding worker holds no rider JWT —
the rider app is still jupiter-shaped — so LegacyMilerIdentity resolves
an identity from a header into c.Locals("userid") behind the existing
X-Internal-Key guard. That lets the routes reuse the miler handlers
unchanged instead of growing a parallel set that would drift.
Identity comes from a header, never the body: the telemetry handlers
overwrite a body-supplied userid precisely so one rider cannot write
another's GPS trail, and reading it from the body here would reopen that
from behind the internal key. MilerProfile.Legacyuserid (nullable,
indexed) maps a jupiter userid to a Doormile one.
Only fire-and-forget telemetry is exposed. Transactional actions stay
synchronous — a rider needs a real answer from pickup-complete, which a
queue in front of it cannot give.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
2.7 KiB
Go
77 lines
2.7 KiB
Go
package middlewares
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"doormile/db"
|
|
"doormile/models"
|
|
"doormile/utils"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
// LegacyMilerIdentity resolves a rider identity for machine-to-machine ingest
|
|
// and puts it in c.Locals("userid"), which is exactly where the normal miler
|
|
// handlers read it from. That is the whole point: the ingest routes reuse the
|
|
// existing handlers unchanged rather than growing a parallel set with their own
|
|
// (inevitably drifting) validation.
|
|
//
|
|
// It must be mounted *behind* InternalKeyAuth. On its own it is not
|
|
// authentication — it names a rider, it does not prove anything about the
|
|
// caller. The X-Internal-Key check is what makes that safe, and it is the reason
|
|
// this cannot be reached from the public internet.
|
|
//
|
|
// Two headers, checked in order:
|
|
//
|
|
// X-Miler-Userid a Doormile userid, used directly
|
|
// X-Legacy-Userid a jupiter/Nearle userid, resolved via MilerProfile.Legacyuserid
|
|
//
|
|
// The identity deliberately does NOT come from the request body. The miler
|
|
// telemetry handlers overwrite any body-supplied userid with the token's, which
|
|
// is what stops one rider writing another's GPS trail; taking it from the body
|
|
// here would reopen that hole from behind the internal key. A header keeps the
|
|
// rule intact and works for POST /consignments/logs, whose body is a bare JSON
|
|
// array with nowhere to put an id anyway.
|
|
func LegacyMilerIdentity(c *fiber.Ctx) error {
|
|
if raw := c.Get("X-Miler-Userid"); raw != "" {
|
|
userID, err := strconv.Atoi(raw)
|
|
if err != nil || userID <= 0 {
|
|
return utils.BadRequest(c, "invalid X-Miler-Userid")
|
|
}
|
|
|
|
var count int64
|
|
if err := db.DB.Model(&models.MilerProfile{}).
|
|
Where("userid = ?", userID).Count(&count).Error; err != nil {
|
|
return utils.Internal(c, "failed to resolve miler")
|
|
}
|
|
if count == 0 {
|
|
return utils.NotFound(c, "no miler with that userid")
|
|
}
|
|
|
|
c.Locals("userid", userID)
|
|
return c.Next()
|
|
}
|
|
|
|
raw := c.Get("X-Legacy-Userid")
|
|
if raw == "" {
|
|
return utils.BadRequest(c, "X-Miler-Userid or X-Legacy-Userid header is required")
|
|
}
|
|
|
|
legacyID, err := strconv.Atoi(raw)
|
|
if err != nil || legacyID <= 0 {
|
|
return utils.BadRequest(c, "invalid X-Legacy-Userid")
|
|
}
|
|
|
|
var profile models.MilerProfile
|
|
if err := db.DB.Where("legacyuserid = ?", legacyID).First(&profile).Error; err != nil {
|
|
// Unmapped is the expected case for every rider who was never migrated
|
|
// from jupiter, so this is a routine 404 rather than an error condition.
|
|
// The forwarder treats it as "drop, do not retry" — retrying cannot
|
|
// invent a mapping, and NAK-looping on it would wedge the consumer.
|
|
return utils.NotFound(c, "no Doormile miler mapped to that legacy userid")
|
|
}
|
|
|
|
c.Locals("userid", profile.Userid)
|
|
return c.Next()
|
|
}
|