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
- Configuration basics
- Auth screens —
Login·Signup·ForgotPassword - Session management —
SessionProvider, enforce subscription - Account Settings modal —
openAccountSettings/AccountSettings - Logout —
logout·LogoutButton·logoutApi - Fetching the current user —
getUser - Fetching user data —
getUserProfile·getUserWallet·getUserCreditWallet·getUserSubscription - Two-factor setup
- Hooks
- Theming
- API reference
- TypeScript
Installation
npm install ab-ecosystem-ssoreact 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 wireopen/onOpenChangenormally — on load it reads the hash and callsonOpenChange(true)for you.Imperative
openAccountSettings(): callrestoreAccountSettings()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 (ornull).
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). |
getUserProfileandgetUserWalletboth read/me;getUserWalletjust narrows the same response to the wallet balances.getUserSubscriptionreturns the API response as-is (the unwrappeddata), unlike the others which map to a friendly shape. All four throwAccountApiErroron 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-config → AuthConfig (captcha, branding, card layout; no token) |
| getUser(projectId, token, apiBaseUrl?) | GET /me (raw envelope) |
| getUserProfile(projectId, token, apiBaseUrl?) | GET /me → MappedProfile |
| getUserWallet(projectId, token, apiBaseUrl?) | GET /me → UserWallets |
| getUserCreditWallet(projectId, token, apiBaseUrl?) | GET /credit-wallets → CreditWallet[] |
| 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.
