Skip to content

fix(sep10,ci): self-consistent SEP-10 domains & token expiry; CI migration gate (#671 #673) - #852

Merged
Cjay-Cyber-2 merged 1 commit into
mergepay:mainfrom
Teescom:issue-671-673-migration-ci-sep10
Oct 19, 2026
Merged

Cjay-Cyber-2 merged 1 commit into
mergepay:mainfrom
Teescom:issue-671-673-migration-ci-sep10

Conversation

@Teescom

@Teescom Teescom commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Summary

One PR closing two assigned issues:

The challenge verification logic (src/services/sep10.ts) was already strict and well covered; these fixes are the two seams around it that were provably broken, plus the migration gate.


#673 — SEP‑10 domain & token‑expiry strictness

1. home_domain rejected the server's own fallback domain

/auth/verify takes an optional home_domain and, when it differs from the configured domain, returns an error naming config.SEP10_HOME_DOMAIN (src/routes/auth.ts). SEP10_HOME_DOMAIN defaults to new URL(API_PUBLIC_URL).host, which keeps the port and in local/container dev is a single label — hostOf even hard‑codes localhost:4000 as its fallback.

The request schema used one regex for all three domain fields that required a dotted FQDN and forbade a port:

/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/

Measured on the base commit 26c6ff4: localhost:4000 → false, api.mergepay.app → true. So a client that obeyed the server's own error message ("send home_domain=localhost:4000") was then rejected for the shape of that value.

Fix — src/lib/sep10-domain.ts defines two grammars:

  • sep10DomainSchema (home domains + boot config SEP10_HOME_DOMAIN/WEB_AUTH_DOMAIN): a hostname with an optional port 1‑65535; still refuses a URL (http://…), underscores, empty/hyphen‑edge labels, a bad port, and >253 chars. Accepting a port is not a weakening — the value only ever has to equal the server's own configured domain, and signature verification binds the challenge to it.
  • clientDomainSchema (client_domain / clientDomain): keeps main's exact strict FQDN rule. A client domain names the origin its stellar.toml is fetched from, so a single label or host:port has nothing to resolve. This field's strictness was deliberately not relaxed — the test tests/schemas/auth.test.ts now pins both halves of that split.

2. A token lifetime shorter than its own expiry margin

verifyToken rejects a token whose remaining lifetime is below TOKEN_EXPIRY_MARGIN_SECONDS, while signToken sets exp = now + ACCESS_TOKEN_TTL_SECONDS (src/services/jwt.ts). On base the two numbers were validated independently (config.ts:79 and :85), and the only cross‑field .refine was the pre‑existing HORIZON‑URL/network check. So ACCESS_TOKEN_TTL_SECONDS=25 with the default 30s margin boots cleanly and then rejects every access token the instant it is issued — a total auth outage with no other failure to point at.

Fix — a cross‑field refine: ACCESS_TOKEN_TTL_SECONDS > TOKEN_EXPIRY_MARGIN_SECONDS, reported on the ACCESS_TOKEN_TTL_SECONDS path.


#671 — CI migration status check

ci.yml had two jobs: build-test (no database) and integration, which provisions its schema with prisma db push — and db push builds the database straight from schema.prisma and never reads prisma/migrations/. Nothing in CI ever ran prisma migrate deploy, so the migration history was free to be undeployable and to drift from the schema.

Reproduced on an untouched checkout of the base

prisma migrate deploy against a fresh Postgres aborts at the 3rd of 38 migrations:

Applying migration `20260724000000_add_webhooks`
Error: P3018  42P01: relation "Group" does not exist

Enumerating the full set (drop/recreate/no‑op loop, every file restored afterwards), five migrations cannot be deployed and two schema tables are never created by any migration:

migration failure
20260724000000_add_webhooks 42P01 "Group" — builds PascalCase tables that match no model
20260728000000_add_audit_log_group_id 42701 audit_logs.group_id — re‑adds a column an earlier migration created
20260728010000_add_idempotency_key_status 42701 idempotency_keys.user_id
20260729000000_add_idempotency_key_uniqueness 42P01 "IdempotencyKey" — a table the schema never declares
20260729000000_idempotency_key_lifecycle 42701 idempotency_keys.user_id
(missing entirely) treasury_approvals, status_history, and the withdrawal/anchor retry columns that schema.prisma declares

The repair

  1. The five broken migrations are superseded — each object they wanted is already created (correctly) by a surviving migration (20260828000000_webhook_delivery_queue, 20260728000000_scope_idempotency_keys, 20260801000000_idempotency_key_lifecycle_state). They are rewritten as documented no‑ops, keeping the directories so nothing is renumbered. This matches the repo's own established style (reconcile_worker_lease_columns, settlement_job_state already converge defensively with IF NOT EXISTS).
  2. One reconciliation migration — 20260929000000_reconcile_schema_drift — is the literal output of prisma migrate diff --from-migrations … --to-schema-datamodel … --shadow-database-url … --script, so by construction it is exactly the SQL that makes a deploy‑built database equal schema.prisma: drops the drift the schema does not declare, adds the objects it does.

Why rewriting history here is safe: migrate deploy has always failed at add_webhooks, so no database can hold a successful _prisma_migrations checksum for any of these, and every real environment is built with db push, which writes no checksum at all. The job_* columns dropped from settlements are declared only on AnchorSession in the schema and referenced nowhere in src/; the one src/ mention of the renamed index is a doc‑comment example.

The gate

A new migration job (postgres:16 service, same healthcheck pattern as integration) runs, on a fresh DB: prisma validate → migrate deploy → prisma migrate status → prisma migrate diff … --exit-code. Added prisma:validate and prisma:check-drift scripts for local use; the required DATABASE_URL is documented in the job comment. It is not loosening anything — it adds a gate where there was none.


Verification (local replicas of all CI jobs, on the rebased head)

npm run prisma:generate   rc=0
npm run build             rc=0   (tsc -p tsconfig.json)
npm test                  rc=0   → 205 files, 4153 passed | 22 skipped
integration (DATABASE_URL_TEST, fresh PG) rc=0 → 14 files, 104 passed | 9 skipped
migration job replica (fresh PG):
  prisma:validate         rc=0   "schema … is valid"
  prisma migrate deploy   rc=0   "All migrations have been successfully applied."
  prisma:status           rc=0   "Database schema is up to date!"
  prisma:check-drift      rc=0   "No difference detected."

Mutation proofs that the gates are not vacuous:

  • Removing 20260929000000_reconcile_schema_drift makes prisma:check-drift return exit 2 and flags treasury_approvals / status_history — so the reconcile migration is load‑bearing.
  • Adding a stray drift_probe table makes the same check return exit 2, and dropping it returns it to 0.
  • Baseline on untouched 26c6ff4: migrate deploy exits 1 (P3018/42P01); the old domain grammar rejects localhost:4000; config has no TTL>margin refine.
  • New specs in tests/sep10-domain-config.test.ts (45 tests) cover the grammar accept/reject boundaries (253/254, 63/64 label, ports), the hostOf‑echo invariant, boot‑time config validation, the TTL>margin refusals, and the home/client two‑grammar split.

Required environment

  • CI migration job sets DATABASE_URL for its own service; no new secrets.
  • prisma:check-drift reads $DATABASE_URL (must point at an already‑deployed database).

…add CI migration gate

Issue mergepay#673: two strictness defects around SEP-10 auth.

- home_domain used one grammar that required a dotted FQDN, so it rejected
  the single-label / with-port values that config.SEP10_HOME_DOMAIN falls
  back to (hostOf(API_PUBLIC_URL) -> "localhost:4000"). A client that obeyed
  the server's own mismatch error was then rejected for the shape of the
  domain it was told to send. Extracted a shared grammar in
  src/lib/sep10-domain.ts: home domains and boot config accept a hostname
  with an optional port; client_domain keeps the strict FQDN rule (it names
  the origin of the client's stellar.toml and has no such fallback).
- ACCESS_TOKEN_TTL_SECONDS and TOKEN_EXPIRY_MARGIN_SECONDS were validated
  independently, so a margin >= TTL boots cleanly and then rejects every
  access token the moment it is minted. Added a cross-field refine.

Issue mergepay#671: add a CI migration job. build-test has no DB and integration
provisions schema with `db push`, so nothing ever ran `migrate deploy` —
the migration history had 5 broken steps and 2 schema tables never created
by any migration. Neutralized the superseded steps to documented no-ops
(safe: deploy has always failed at the first one, and db push writes no
checksum), added a `migrate diff`-generated reconciliation migration, and a
`migration` job asserting validate + deploy + status + zero drift.
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@Teescom Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — approving.

@Cjay-Cyber-2 Cjay-Cyber-2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — approving.

@Cjay-Cyber-2
Cjay-Cyber-2 merged commit 1340537 into mergepay:main Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance SEP-10 authentication challenge validation and token expiration handling Implement automated database migration status check in CI workflow

2 participants