v0.0.2-alpha.14
Pre-releaseReleased 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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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
ea7fbe5Thanks @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
getUserCountdependency in the auth handler bridge used to swallow any DB error and return0, which madeGET /auth/setup-statusreply{ 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 duringPOST /auth/setupcould allow a second super-admin to be created while the real first user was briefly invisible to the query.getUserCountnow propagates errors;handleSetupStatusandhandleSetupcatch them, emit a canonical503 SERVICE_UNAVAILABLEenvelope through the sharedbuildAuthErrorResponsehelper (application/problem+json+x-request-id), and log a structured operator event (setup-status-failed/setup-precheck-failed). The admin'sPrivateRouteandPublicRoutenow consume a sharedlib/auth/setup-status.tsmodule 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.useCurrentUserPermissionsis gated byrouteType === "private"so itsrefetchOnWindowFocuscannot fire/me/permissionsduring 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 returnednullfrom the lookup, the handler interpreted that as "user is gone" and ranclearAndDeny— clearing both auth cookies and revoking the still-valid session.findUserByIdnow propagates errors;handleRefreshwas reordered so all read-only lookups (findUserById,fetchRoleIds,fetchCustomFields) run BEFORE the destructivedeleteRefreshToken, and is wrapped in a try/catch that returns503 SERVICE_UNAVAILABLEon any DB failure with cookies and tokens intact — the client retries on the next request and the session survives. The admin'srefreshAccessTokenwas 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) soauthFetchonly redirects on a genuine 401 from/auth/refreshand surfaces transient server errors to the caller without logging the user out.Internal: consolidated four identical
build{Login,Register,Forgot,Setup}ErrorResponsehelpers into a singlebuildAuthErrorResponseinhandler-utils.ts, fixed a long-standingchange-passwordtest mock missingauditLog/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.