Skip to content

v0.1.2 — 2FA lockout and fresh-install fixes

Choose a tag to compare

@AshishGTH AshishGTH released this 10 Aug 13:21
· 172 commits to master since this release

Upgrade from v0.1.1 as soon as practical. Two bugs in that release
are severe enough on their own to justify this one:

  • Any account with 2FA enabled was permanently locked out of login,
    with no way back in — not even via a recovery code. The login
    response's 2FA-pending branch never set the CSRF cookie totp/verify
    requires, so every totp/verify call (including recovery-code
    attempts) 403'd with "CSRF token mismatch," unconditionally, for
    every account. If you're running v0.1.1 and have 2FA enabled on
    any staff account, upgrade before enrolling further users
    — those
    accounts have been unable to log in since enabling it, and existing
    locked-out accounts will need a staff admin to disable 2FA for them
    (POST /auth/totp/disable, or directly via the database) after
    upgrading, since the fix doesn't retroactively unlock an in-progress
    login attempt.
  • A genuinely fresh install failed immediately on the exact command
    the docs tell you to run. Every deploy/native/*.sh script an admin
    runs directly was git-tracked without the executable bit; sudo ./install-native.sh right after git clone died at "Permission
    denied" trying to exec setup-database.sh. Anyone who successfully
    installed v0.1.1 did so from a checkout that had picked up a local,
    uncommitted chmod +x somewhere along the way (e.g. copying files
    instead of cloning) — a literal fresh clone was never viable.

Fixed

  • The 2FA lockout above also had a second, independent bug: the
    code-verification schema only accepted 6 digits, rejecting every
    recovery code's XXXXX-XXXXX format before it reached the
    already-correct recovery-code check. Both fixed together; verified
    live end-to-end (enroll, login-requires-code, wrong code rejected,
    recovery code works once and is rejected on reuse).
  • Company config had no way to set companyGstin/gstStateCode after
    the initial seed, despite the frozen booking service already reading
    gstStateCode to decide CGST+SGST vs IGST. Added, with GSTIN format
    validation (checksum digit deferred — see docs/todo.md) and a
    Company Config UI section.
  • Health endpoint's version field was hardcoded 0.1.0 in every real
    deployment (npm_package_version is only set by pnpm run, never by
    systemd's/Docker's direct node dist/main.js). Now reads
    package.json directly.
  • 18 of 19 generic master types (unit types, inquiry sources, charge
    types, etc.) 500'd on any create/update call that included a
    description — createMasterSchema's optional description field is
    only backed by a real column on PaymentPlanTemplate; the shared
    factory blindly spread the whole dto into Prisma's data. Fixed once,
    at the root, in the factory.
  • DocumentType, InterestRule, and TransferFeeRule couldn't be
    created via the API at all — each has its own required, non-nullable
    columns (entityType; rateType/ratePercent/frequency;
    feeType) that the generic master schema never had, so every attempt
    500'd on a Prisma "Argument missing" error instead of failing
    validation cleanly. The shared factory now supports a per-model
    extraFields schema extension.
  • LetterTemplate had no working create path at all — zero templates
    could ever exist, blocking demand/allotment/reminder letter generation
    entirely. It was routed through the generic master factory (whose
    schema has no subject/entityType/body) instead of a dedicated
    module; given one, mirroring the existing SmsTemplateModule
    precedent, with merge-field validation at save time.
  • Duplicate master names (any of the 18+ shared-factory types) returned
    a raw 500 instead of a clean "already exists" 400 — nothing in this
    codebase had ever caught Prisma's P2002 unique-constraint error for
    these dynamically-keyed services (unlike RolesService/UsersService,
    which pre-check via findFirst). Mapped once, in the factory.
  • install-native.sh/upgrade-native.sh's database migration step
    failed on any host where the git checkout lives under a directory
    tree the postgres OS user can't traverse (e.g. GitHub Actions
    runners: /home/runner is mode 0750) — Prisma 6.19+ auto-discovers
    a prisma.config.* file in the current working directory before
    running any command, and that lookup's lstat() fails EACCES (not
    ENOENT) in that case, which Prisma treats as a hard failure rather
    than "no config file, proceed." run_as_superuser() now runs from
    the already-world-traversable release directory instead of the
    checkout.
  • install-native.sh's final systemctl reload nginx failed outright
    ("nginx.service is not active, cannot reload") on any host where apt
    installed nginx without starting it — the script only ever checked
    that the nginx binary was present, never that the service was
    running. Now enables and reload-or-restarts it, correct whether
    nginx was already running or not.
  • CustomFieldDefinition.defaultValue — accepted by the create/update
    schema since it was written, but no backing column ever existed, so
    any real caller sending it 500'd. Added the missing column.
  • POST /users never returned the phone it had just saved — a
    select allowlist copy/paste gap (present in update(), missing
    from create()) left an admin with no way to confirm the phone
    number was stored.

Everything above except the two headline bugs was found by a full
production-readiness pass and a new through-the-wire creation test for
every master type and admin-creatable entity (users, roles, custom
fields) — the existing suite seeded rows directly, which is exactly why
these bugs survived to a tagged release. Also added: real-HTTP creation
coverage for all 22 master types plus users/roles/custom fields, and a
native-install CI job that runs the full native install on a real
ubuntu-latest runner on every push. That CI job is not yet green — a
separate, CI-runner-specific issue (the deployed app crash-looping with
SIGSEGV, isolated to argon2's native module, confirmed not to reproduce
on a real server) is still open and tracked in CLAUDE.md; it does not
affect real installs, only that one job's own coverage.