zlh-grind/SCRATCH/2026-02-07_customer-id-schema.md

3.1 KiB
Raw Blame History

2026-02-07 — Customer ID schema + "hashed id" confusion

Current reality

User table

Prisma schema:

model User {
  id           String   @id @default(cuid())
  email        String   @unique
  username     String   @unique
  passwordHash String

  firstName    String?
  lastName     String?
  displayName  String?

  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
}
  • When a user registers via the API (POST /api/auth/register), Prisma assigns a CUID (looks "hashed" in Prisma Studio, e.g. cmk07b7n4000q...).
  • That value is not derived from the JWT token.
  • During testing you manually created a user with an ID like u000 (and other prefixed IDs), which is why you're seeing mixed formats.

ContainerInstance.customerId (what broke)

You observed ContainerInstance.customerId being polluted:

  • sometimes u000 (manual)
  • sometimes u-dev-001 (hardcoded during testing/dev)
  • sometimes a Prisma CUID (new registrations)

Root cause (confirmed):

  • Frontend / provisioning calls were hardcoded to use u-dev-001 as customerId.
  • New registrations correctly used Prisma's default User.id (CUID), but container creation wasn't consistently using it.

Short-term decision (now)

Use User.id as the authoritative customer identifier everywhere.

That means:

  • customerId in ContainerInstance should always be set to req.user.id.
  • Stop inventing or hardcoding IDs (u-dev-001, etc.) in the frontend.
  • Keep username / displayName for human-friendly display and troubleshooting.

This keeps the system consistent immediately and avoids adding schema complexity right now.

Why this is OK

Pros:

  • Guaranteed unique
  • Harder to guess (good for security)
  • Already produced by Prisma automatically
  • No more "what format is customerId?" problems

Cons:

  • Not pretty/human-friendly in logs (CUIDs)

Mitigation:

  • When displaying in UI or logs, show a composite:

    • displayName (username) and optionally the first 68 chars of the id
    • Example: Jester (jester) • cmk07b7n4

Long-term (optional) improvement

If you later want a human-readable / sequential ID:

Add a second field, e.g.:

  • publicCustomerId (u001, u002, etc.) generated by a counter table or dedicated allocator

Then:

  • Keep id as the internal primary key (CUID)

  • Use publicCustomerId only for:

    • display
    • support tickets
    • invoices / Stripe customer metadata
    • external references

But: don't block on this now.

Cleanup / migration note

If you want to clean existing polluted rows later:

  • Update all ContainerInstance.customerId = 'u-dev-001' to the correct User.id (based on ownership mapping / audit trail / known associations).
  • If no reliable mapping exists, leave historic test rows as-is and only enforce the rule going forward.

Implementation rule (what to enforce)

  • Provisioning endpoint should ignore any client-supplied customerId.
  • Always derive from auth context:
const customerId = req.user.id;
  • If you need admin tooling to create for others, that should be a separate privileged endpoint that explicitly sets ownership (and logs it).