160 Commits

Author SHA1 Message Date
Test User c76ea93c29 fix(GRO-818): refund button for all paid invoices, inline cardLast4, manual refund for non-Stripe
- Backend refund endpoint: allow refunds on paid invoices without stripePaymentIntentId (manual refund path)
- Backend GET /invoices/🆔 inline fetch cardLast4 + paymentStatus from Stripe when stripePaymentIntentId present
- Frontend: show Refund button on all paid invoices for managers (not just Stripe-backed ones)
- Seed: add stripePaymentIntentId (pi_test_*) to ~20% of paid invoices for Stripe-path testing

cc @cpfarhood
2026-04-24 16:18:48 +00:00
Test User d65d121a5d fix(GRO-876): wire up refund button in invoice detail modal
Cherry-pick of 628ed34 to fix @typescript-eslint/no-unused-vars
error on PR #351 Lint & Typecheck.

The issueRefund function was defined but never called. This commit:
- Removes the inline async onClick handler that bypassed issueRefund
- Wires the Refund button to open setShowRefundDialog(true) instead
- Uses issueRefund function (with refundAmount/refundError/refunding state)
- Adds manager role check before showing refund button
- Shows "Refunded" badge when invoice.stripeRefundId is set

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-23 23:23:27 +00:00
groombook-engineer[bot] b8fd7ec18f fix(gro-609): cherry-pick refund/stats fixes to dev (#358)
* fix(gro-609): include stripePaymentIntentId in invoice list and wrap stats endpoint in try/catch

- Add stripePaymentIntentId to the GET /invoices list query so the refund button
  renders when seed data includes a payment intent ID
- Wrap /api/invoices/stats/summary in try/catch so errors return 200 with zero
  defaults instead of 5xx, preventing the Invoices page from crashing on
  mount for groomer-role sessions

Parent: GRO-882
Grandparent: GRO-816

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(gro-609): add payment stats to admin dashboard (AppointmentsPage)

- Fetch /api/invoices/stats/summary on mount and display Revenue/Outstanding/Refunds
  summary cards above the calendar view on /admin
- Mirrors the same stats section already on /admin/invoices
- Gracefully handles errors via try/catch on the stats endpoint

Parent: GRO-882
Grandparent: GRO-816

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 22:38:13 +00:00
Flea Flicker 2af1671891 fix(GRO-870): /api/branding returns raw S3 URL — add public logo proxy
Add GET /api/branding/logo as a public endpoint that proxies logo bytes
from S3, and change /api/branding to return logoUrl: "/api/branding/logo"
instead of calling getPresignedGetUrl(). Eliminates mixed-content warnings
when the branding context is consumed on unauthenticated pages (portal,
login).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-22 03:08:36 +00:00
Flea Flicker c811b58c62 fix(GRO-867): remove unused getPresignedGetUrl import from settings.ts
ESLint @typescript-eslint/no-unused-vars flagged the import.
The logo proxy no longer uses pre-signed GET URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:20:55 +00:00
Flea Flicker 1dfcdcc2cb fix(GRO-867): c.body does not accept Buffer in Hono 4.x
c.body() signature only accepts string | ArrayBuffer | ReadableStream | Uint8Array
in Hono 4.x, not Node.js Buffer. Return a plain Response directly instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:19:26 +00:00
Flea Flicker f74e034495 fix(GRO-867): replace transformToBuffer with async iteration over S3 stream
transformToBuffer() does not exist on StreamingBlobPayloadOutputTypes
in the AWS SDK v3 client. Use for-await-of over the async iterable body
to collect chunks and Buffer.concat instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 22:16:08 +00:00
Flea Flicker 4c46cec4e3 fix(GRO-867): proxy logo download through API server — eliminate mixed content
All logo S3 interactions are now server-proxied:
- GET /api/admin/settings/logo streams image bytes directly instead of
  returning a presigned S3 URL to the browser
- Upload already went through POST /api/admin/settings/logo/upload
- Frontend uses relative /api/admin/settings/logo path as img src,
  never a raw S3 URL
- Appends cache-buster query param (?t=Date.now()) after upload so
  the browser fetches the fresh image instead of serving a stale cache

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 22:07:21 +00:00
the-dogfather-cto[bot] 3c366ccc46 Merge pull request #346 from groombook/fix/gro-816-portal-pets-crash
fix(GRO-816): fix PetProfiles crash from appointments response shape change
2026-04-19 11:02:07 +00:00
Test User ff149f75dc fix(GRO-816): remove unused 'now' variable from portal.ts appointments handler
The PR refactored appointments response from { upcoming, past } to
{ appointments: [] } but the `now` variable used to compute those
filters was left behind. ESLint correctly flags it as unused.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 10:52:13 +00:00
Flea Flicker 03bd2d0235 fix(GRO-816): update PetProfiles.tsx to use new appointments response shape
- PetProfiles.tsx: update AppointmentsResponse interface to use flat
  appointments[] array instead of { upcoming, past }
- PetProfiles.tsx: update petHistory filter to use appointments.appointments
  with date filter for past-only appointments
- portal.ts: change /api/portal/appointments response to { appointments: [] }
  instead of { upcoming: [], past: [] }
- portal.ts: change /api/portal/pets response field names to match frontend
  Pet interface: weightKg→weight, dateOfBirth→birthDate, photoKey→photoUrl,
  groomingNotes→notes

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 08:13:53 +00:00
Test User 560d33edf8 fix(gro-609): fix two bugs found by CTO review
1. Refund stats now sum actual refund amounts from refunds table
   instead of incorrectly summing tip_cents from invoices table.

2. Stripe payment_intents.retrieve now expands payment_method
   so card.last4 is correctly available instead of null.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 01:55:32 +00:00
Test User 50e9e70935 feat(gro-609): add Stripe details to invoice modal and fix stats date filter
- Add GET /api/invoices/:id/stripe-details endpoint to fetch card last4 and
  payment status from Stripe
- Add getPaymentIntentDetails() to payment service
- Fix stats summary query to filter by startOfMonth
- Add cardLast4, paymentStatus, stripeRefundId transient fields to Invoice type
- Display Stripe details (card last4, payment status, refund status) in modal
- Add stripeRefundId and paymentFailureReason to Invoice schema (was missing in dev types)

Ref: GRO-609
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 01:02:49 +00:00
Test User d59cb1ab1d feat(gro-609): add refund handling and payment stats to admin
- Add stripePaymentIntentId to Invoice schema and types
- Add POST /api/invoices/:id/refund endpoint (Stripe placeholder)
- Add GET /api/invoices/stats/summary for payment analytics
- Add refund button + dialog (full/partial) to InvoiceDetailModal
- Add payment stats cards to Invoices page (revenue, outstanding, refunds, method breakdown)

Ref: GRO-609
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 00:59:18 +00:00
groombook-engineer[bot] 740e46baf2 Merge pull request #340 from groombook/fix/gro-805-invoices-rbac
Merge groomer RBAC fix into dev. cc @cpfarhood
2026-04-18 11:00:57 +00:00
Test User b1b89966d9 fix: allow groomer role to access invoices endpoint
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-18 10:36:23 +00:00
the-dogfather-cto[bot] ffe8aef035 Merge pull request #333 from groombook/feature/gro-628-frontend-error-handling
feat(GRO-785): validate tip split totals before marking invoice paid
2026-04-17 22:50:45 +00:00
Flea Flicker 2153505875 fix(GRO-785): restore eslint-disable for intentionally unused _tipSplits var
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:39:19 +00:00
Flea Flicker 4aaf2a3b3f fix(GRO-785): wrap tip split save + invoice update in single transaction
Both tip split persistence (delete + insert) and the invoice PATCH update
are now inside one db.transaction() block. If the invoice update fails
after splits are written, the entire operation rolls back.

Also removed unnecessary eslint-disable comment on _tipSplits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:29:30 +00:00
Flea Flicker 20ca93b36d fix(GRO-785): address invoice tip split regression
- Use body.tipCents ?? current.tipCents for validation condition
  so that simultaneous status=paid + tipCents=0 skip split validation
- Use body.tipCents (now aliased as tipCents) instead of current.tipCents
  inside the atomic transaction for shareCents calculation
- Add explicit check for empty tipSplits array with appropriate error
  message ("Tip splits are required when tip amount is greater than zero")
  before the sum-to-100% check
- Destructure tipSplits out of body before spreading into update object
  to prevent it from leaking into the invoices table SET clause

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:21:19 +00:00
Flea Flicker 9793283021 fix(GRO-785): restore atomic tip split save in PATCH and fix error message
- When body.tipSplits is provided in PATCH /invoices/:id, validate sum
  first then atomically replace existing splits (delete + insert)
- When no incoming splits, validate existing DB splits with corrected
  message: "Tip splits are required when tip amount is greater than zero"
  (previously misleading "must sum to 100%" when no splits existed)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 22:15:48 +00:00
the-dogfather-cto[bot] 47ccd1395c fix(GRO-778): exempt /dev-session from validatePortalSession middleware (#329)
fix(GRO-778): exempt /dev-session from validatePortalSession middleware
2026-04-17 21:54:32 +00:00
Flea Flicker 06846952a1 feat(GRO-785): validate tip split totals before marking invoice paid
- PATCH /invoices/:id returns 400 when tipCents > 0 but no tip splits
  exist or splits don't sum to 100%
- POST /invoices/:id/tip-splits now returns 400 (not 422) on validation
  failure via router-level ZodError handler

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 21:51:40 +00:00
Test User d72485c08a fix(GRO-778): physically move /dev-session route above validatePortalSession middleware
GRO-778 QA found that the previous commit only added a misleading comment;
the portalRouter.post("/dev-session") handler remained at line ~476, well
after portalRouter.use("/*", validatePortalSession, portalAudit) at line 16.
In Hono, use() applies only to routes registered AFTER it.

This commit moves the entire dev-session block to lines 1–72, before the
use("/*", ...) call, so the exemption actually takes effect.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 21:36:44 +00:00
lint-roller-qa[bot] 4001691ae7 fix(GRO-773): raise auth rate-limit threshold and exempt /get-session (#327)
Raise the Better Auth rate limit from max:10/window:60 to max:100/window:10
to match library defaults, and exempt /get-session from rate limiting entirely
via customRules (returns null = no rate limit check).

Both AUTH_DISABLED and production rateLimit blocks updated.

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 18:04:41 +00:00
Test User b980e4177c fix(GRO-778): exempt /dev-session from validatePortalSession middleware
Route ordering: /dev-session is registered after portalRouter.use("/*")
so it is NOT subject to the validatePortalSession/portalAudit middleware
chain — this is correct Hono behaviour since use() only applies to routes
registered after it.

The /dev-session POST endpoint creates the impersonation session and
cannot have a valid X-Impersonation-Session-Id header at call time.
Without this exemption, POST /api/portal/dev-session returns 401 before
the handler runs, breaking all portal pages when AUTH_DISABLED=true.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 17:56:31 +00:00
groombook-engineer[bot] 77971a1ac9 fix(GRO-769): proxy logo uploads through API server to fix mixed content (#325)
* fix(GRO-766): prevent horizontal overflow on portal mobile pages

- Add overflow-x-hidden to main content area in CustomerPortal
- Add w-full overflow-hidden to content wrapper div
- Add flex-wrap to BillingPayments tab button row

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(GRO-769): proxy logo uploads through API server to fix mixed content

The pre-signed URL flow used an internal HTTP endpoint for S3 uploads,
which browsers blocked as mixed content on HTTPS pages. Instead of
generating a pre-signed URL that the browser uploads to directly,
the new /logo/upload endpoint receives the file via multipart POST
and streams it to S3 from the API server using the internal endpoint.

This resolves the mixed content error that was blocking logo uploads
on dev.groombook.dev.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 17:13:44 +00:00
the-dogfather-cto[bot] 3c7820d785 fix(GRO-751): add server-side tip split validation to markPaid
- Extend updateInvoiceSchema to accept optional tipSplits array in PATCH body
- Validate tip splits sum to 100% (10000 bps) when marking paid with tipCents > 0
- Return 422 if tipSplits not provided and no existing splits in DB
- Save tip splits atomically in same DB transaction as invoice status update
- Update frontend markPaid() to send tipSplits in PATCH body instead of separate POST
- Remove non-atomic POST /tip-splits call from markPaid flow

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 12:33:43 +00:00
scrubs-mcbarkley-ceo[bot] 8e1e51be59 Merge pull request #318 from groombook/dev
Promote dev → main: GRO-639, GRO-642, GRO-666, GRO-724
2026-04-17 11:43:47 +00:00
the-dogfather-cto[bot] 6e1e51fba7 fix(GRO-639): replace N+1 per-appointment queries with single JOIN query (#306)
fix(reminders): replace N+1 per-appointment queries with single JOIN query
2026-04-17 10:45:17 +00:00
lint-roller-qa[bot] 772f4df62f fix(GRO-643): add appointment indexes to schema and S3 error handling (#315)
- Add idx_appointments_client_id, idx_appointments_staff_id,
  idx_appointments_start_time, idx_appointments_status to schema.
  Migration 0029 already handles the DB side; this brings schema.ts
  in sync so drizzle-kit push is clean going forward.
- Wrap deleteObject calls in try/catch (POST /photo/confirm and
  DELETE /:petId/photo endpoints) so S3 failures don't abort the
  DB update — orphaned objects are logged as warnings instead.

Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-17 06:42:01 +00:00
Flea Flicker 8182870d38 feat(GRO-642): add logo magic-bytes validation to prevent MIME confusion attacks
Defensive validation in /api/branding ensures base64-encoded logo content
matches its declared MIME type by checking image magic bytes (PNG, JPEG,
GIF, WebP). If the content doesn't match, the legacy base64 fields are
nulled out before returning to prevent MIME type confusion attacks.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 02:58:15 +00:00
Flea Flicker 0abb79010d fix(GRO-639): replace sql ANY() with inArray for Drizzle compatibility
Use Drizzle's inArray() instead of raw sql template with = ANY()
to avoid PostgreSQL array binding issues in the reminder scheduler
bulk sent-check query.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-17 02:08:03 +00:00
groombook-engineer[bot] 2577e33c50 feat(GRO-653): add portal session middleware and server-side audit logging (#300)
* feat(GRO-653): add portal session middleware and server-side audit logging

- Add validatePortalSession middleware that reads X-Impersonation-Session-Id header,
  queries impersonationSessions, and sets portalClientId + portalSessionId on the context
- Add portalAudit middleware that logs all portal requests to impersonationAuditLogs table
- Apply both middlewares to the portalRouter
- Replace all getClientIdFromSession() calls with c.get("portalClientId")
- Remove getClientIdFromSession() helper and inline session checks in waitlist routes
- Consistent session.expiry > new Date() check across all routes

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(GRO-653): remove unused sessionId variable and and import

Fix lint errors flagged by QA:
- Remove unused `sessionId` variable from PATCH waitlist handler
- Remove unused `and` import from portal.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Flea Flicker <fleaflicker@groombook.farh.net>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Flea Flicker <flea-flicker@groombook.ai>
2026-04-16 11:20:36 +00:00
groombook-cto[bot] b904418628 fix(GRO-640): replace N+1 queries in sendConfirmationEmail with single JOIN query
CTO approved: clean perf fix replacing 4 sequential DB queries with a single JOIN. QA approved.
2026-04-16 10:14:06 +00:00
Flea Flicker cdf4d6c4b1 fix(GRO-689): only validate authorizationUrl hostname, add OIDC_INTERNAL_BASE in dev
- Move hostname validation to run AFTER OIDC_INTERNAL_BASE replacement
  (was checking raw discovery URLs before replacement caused false positives)
- Only validate authorizationUrl hostname against issuer; token/userinfo
  are server-to-server and may legitimately use internal hostnames
- Infra: add OIDC_INTERNAL_BASE env var to dev overlay (was missing, matches UAT)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 04:55:17 +00:00
Flea Flicker 376180ab9d fix: make email required in createClientSchema to match NOT NULL column
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 10:52:45 +00:00
Flea Flicker 66a6659ccd feat(GRO-600): extend reminder scheduler to send SMS alongside email
- Add SMS opt-in fields to clients schema (smsOptIn, smsConsentDate, smsOptOutDate, smsConsentText)
- Add channel column to reminderLogs with per-channel idempotency
- Create SMS service with Telnyx SDK integration and E.164 validation
- Update reminders service to conditionally send SMS to opted-in clients
- Add TCPA opt-out text to SMS reminders
- Graceful degradation: catch SMS errors without blocking email
- Fix: use clients.phone instead of non-existent clients.phoneE164
- Update clients route to expose SMS fields in API
- Add telnyx dependency to API package
- Create database migration 0028_sms_reminders

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 09:23:24 +00:00
groombook-cto[bot] 1ef740c361 Merge branch 'main' into feature/gro-622-security-hardening 2026-04-15 06:53:50 +00:00
groombook-cto[bot] d433c902b4 fix(GRO-637): invoice status transitions, tip-split validation, refund idempotency, and tip-split response format
* Fix invoice status transitions, tip-split validation, refund idempotency, and tip-split response format

- Add ALLOWED_TRANSITIONS state machine for invoice status changes (GRO-637)
- Replace floating-point tip-split validation with integer basis-points math
- Add idempotency key support to refund endpoint with new refunds table
- Return full invoice shape from POST /:id/tip-splits matching GET response
- All existing tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(invoices): wrap refund flow in transaction for idempotency safety

- Wrap idempotency check + processRefund() + db.insert() in db.transaction()
- This prevents duplicate Stripe refunds if the DB insert fails after Stripe processes the refund
- Add migration 0027_refunds for the refunds table (was missing)
- Removes out-of-scope changes from PR #278 (csrf.ts, appointmentGroups, appointments, book, groomingLogs, services, stripe-webhooks)

Fixes GRO-637 per CTO review

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(api): wire up CSRF middleware for protected routes

Register csrfMiddleware in the protected API routes after authMiddleware
and resolveStaffMiddleware to protect against CSRF attacks on state-
changing operations (POST, PUT, PATCH, DELETE).

Addresses CTO review feedback on PR #278.

* fix(api): remove CSRF middleware that breaks POST/PUT/PATCH/DELETE

The CSRF middleware requires x-csrf-token header but the frontend never
sends it, which would break all mutating operations with 403 errors.

CSRF protection should be implemented in a separate coordinated PR with
frontend changes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Flea Flicker <flea-flicker@groombook.ai>
2026-04-15 06:04:38 +00:00
groombook-cto[bot] dc3b3ddcb7 fix(auth): add email-based staff auto-linking in resolveStaffMiddleware
Auto-link staff records by email when userId is NULL on first authenticated request.
Resolves GRO-667 UAT 403 blocker.

Co-Authored-By: Flea Flicker <noreply@anthropic.com>
2026-04-15 05:50:59 +00:00
groombook-cto[bot] e118607fd6 Merge branch 'main' into fix/gro-627-scheduling-correctness 2026-04-15 05:00:12 +00:00
groombook-cto[bot] e1e13d5091 fix(GRO-636): input validation fixes for 5 API routes
Adds Zod validation across 5 API routes:

1. invoices GET / — query param validation (uuid, enum, int bounds)
2. book POST / — future-time refinement on startTime
3. appointments — recurrence series capped at 1 year
4. services — durationMinutes capped at 480 (8 hours)
5. stripe-webhooks — UUID validation on invoice IDs before DB lookup

Closes GRO-636

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 04:26:20 +00:00
groombook-cto[bot] 67e2157975 feat(GRO-631): add graceful shutdown to API server (#292)
- Capture server instance from serve() call
- Add SIGTERM and SIGINT handlers for graceful shutdown
- Add 10-second forced exit timeout

Co-authored-by: Flea Flicker <flea-flicker@groombook.ai>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 01:54:00 +00:00
groombook-cto[bot] 3f2769a43a Merge branch 'main' into fix/gro-641-churn-pagination 2026-04-15 00:25:55 +00:00
Flea Flicker 0ed87f9ed8 fix(api): add server-side pagination to churn risk query (GRO-641)
- Add SQL-level LIMIT/OFFSET pagination to churn risk query
- Add separate COUNT(*) subquery for total without fetching all rows
- Accept page and limit query params with sensible defaults and bounds
- Return page, limit, and churnRiskTotal in response

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 00:12:01 +00:00
Flea Flicker 233e68769a fix(GRO-634): rename unused 'clauses' param to _clauses in confirmation test
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 23:23:51 +00:00
Flea Flicker f7b8b7e668 fix(GRO-634): atomic confirmation token in book.ts, correct RBAC error message
- Replace SELECT-then-UPDATE with atomic UPDATE ... WHERE token=? AND status='pending' RETURNING *
  to prevent confirmation token replay attacks (TOCTOU race condition)
- Fix requireRoleOrSuperUser() error message: swap the conditional branches so
  'Forbidden: super user privileges required' is returned when user lacks role,
  and 'Forbidden: role X is not permitted' when user is not superuser
- Add 'and' mock export to confirmation.test.ts and rbac.test.ts for new query patterns
- Update test expectations to match corrected error message semantics
2026-04-14 23:23:48 +00:00
Flea Flicker 1cce354413 fix(GRO-622): security hardening for auth, authorization, and token handling
- Remove placeholder secret fallback in AUTH_DISABLED mode (auth.ts)
- Make auth-provider setup atomic via DB transaction (setup.ts)
- Fix confirmation token replay with atomic UPDATE...WHERE (book.ts)
- Add strict CORS origin allowlist validation (index.ts)
- Validate OIDC discovery URL hostname matches issuer (auth.ts)
- Use timingSafeEqual for iCal token comparison (calendar.ts)
- Add in-memory rate limiting to setup endpoints (setup.ts)
- Keep RBAC error message correct (rbac.ts - already correct in main)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 23:23:48 +00:00
groombook-cto[bot] 648755eee5 fix: add corepack cache dir to Dockerfile (GRO-655)
Adds mkdir -p /home/node/.cache/node/corepack in builder stage to fix ENOENT crash in migration/seed jobs.

Root cause: c438f57 image regression — container user's home cache directory not pre-created for corepack.

Blocking: GRO-618 (UAT promotion), GRO-607 (payment UI), GRO-609

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-14 23:02:37 +00:00