npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ab-ecosystem-sso

v0.1.12

Published

Drop-in React auth & account UI for the partner-ecosystem **Auth-as-a-Service** backend. Ships production-ready screens — login (with 2FA), signup, forgot-password, session validation, and a full **Account Settings** modal (profile, wallet, plans & add-on

Downloads

1,069

Readme

ab-ecosystem-sso

Drop-in React auth & account UI for the partner-ecosystem Auth-as-a-Service backend. Ships production-ready screens — login (with 2FA), signup, forgot-password, session validation, and a full Account Settings modal (profile, wallet, plans & add-ons, credit wallets) — all wired to the /api/v1/projects/:projectId/... endpoints, plus low-level helpers (getUser, logout, useSession) for building your own UI.

  • ⚛️ React 18 / 19, shipped as ESM + CJS with full TypeScript types.
  • 🎨 Self-contained, theme-aware CSS that won't leak into or be overridden by your app.
  • 🔌 Use it declaratively (<AccountSettings />) or imperatively (openAccountSettings()).
  • 🧩 Every network call also available as a plain function for custom UIs.

Table of contents


Installation

npm install ab-ecosystem-sso

react and react-dom (v18 or v19) are peer dependencies — install them in your app.

Import the stylesheet once

import "ab-ecosystem-sso/styles.css";

The CSS is scoped (a [data-aas] root + the sso: utility prefix) and shipped unlayered, so it never touches your app's elements and your app's resets (e.g. Tailwind's preflight) never override it. No extra @layer setup needed.


Configuration basics

Almost everything takes the same three inputs:

| Input | Description | | --- | --- | | projectId | Your project id, e.g. 436582-forms.abprojects.com. Required. | | token | The auth JWT from the login response. Required for authenticated calls. | | apiBaseUrl | API origin, e.g. https://partner-api.example.com. Falls back to the VITE_AUTH_API_BASE_URL env var. |

You own the token: persist it (e.g. localStorage) on onSuccess, pass it back into the authenticated components/functions, and clear it on logout.


Auth screens

Login

import { Login } from "ab-ecosystem-sso";
import "ab-ecosystem-sso/styles.css";

function LoginPage() {
  return (
    <Login
      projectId="your-project-id"
      apiBaseUrl="https://your-api"
      onSuccess={({ token, user }) => {
        localStorage.setItem("token", token);
        navigate("/dashboard");
      }}
      onError={(err) => toast.error(err.message)}
      onForgotPassword={() => navigate("/forgot-password")}
      onSignupClick={() => navigate("/signup")}
    />
  );
}

Login first fetches the project's auth-config (captcha provider/site-key, branding, which links to show) and renders accordingly. If the account has 2FA enabled it automatically shows a segmented 6-digit OTP step (with captcha when the project requires it), and only calls onSuccess after verification succeeds.

Signup and ForgotPassword

import { Signup, ForgotPassword } from "ab-ecosystem-sso";

<Signup projectId="…" onSuccess={handleAuth} onLoginClick={() => navigate("/login")} />
<ForgotPassword projectId="…" onBackToLogin={() => navigate("/login")} />

Shared auth props

| Prop | Type | Notes | | --- | --- | --- | | projectId | string | Required. | | apiBaseUrl | string | Optional API origin. | | config | AuthConfig | Pre-fetched auth-config to skip the internal request. | | onSuccess | ({ token, user }) => void | On successful auth. | | onError | (error: Error) => void | On failure. | | classNames | AuthClassNames | Per-element class overrides: card, input, label, button, link, captcha, error, … |

Per-screen extras: Login adds onForgotPassword / onSignupClick; Signup adds onLoginClick; ForgotPassword adds onBackToLogin.


Session management

Wrap authenticated areas in SessionProvider. It validates the token against /me and renders children only when the session is valid; otherwise it calls onSessionInvalid (redirect to login there).

import { SessionProvider } from "ab-ecosystem-sso";

<SessionProvider
  projectId="your-project-id"
  token={token}
  apiBaseUrl="https://your-api"
  onSessionInvalid={() => navigate("/login")}
>
  <Dashboard />
</SessionProvider>

| Prop | Type | Notes | | --- | --- | --- | | projectId | string | Required. | | token | string \| null | Null ⇒ treated as signed-out. | | onSessionInvalid | () => void | Called on invalid/expired session or logout. | | apiBaseUrl | string | Optional. | | enforceActiveSubscription | boolean | See below. |

Enforce an active subscription

With enforceActiveSubscription, once authenticated the provider checks the user's subscription. If none is active it opens the Account Settings modal on the Plans section — and blocks closing until a subscription exists (it re-checks on every close attempt).

<SessionProvider … enforceActiveSubscription>
  <Dashboard />
</SessionProvider>

Account Settings modal

A complete settings surface with these sections: General (profile), Account (email, change password, 2FA, sessions), Wallet & Balance (balances + transaction history), Plans & Add-ons (subscribe, multi-subscribe add-ons, markdown plan details), and Credit Wallets (per-wallet balance, logs, top-up). It loads and saves via the live API whenever projectId + token are supplied (otherwise it renders sample data).

Imperative (recommended)

Open it from any click handler; it mounts into <body> and unmounts on close:

import { openAccountSettings } from "ab-ecosystem-sso";

<button onClick={() => openAccountSettings({
  projectId: "your-project-id",
  token,
  apiBaseUrl: "https://your-api",
  initialSection: "plans",             // optional
  onLogout: () => { localStorage.clear(); navigate("/login"); },
})}>
  Settings
</button>

openAccountSettings(options) returns a handle: { close() } to dismiss it programmatically.

Declarative

import { AccountSettings } from "ab-ecosystem-sso";

const [open, setOpen] = useState(false);

<AccountSettings
  open={open}
  onOpenChange={setOpen}
  projectId="your-project-id"
  token={token}
  apiBaseUrl="https://your-api"
  initialSection="general"
  onLogout={() => { localStorage.clear(); navigate("/login"); }}
/>

Props

| Prop | Type | Notes | | --- | --- | --- | | open | boolean | Required (declarative). | | onOpenChange | (open: boolean) => void | Required (declarative). | | projectId | string | Enables live API mode (with token). | | token | string | Bearer token. | | apiBaseUrl | string | Optional API origin. | | initialSection | "general" \| "account" \| "wallet" \| "plans" \| "credits" | First section shown. Default "general". | | syncUrlHash | boolean | Mirror open state + active section in the URL hash. Default true. See URL hash & refresh. | | title | string | Modal heading. Default "Settings". | | data | Partial<AccountSettingsData> | Override sample data (non-API mode / previews). | | onLogout | () => void | Sign-out action (closes the modal, then runs this). | | onSaveProfile | (profile) => void | Fires alongside the profile save. | | onChangePassword | (data) => void | Fires alongside the password change. | | onChangePlan | (planId) => void | Fires when a plan is subscribed. | | onUpdateWhatsApp, onToggleTwoFactor, onRevokeSession, onAddFunds | callbacks | Observers for the respective actions. |

In live mode the modal performs the API calls itself (save profile, change password, subscribe, recharge, etc.); the callbacks are observation hooks, not required wiring.

URL hash & refresh

While the modal is open it reflects the active section in the URL hash as #settings-<section> (e.g. #settings-general, #settings-plans), updating as the user switches tabs and clearing it on close. This means a page refresh can re-open the modal on the same section. It uses history.replaceState, so it never adds browser-history entries and never clobbers an unrelated #anchor. Opt out with syncUrlHash={false}.

  • Declarative <AccountSettings>: keep it mounted (even while closed) and wire open/onOpenChange normally — on load it reads the hash and calls onOpenChange(true) for you.

  • Imperative openAccountSettings(): call restoreAccountSettings() once on app load to re-open it after a refresh:

    import { restoreAccountSettings } from "ab-ecosystem-sso";
    
    useEffect(() => {
      restoreAccountSettings({ projectId: "your-project-id", token, apiBaseUrl: "https://your-api" });
    }, []);

    It opens the modal (on the hash's section) only when a #settings-* hash is present, and returns the modal handle (or null).


Logout

Three options, from most convenient to most raw:

import { LogoutButton, logout, logoutApi } from "ab-ecosystem-sso";

// 1) Ready-made button (works standalone, no SessionProvider needed)
<LogoutButton
  projectId="your-project-id"
  token={token}
  apiBaseUrl="https://your-api"
  onSuccess={() => { localStorage.clear(); navigate("/login"); }}
/>

// 2) A method to attach to any custom control (e.g. a profile-dropdown item)
onClick={() => logout({
  projectId: "your-project-id",
  token,
  apiBaseUrl: "https://your-api",
  onSuccess: () => { localStorage.clear(); navigate("/login"); },
  onError: (e) => console.error(e),
})}

// 3) The raw endpoint call
await logoutApi("your-project-id", token, "https://your-api");

logout POSTs to /auth/logout and always runs onSuccess once the request settles — so the user is logged out client-side even if the server call fails (onError fires for observability). Inside a SessionProvider, <LogoutButton /> with no props uses the provider's logout instead.


Fetching the current user

Populate your own header / profile-dropdown from /me:

import { getUser } from "ab-ecosystem-sso";

const res = await getUser("your-project-id", token, "https://your-api");
// res = { success, data: { id, username, profile: { name, email, address, … }, mainWallet, coinWallet } }
const firstName = res.data.profile.name.first;

getUser returns the raw response envelope and throws AuthApiError on a non-2xx response.


Fetching user data

When you want clean, ready-to-render data instead of the raw envelope, use these getters. Each fetches and maps in one call, and all share the same (projectId, token, apiBaseUrl?) signature.

import {
  getUserProfile,
  getUserWallet,
  getUserCreditWallet,
  getUserSubscription,
} from "ab-ecosystem-sso";

const profile       = await getUserProfile("your-project-id", token, "https://your-api");
const { mainWallet, coinWallet } = await getUserWallet("your-project-id", token, "https://your-api");
const creditWallets = await getUserCreditWallet("your-project-id", token, "https://your-api");
const subscription  = await getUserSubscription("your-project-id", token, "https://your-api");

| Function | Endpoint | Returns | | --- | --- | --- | | getUserProfile | GET /me | MappedProfile — flat fields (name, email/username, address, phone, mainWallet/coinWallet). | | getUserWallet | GET /me | UserWallets — just { mainWallet, coinWallet } balances. | | getUserCreditWallet | GET /credit-wallets | CreditWallet[] — each { id, label, balance, balanceFormatted, creditUnit, fundSource, rechargeable }. | | getUserSubscription | GET /subscription | The raw subscription object from the API (unmapped). |

getUserProfile and getUserWallet both read /me; getUserWallet just narrows the same response to the wallet balances. getUserSubscription returns the API response as-is (the unwrapped data), unlike the others which map to a friendly shape. All four throw AccountApiError on a non-2xx response.


Two-factor setup

For a settings page outside the modal:

import { TwoFactorSetup } from "ab-ecosystem-sso";

<TwoFactorSetup
  projectId="your-project-id"
  token={token}
  apiBaseUrl="https://your-api"
  onEnabled={() => …}
  onDisabled={() => …}
  onError={(err) => …}
/>

Hooks

| Hook | Returns | | --- | --- | | useSession() | { isAuthenticated, isValidating, logout } — must be inside a SessionProvider. | | useOptionalSession() | Same, or null when there is no provider. | | useAuthConfig() | The project's auth-config (captcha, branding, card layout). |


Theming

The UI reads --aas-* CSS custom properties, so you can retheme without touching the components. Override them on :root (or any ancestor) after importing the stylesheet:

:root {
  --aas-primary: oklch(0.55 0.22 265);
  --aas-primary-foreground: oklch(0.99 0 0);
  --aas-background: oklch(1 0 0);
  --aas-foreground: oklch(0.15 0 0);
  --aas-muted: oklch(0.97 0 0);
  --aas-muted-foreground: oklch(0.55 0 0);
  --aas-border: oklch(0.92 0 0);
  --aas-radius: 0.625rem;
}

The styles are theme-aware and scoped to the components — they won't affect the rest of your app, and your app's global resets can't override them.


API reference

Beyond the components, these functions are exported for building custom UIs:

| Function | Endpoint | | --- | --- | | getAuthConfig(projectId, apiBaseUrl?) | GET /auth-configAuthConfig (captcha, branding, card layout; no token) | | getUser(projectId, token, apiBaseUrl?) | GET /me (raw envelope) | | getUserProfile(projectId, token, apiBaseUrl?) | GET /meMappedProfile | | getUserWallet(projectId, token, apiBaseUrl?) | GET /meUserWallets | | getUserCreditWallet(projectId, token, apiBaseUrl?) | GET /credit-walletsCreditWallet[] | | getUserSubscription(projectId, token, apiBaseUrl?) | GET /subscription → raw subscription object | | logout(options) | POST /auth/logout + callbacks | | logoutApi(projectId, token, apiBaseUrl?) | POST /auth/logout |

The Account Settings modal also drives a wider set of endpoints internally — profile (/me, /profile), password (/account), 2FA, WhatsApp, plans/subscription (/plans, /subscribe, /subscription), wallet (/wallet/logs, /recharge-links), and credit wallets (/credit-wallets, /credit-wallets/:id/logs, /credit-wallets/:id/recharge). If you need any of these as standalone helpers, open an issue and they can be exported.


TypeScript

Everything is fully typed. Notable exports:

import type {
  LoginProps, SignupProps, ForgotPasswordProps, AuthClassNames,
  AccountSettingsProps, AccountSettingsSection, AccountSettingsData, ProfileFormValues,
  OpenAccountSettingsOptions, AccountSettingsHandle,
  AuthConfig, CaptchaConfig, CardLayoutConfig,
  LoginRequest, SignupRequest, ForgotPasswordRequest, AuthResponse, LogoutOptions,
  MappedProfile, WalletInfo, UserWallets, CreditWallet, MappedSubscription,
} from "ab-ecosystem-sso";

import { AuthApiError } from "ab-ecosystem-sso";

License

Proprietary — © combot.ltd. Internal partner-ecosystem use.