Skip to content

v0.0.2-alpha.14

Pre-release
Pre-release

Choose a tag to compare

@mobeenabdullah mobeenabdullah released this 20 May 10:12
· 216 commits to main since this release
f61b8d2

Released all 12 packages at 0.0.2-alpha.14 in lockstep (nextly, create-nextly-app, and 10 @nextlyhq/* packages).

What's changed

@nextlyhq/adapter-drizzle

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

@nextlyhq/adapter-mysql

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/adapter-drizzle@0.0.2-alpha.14

@nextlyhq/adapter-postgres

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/adapter-drizzle@0.0.2-alpha.14

@nextlyhq/adapter-sqlite

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/adapter-drizzle@0.0.2-alpha.14

@nextlyhq/admin

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/ui@0.0.2-alpha.14

create-nextly-app

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

nextly

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/adapter-drizzle@0.0.2-alpha.14
    • @nextlyhq/adapter-mysql@0.0.2-alpha.14
    • @nextlyhq/adapter-postgres@0.0.2-alpha.14
    • @nextlyhq/adapter-sqlite@0.0.2-alpha.14

@nextlyhq/plugin-form-builder

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

  • Updated dependencies [ea7fbe5]:

    • @nextlyhq/admin@0.0.2-alpha.14
    • nextly@0.0.2-alpha.14
    • @nextlyhq/ui@0.0.2-alpha.14

@nextlyhq/storage-s3

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

@nextlyhq/storage-uploadthing

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

@nextlyhq/storage-vercel-blob

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.

@nextlyhq/ui

Patch Changes

  • #49 ea7fbe5 Thanks @aqib-rx! - Fix two related admin-auth failures that surface on hosted databases (Neon, Supabase, PlanetScale, etc.) during transient DB hiccups.

    Login/setup fluctuation. The getUserCount dependency in the auth handler bridge used to swallow any DB error and return 0, which made GET /auth/setup-status reply { isSetup: false } whenever a pool cold-start, brief disconnect, or failover landed on this endpoint — the admin route guards then redirected the user to /admin/setup, the next call returned { isSetup: true } once the DB recovered, and the guards redirected back to /admin/login, oscillating until the next hiccup or full page reload. The user count is the bootstrap-gate for two security-relevant decisions (setup-status reporting and the first-admin pre-check), and treating an unknown count as zero also opened a window where a transient DB failure during POST /auth/setup could allow a second super-admin to be created while the real first user was briefly invisible to the query. getUserCount now propagates errors; handleSetupStatus and handleSetup catch them, emit a canonical 503 SERVICE_UNAVAILABLE envelope through the shared buildAuthErrorResponse helper (application/problem+json + x-request-id), and log a structured operator event (setup-status-failed / setup-precheck-failed). The admin's PrivateRoute and PublicRoute now consume a shared lib/auth/setup-status.ts module that fail-safes to "setup complete" on any failure (network error, 5xx, invalid response shape) — staying on the dashboard or login screen is recoverable on the next request, whereas dragging an authenticated user into the setup wizard is destructive. useCurrentUserPermissions is gated by routeType === "private" so its refetchOnWindowFocus cannot fire /me/permissions during a brief Suspense window on a public route.

    Intermittent logout around the access-token TTL boundary. The same swallow-and-return-null pattern lived in findUserById, which the refresh handler called after deleting the old refresh token. A momentary DB hiccup at the 15-minute boundary returned null from the lookup, the handler interpreted that as "user is gone" and ran clearAndDeny — clearing both auth cookies and revoking the still-valid session. findUserById now propagates errors; handleRefresh was reordered so all read-only lookups (findUserById, fetchRoleIds, fetchCustomFields) run BEFORE the destructive deleteRefreshToken, and is wrapped in a try/catch that returns 503 SERVICE_UNAVAILABLE on any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin's refreshAccessToken was a boolean primitive that treated every non-200 response (5xx, network errors, our new 503) as "session invalid" and redirected to login; it now returns a tri-state (ok / auth_failed / transient) so authFetch only redirects on a genuine 401 from /auth/refresh and surfaces transient server errors to the caller without logging the user out.

    Internal: consolidated four identical build{Login,Register,Forgot,Setup}ErrorResponse helpers into a single buildAuthErrorResponse in handler-utils.ts, fixed a long-standing change-password test mock missing auditLog/trustProxy/trustedProxyIps, and added regression tests covering the 503 path on both setup endpoints, the refresh-handler 503 path (asserting no cookie clearing and no token deletion), and the "no super-admin is created when the pre-check throws" security invariant.