aoothjs
Install
npx skills add moostjs/aoothjs # this skill — full aoothjs stack
npx skills add moostjs/moostjs # sibling — moost framework
npx skills add moostjs/atscript # sibling — .as syntax
npx skills add moostjs/atscript-db # sibling — @atscript/db
# Core (always needed)
pnpm add @aooth/user @aooth/arbac
# Credential layer (sessions, JWT, magic links, refresh)
pnpm add @aooth/auth
pnpm add ioredis # opt: Redis store
pnpm add @atscript/db # opt: DB-backed store
# Federated login (OAuth2 / OIDC — Sign in with Google)
pnpm add @aooth/idp # opt: federated-login core
# CLI loopback login (client half of the authorization server; zero deps)
pnpm add @aooth/login-client
# Moost integration
pnpm add @aooth/auth-moost @aooth/arbac-moost moost
pnpm add @moostjs/event-http @moostjs/event-wf @atscript/moost-wf
# Atscript build (for .as models)
pnpm add -D unplugin-atscript @atscript/typescript @atscript/core
Packages
@aooth/arbac-core zero-dep RBAC engine: Arbac, evaluate(), wildcard patterns, deny-wins
└── @aooth/arbac re-exports core + defineRole, definePrivilege, allowTable*, scope mergers, codegen
└── @aooth/arbac-moost moost guard + interceptor + AsArbacDbController + ArbacUserProvider
│ └── /atscript AtscriptArbacUserProvider, AoothArbacUserCredentials
│ └── /plugin arbacPlugin() — @arbac.role/.attribute/.userId
│
@aooth/user UserService, password (scrypt), policy, TOTP/HOTP, backup codes, lockout, FederatedIdentityStore (account linking)
│ └── /atscript-db UsersStoreAtscriptDb + AoothUserCredentials .as model; FederatedIdentityStoreAtscriptDb + AoothFederatedIdentity .as model
│
└── @aooth/auth AuthCredential, stores (Memory/JWT/Encapsulated/Redis/AtscriptDb), denylist, magic links
│ └── /redis CredentialStoreRedis, DenylistStoreRedis
│ └── /atscript-db CredentialStoreAtscriptDb + AoothAuthCredential .as model
│ └── /authz authorization-server core: IdTokenSigner, Loopback/Registered/Dynamic/Composite client policies, OidcClaimsResolver, pending/auth-code/dynamic-client stores, DynamicClientRegistration (RFC 7591), RFC 8414/9728 metadata + WWW-Authenticate builders
│
├── @aooth/idp federated-login core (depends on user+auth): OidcProvider/GoogleProvider/FakeIdentityProvider, OAuthProviderRegistry,
│ FederatedLoginService.resolveUser, PKCE + signState/verifyState, OAuthError. HTTP/workflow wiring is in auth-moost (OAuthController + federated leg of auth/login/flow)
│
└── @aooth/auth-moost AuthController, SessionsController, OAuthController, AuthorizeController (authorization server: /auth/authorize + /auth/token + OIDC discovery/JWKS), authGuardInterceptor, @Public, @UserId, useAuth,
AuthWorkflow (login [+federated sso-callback/prove-control] /invite/recovery/signup/change-password/add-mfa), ConsentStore, WfTriggerProvider
@aooth/login-client ZERO-dep CLI half of the authorization server: authorize() — browser + PKCE + one-shot loopback callback → token. Depends on nothing (not even @aooth/*)
Quick start
// src/app-user.as
import { AoothArbacUserCredentials } from '@aooth/arbac-moost/atscript/models'
// id (PK / @meta.id — the token subject), username + email (unique handles),
// version — all inherited. The auth subject IS @meta.id, so the provider's
// default lookup chain resolves to it: NO @arbac.userId annotate needed.
@db.table 'users'
export interface AppUser extends AoothArbacUserCredentials {
@arbac.attribute
department?: string
}
// src/main.ts
import { Moost, createProvideRegistry, createReplaceRegistry } from "moost";
import { MoostHttp } from "@moostjs/event-http";
import { MoostWf } from "@moostjs/event-wf";
import { formInputInterceptor } from "@atscript/moost-wf";
import { DbSpace, syncSchema } from "@atscript/db";
import { SqliteAdapter, BetterSqlite3Driver } from "@atscript/db-sqlite";
import { UserService } from "@aooth/user";
import { UsersStoreAtscriptDb, type AuthUserTable } from "@aooth/user/atscript-db";
import { AuthCredential, CredentialStoreJwt } from "@aooth/auth";
import { AuthController, authGuardInterceptor, AuthWorkflow, useAuth } from "@aooth/auth-moost";
import {
MoostArbac,
arbacAuthorizeInterceptor,
ArbacUserProviderToken,
type ArbacDbScope,
} from "@aooth/arbac-moost";
import { AtscriptArbacUserProvider, type ArbacUserTable } from "@aooth/arbac-moost/atscript";
import { defineRole, allowTableRead } from "@aooth/arbac";
import { Injectable, getMoostInfact } from "moost";
import { AppUser } from "./app-user.as";
const db = new DbSpace(() => new SqliteAdapter(new BetterSqlite3Driver(":memory:")));
await syncSchema(db, [AppUser]);
const userStore = new UsersStoreAtscriptDb<AppUser>({
table: db.getTable(AppUser) as unknown as AuthUserTable<AppUser>,
});
const userService = new UserService<AppUser>(userStore, {
password: { pepper: process.env.AOOTH_PEPPER ?? "" },
});
const auth = new AuthCredential({
store: new CredentialStoreJwt({ secret: process.env.AOOTH_JWT_SECRET! }),
accessTtl: 3_600_000,
refresh: { ttl: 7 * 24 * 3_600_000, rotation: "always" },
});
@Injectable()
class AppUserProvider extends AtscriptArbacUserProvider<AppUser> {
constructor() {
// Provider resolves users by `@meta.id` (= `id` = the auth subject) — no
// @arbac.userId, no shim. The cast is needed because `AtscriptDbTable.findOne`
// is typed wider than `ArbacUserTable.findOne` (engine-specific `controls.*`
// keys); they're structurally compatible at runtime.
super(AppUser, db.getTable(AppUser) as unknown as ArbacUserTable<AppUser>);
}
override getUserId() {
// Returns the stable `id` — the token `sub` claim `auth.issue(subject)` set.
return useAuth().getUserId();
}
}
const app = new Moost();
app.adapter(new MoostHttp());
app.adapter(new MoostWf());
app.setProvideRegistry(
createProvideRegistry([UserService, () => userService], [AuthCredential, () => auth]),
);
app.setReplaceRegistry(createReplaceRegistry([ArbacUserProviderToken, AppUserProvider]));
app.applyGlobalInterceptors(
authGuardInterceptor(),
arbacAuthorizeInterceptor,
formInputInterceptor(),
);
app.registerControllers(AuthController);
await app.init();
// Grab the singleton MoostArbac from moost's IoC container and register roles.
const arbac = (await getMoostInfact().get(MoostArbac)) as MoostArbac<
{ department?: string },
ArbacDbScope
>;
arbac.registerRole(defineRole().id("reader").use(allowTableRead("articles")).build());
Invariants
Engine-internals — see references/invariants.md for the full 18-row table covering deny-wins, scope-union sentinels, refresh-rotation degradation, moost@0.6.x DI quirks, and the dual-purpose @Public(). Load when debugging silent-deny / refresh / scope-merge issues.
Key imports
// — @aooth/user
import {
UserService,
UserStore,
UserStoreMemory,
PasswordHasher,
PasswordPolicy,
definePasswordPolicy,
normalizePolicies,
ppHasMinLength,
ppHasUpperCase,
ppHasLowerCase,
ppHasNumber,
ppHasSpecialChar,
ppMaxRepeatedChars,
generateTotpSecret,
generateTotpUri,
generateTotpCode,
verifyTotpCode,
generateMfaCode,
hashMfaCode,
verifyMfaCode,
maskEmail,
maskPhone,
maskMfaValue,
setAtPath,
UserAuthError,
} from "@aooth/user";
import type {
UserCredentials,
PasswordData,
AccountData,
MfaData,
MfaMethod,
UserServiceConfig,
PasswordConfig,
LockoutConfig,
PasswordPolicyDef,
PasswordPolicyInstance,
UserStoreUpdate,
DeepPartial,
LoginResult,
LockStatus,
PolicyCheckResult,
TransferablePolicy,
MfaMethodInfo,
TotpConfig,
TrustedDeviceRecord,
UserAuthErrorType,
} from "@aooth/user";
// — @aooth/user/atscript-db
import { UsersStoreAtscriptDb } from "@aooth/user/atscript-db";
import type { UserCredentialsRow, AuthUserTable } from "@aooth/user/atscript-db";
import { AoothUserCredentials } from "@aooth/user/atscript-db/model.as";
// — @aooth/arbac (re-exports @aooth/arbac-core)
import {
Arbac,
arbacPatternToRegex,
defineRole,
definePrivilege,
allowTableRead,
allowTableWrite,
allowTableAction,
mergeScopeFilters,
unionProjections,
restrictProjection,
getProjectionMode,
isFieldAllowed,
unionControlsPolicy,
extractResourceActions,
generateResourceTypes,
} from "@aooth/arbac";
import type {
TArbacRole,
TArbacRule,
TArbacEvalResult,
RoleBuilder,
TPrivilegeFunction,
TProjection,
TProjectionMode,
TScopeFilter,
ControlGate,
TCodegenOptions,
TResourceActionMap,
} from "@aooth/arbac";
// — @aooth/auth + subpaths
import {
AuthCredential,
AuthError,
CredentialStoreMemory,
CredentialStoreJwt,
CredentialStoreEncapsulated,
DenylistStoreMemory,
generateMagicLinkToken,
generateOpaqueToken, // the shared CSPRNG mint (magic links / client secrets / one-shot tokens)
defaultClock,
} from "@aooth/auth";
import type {
AuthContext,
CredentialMetadata, // framework keys: credentialKind, authzClientId, accessTtl, refreshRotation
CredentialState,
IssueResult,
RefreshResult, // refresh()'s return — IssueResult + userId
IssueOptions, // per-mint ttl/expiresAt/kind/refresh (refresh: false | { ttl? })
RefreshCallOptions, // refresh(token, { guard }) — pre-rotation gate
RefreshConfig,
CredentialStore,
DenylistStore,
SessionInfo,
EnrichedSession,
SessionEnricher,
EmailSender,
AuthEmailEvent,
AuthEmailKind,
SmsSender,
AuthSmsEvent,
AuthSmsKind,
BuildMagicLinkUrl,
Clock,
AuthErrorType,
} from "@aooth/auth";
import { CredentialStoreRedis, DenylistStoreRedis } from "@aooth/auth/redis";
import { CredentialStoreAtscriptDb } from "@aooth/auth/atscript-db";
import type { AuthCredentialRow, AuthCredentialTable } from "@aooth/auth/atscript-db";
import { AoothAuthCredential } from "@aooth/auth/atscript-db/model.as";
// — @aooth/idp (federated login — see references/idp.md for the full surface)
import {
OidcProvider,
GoogleProvider,
FakeIdentityProvider,
OAuthProviderRegistry,
FederatedLoginService,
createPkcePair,
generateNonce,
signState,
verifyState,
resolveFederatedPolicy,
OAuthError,
} from "@aooth/idp";
import type {
IdentityProvider,
NormalizedProfile,
FederatedPolicy,
ResolveOutcome,
OidcProviderOptions,
OAuthProviderRegistryOptions,
FederatedLoginServiceDeps,
OAuthStatePayload,
OAuthErrorType,
} from "@aooth/idp";
// account-linking store ships in @aooth/user (NOT @aooth/idp):
import {
FederatedIdentityStore,
FederatedIdentityStoreMemory,
pickDefinedProfile,
} from "@aooth/user";
import type {
FederatedIdentity,
NewFederatedIdentity,
FederatedProfileSnapshot,
} from "@aooth/user";
import { FederatedIdentityStoreAtscriptDb } from "@aooth/user/atscript-db";
// — @aooth/auth-moost
import {
AuthController,
authGuardInterceptor,
AuthGuarded,
Public,
UserId,
useAuth,
getAuthMate,
AuthWorkflow,
ConsentStore,
SessionsController,
SessionEnricherProvider,
deriveWfStateSecret,
WfTrigger,
WfTriggerProvider,
createAuthEmailOutlet,
DEFAULT_AUTH_WORKFLOWS,
buildInviteAlreadyAcceptedEnvelope,
parseInviteRoles,
stripReservedUserKeys,
RESERVED_USER_KEYS,
haversineKm,
humanizeUserAgent,
} from "@aooth/auth-moost";
import type {
AuthOptions,
ResolvedAuthOptions,
ResolvedAuthCookieConfig,
AuthBindings,
AuthLoginResponse,
AuthLogoutBody,
AuthRefreshBody,
AuthOkResponse,
AuditEvent,
AuditEmitter,
AuthDeliveryPayload,
AuthWorkflowOpts,
ResolvedAuthWorkflowOpts,
AuthWfCtx,
ConsentDescriptor,
ConsentEvent,
WfTriggerOpts,
} from "@aooth/auth-moost";
// — @aooth/arbac-moost + subpaths
import {
MoostArbac,
arbacAuthorizeInterceptor,
ArbacResource,
ArbacAction,
ArbacAuthorize,
useArbac,
ArbacUserProvider,
ArbacUserProviderToken,
AsArbacDbController,
AsArbacDbReadableController,
} from "@aooth/arbac-moost";
import type { TArbacMeta, ArbacBindings, ArbacDbScope } from "@aooth/arbac-moost";
import { AtscriptArbacUserProvider } from "@aooth/arbac-moost/atscript";
import type { ArbacUserTable } from "@aooth/arbac-moost/atscript";
import { AoothArbacUserCredentials } from "@aooth/arbac-moost/atscript/models";
import arbacPlugin from "@aooth/arbac-moost/plugin";
// — @aooth/login-client (CLI side — zero deps, see references/login-client.md)
import { authorize, AuthorizeError } from "@aooth/login-client";
import type { AuthorizeOptions, AuthorizeResult, AuthorizeErrorCode } from "@aooth/login-client";
References — load only what's needed
| Domain |
File |
When |
| First contact |
getting-started.md |
Install matrix, minimum wiring (with + without moost), atscript-db wiring, choosing token/user stores, testing patterns |
| Ecosystem map |
ecosystem.md |
Package responsibility matrix, dep graph, peer-dep requirements, subpath export map |
| Annotation reference |
annotations.md |
Every @arbac.* annotation + how aoothjs reads @db.* / @meta.* / @ui.form.* / @expect.* / @wf.* from atscript |
| User domain |
user.md |
@aooth/user overview: UserService quick start, full invariants table, key imports |
UserService reference |
user-service.md |
Every public method, config defaults, login flow, lockout, MFA methods, backup codes, trusted devices, seen-device recognition ledger, correspondence email (setVerifiedEmail / getCorrespondenceEmail) |
| Password subsystem |
password.md |
Scrypt + pepper + history, generatePassword, PasswordPolicy DSL, transferable policies, built-in ppHas* factories |
| MFA primitives |
mfa.md |
TOTP secret/URI/code/verify, MFA-code helpers, backup codes, trusted-device tokens |
| User stores |
user-stores.md |
UserStore contract, UserStoreMemory, custom-store skeleton, UsersStoreAtscriptDb wiring |
| ARBAC domain |
arbac.md |
@aooth/arbac + arbac-core overview: quick start, full invariants, key imports |
| Engine + builder |
builder.md |
Arbac class, defineRole chain, definePrivilege double-call, allowTable* helpers + action vocabulary |
| Scope merging |
scopes.md |
ArbacDbScope shape, mergeScopeFilters, unionProjections truth table, restrictProjection, unionControlsPolicy; attenuation conjunction (conjoinScopeFilters / intersectControlsPolicy / conjoinArbacDbScopes / extractAttenuation — scoped tokens / PATs) |
| Codegen |
codegen.md |
Library API + CLI: extractResourceActions, generateResourceTypes, aoothjs-arbac-codegen --roles ... --output ... |
| Auth domain |
auth.md |
@aooth/auth overview: quick start, full invariants, key imports |
| Tokens & sessions |
tokens.md |
CredentialStoreJwt algorithms, claim layout, CredentialStoreEncapsulated, sessions vs tokens |
| Refresh & rotation |
refresh.md |
RefreshConfig, three rotation modes, per-mint IssueOptions.refresh (the refresh() guard, RefreshResult, metadata.refreshRotation/accessTtl stamps), reuse detection, stateless degradation, maxConcurrent, epoch revocation |
| Client (silent refresh) |
client.md |
@aooth/auth/client browser subpath: createAuthedFetch — credentials forwarding, single-flight /auth/refresh on 401, retry-once, onLogout, status probe |
| Magic links |
magic-links.md |
generateMagicLinkToken, single-use guarantees, stateless DenylistStore requirement, recovery recipe |
| Auth stores |
auth-stores.md |
CredentialStore + DenylistStore contracts, Memory / Redis / atscript-db, shipped AoothAuthCredential model |
| Sessions / devices |
sessions.md |
Active-sessions screen: sessionId token-family, listSessions / revokeSession / revokeOtherSessions, SessionEnricher, trackLastSeen, SessionsController + useAuth() facade, getSessionId |
…(truncated)
1---2name: aoothjs3description: Use when adding authentication or authorization to a Moost app — login, JWT/session tokens, password+MFA (TOTP), SMS OTP, RBAC roles/guards, magic links, password reset, invites, OAuth2/OIDC federated login, or BE the OAuth/OIDC provider (CLI SSO, `@aooth/login-client`; MCP-connector DCR (RFC 7591) + client_secret_post, refresh_token grant, RFC 8414/9728 discovery metadata). Covers `@aooth/user`, `@aooth/auth`, `@aooth/idp`, `@aooth/arbac`, `@aooth/auth-moost`, `@aooth/arbac-moost` — the aoothjs auth+authz stack for moost/atscript apps. Triggers on `.as` models extending `AoothUserCredentials` / `AoothArbacUserCredentials`, `@arbac.*` / `@aooth.user.*` annotations, refresh rotation, `AuthWorkflow` seams, `FederatedLoginService`, `ConsentStore`, consent-only authorize (`resolveAuthzReauthPolicy`), or `authGuardInterceptor` / `arbacAuthorizeInterceptor`. Out of scope: moost internals (`moostjs`), `.as`/`asc` (`atscript`), `@atscript/db`/`moost-db` (`atscript-db`), `@ui.*`/SPA components (`atscript-ui`).4---56# aoothjs78## Install910```bash11npx skills add moostjs/aoothjs # this skill — full aoothjs stack12npx skills add moostjs/moostjs # sibling — moost framework13npx skills add moostjs/atscript # sibling — .as syntax14npx skills add moostjs/atscript-db # sibling — @atscript/db15```1617```bash18# Core (always needed)19pnpm add @aooth/user @aooth/arbac2021# Credential layer (sessions, JWT, magic links, refresh)22pnpm add @aooth/auth23pnpm add ioredis # opt: Redis store24pnpm add @atscript/db # opt: DB-backed store2526# Federated login (OAuth2 / OIDC — Sign in with Google)27pnpm add @aooth/idp # opt: federated-login core2829# CLI loopback login (client half of the authorization server; zero deps)30pnpm add @aooth/login-client3132# Moost integration33pnpm add @aooth/auth-moost @aooth/arbac-moost moost34pnpm add @moostjs/event-http @moostjs/event-wf @atscript/moost-wf3536# Atscript build (for .as models)37pnpm add -D unplugin-atscript @atscript/typescript @atscript/core38```3940## Packages4142```43@aooth/arbac-core zero-dep RBAC engine: Arbac, evaluate(), wildcard patterns, deny-wins44 └── @aooth/arbac re-exports core + defineRole, definePrivilege, allowTable*, scope mergers, codegen45 └── @aooth/arbac-moost moost guard + interceptor + AsArbacDbController + ArbacUserProvider46 │ └── /atscript AtscriptArbacUserProvider, AoothArbacUserCredentials47 │ └── /plugin arbacPlugin() — @arbac.role/.attribute/.userId48 │49@aooth/user UserService, password (scrypt), policy, TOTP/HOTP, backup codes, lockout, FederatedIdentityStore (account linking)50 │ └── /atscript-db UsersStoreAtscriptDb + AoothUserCredentials .as model; FederatedIdentityStoreAtscriptDb + AoothFederatedIdentity .as model51 │52 └── @aooth/auth AuthCredential, stores (Memory/JWT/Encapsulated/Redis/AtscriptDb), denylist, magic links53 │ └── /redis CredentialStoreRedis, DenylistStoreRedis54 │ └── /atscript-db CredentialStoreAtscriptDb + AoothAuthCredential .as model55 │ └── /authz authorization-server core: IdTokenSigner, Loopback/Registered/Dynamic/Composite client policies, OidcClaimsResolver, pending/auth-code/dynamic-client stores, DynamicClientRegistration (RFC 7591), RFC 8414/9728 metadata + WWW-Authenticate builders56 │57 ├── @aooth/idp federated-login core (depends on user+auth): OidcProvider/GoogleProvider/FakeIdentityProvider, OAuthProviderRegistry,58 │ FederatedLoginService.resolveUser, PKCE + signState/verifyState, OAuthError. HTTP/workflow wiring is in auth-moost (OAuthController + federated leg of auth/login/flow)59 │60 └── @aooth/auth-moost AuthController, SessionsController, OAuthController, AuthorizeController (authorization server: /auth/authorize + /auth/token + OIDC discovery/JWKS), authGuardInterceptor, @Public, @UserId, useAuth,61 AuthWorkflow (login [+federated sso-callback/prove-control] /invite/recovery/signup/change-password/add-mfa), ConsentStore, WfTriggerProvider6263@aooth/login-client ZERO-dep CLI half of the authorization server: authorize() — browser + PKCE + one-shot loopback callback → token. Depends on nothing (not even @aooth/*)64```6566## Quick start6768```ts69// src/app-user.as70import { AoothArbacUserCredentials } from '@aooth/arbac-moost/atscript/models'7172// id (PK / @meta.id — the token subject), username + email (unique handles),73// version — all inherited. The auth subject IS @meta.id, so the provider's74// default lookup chain resolves to it: NO @arbac.userId annotate needed.75@db.table 'users'76export interface AppUser extends AoothArbacUserCredentials {77 @arbac.attribute78 department?: string79}80```8182```ts83// src/main.ts84import { Moost, createProvideRegistry, createReplaceRegistry } from "moost";85import { MoostHttp } from "@moostjs/event-http";86import { MoostWf } from "@moostjs/event-wf";87import { formInputInterceptor } from "@atscript/moost-wf";88import { DbSpace, syncSchema } from "@atscript/db";89import { SqliteAdapter, BetterSqlite3Driver } from "@atscript/db-sqlite";90import { UserService } from "@aooth/user";91import { UsersStoreAtscriptDb, type AuthUserTable } from "@aooth/user/atscript-db";92import { AuthCredential, CredentialStoreJwt } from "@aooth/auth";93import { AuthController, authGuardInterceptor, AuthWorkflow, useAuth } from "@aooth/auth-moost";94import {95 MoostArbac,96 arbacAuthorizeInterceptor,97 ArbacUserProviderToken,98 type ArbacDbScope,99} from "@aooth/arbac-moost";100import { AtscriptArbacUserProvider, type ArbacUserTable } from "@aooth/arbac-moost/atscript";101import { defineRole, allowTableRead } from "@aooth/arbac";102import { Injectable, getMoostInfact } from "moost";103import { AppUser } from "./app-user.as";104105const db = new DbSpace(() => new SqliteAdapter(new BetterSqlite3Driver(":memory:")));106await syncSchema(db, [AppUser]);107const userStore = new UsersStoreAtscriptDb<AppUser>({108 table: db.getTable(AppUser) as unknown as AuthUserTable<AppUser>,109});110const userService = new UserService<AppUser>(userStore, {111 password: { pepper: process.env.AOOTH_PEPPER ?? "" },112});113const auth = new AuthCredential({114 store: new CredentialStoreJwt({ secret: process.env.AOOTH_JWT_SECRET! }),115 accessTtl: 3_600_000,116 refresh: { ttl: 7 * 24 * 3_600_000, rotation: "always" },117});118@Injectable()119class AppUserProvider extends AtscriptArbacUserProvider<AppUser> {120 constructor() {121 // Provider resolves users by `@meta.id` (= `id` = the auth subject) — no122 // @arbac.userId, no shim. The cast is needed because `AtscriptDbTable.findOne`123 // is typed wider than `ArbacUserTable.findOne` (engine-specific `controls.*`124 // keys); they're structurally compatible at runtime.125 super(AppUser, db.getTable(AppUser) as unknown as ArbacUserTable<AppUser>);126 }127 override getUserId() {128 // Returns the stable `id` — the token `sub` claim `auth.issue(subject)` set.129 return useAuth().getUserId();130 }131}132133const app = new Moost();134app.adapter(new MoostHttp());135app.adapter(new MoostWf());136app.setProvideRegistry(137 createProvideRegistry([UserService, () => userService], [AuthCredential, () => auth]),138);139app.setReplaceRegistry(createReplaceRegistry([ArbacUserProviderToken, AppUserProvider]));140app.applyGlobalInterceptors(141 authGuardInterceptor(),142 arbacAuthorizeInterceptor,143 formInputInterceptor(),144);145app.registerControllers(AuthController);146await app.init();147148// Grab the singleton MoostArbac from moost's IoC container and register roles.149const arbac = (await getMoostInfact().get(MoostArbac)) as MoostArbac<150 { department?: string },151 ArbacDbScope152>;153arbac.registerRole(defineRole().id("reader").use(allowTableRead("articles")).build());154```155156## Invariants157158Engine-internals — see [references/invariants.md](references/invariants.md) for the full 18-row table covering deny-wins, scope-union sentinels, refresh-rotation degradation, moost@0.6.x DI quirks, and the dual-purpose `@Public()`. Load when debugging silent-deny / refresh / scope-merge issues.159160## Key imports161162```ts163// — @aooth/user164import {165 UserService,166 UserStore,167 UserStoreMemory,168 PasswordHasher,169 PasswordPolicy,170 definePasswordPolicy,171 normalizePolicies,172 ppHasMinLength,173 ppHasUpperCase,174 ppHasLowerCase,175 ppHasNumber,176 ppHasSpecialChar,177 ppMaxRepeatedChars,178 generateTotpSecret,179 generateTotpUri,180 generateTotpCode,181 verifyTotpCode,182 generateMfaCode,183 hashMfaCode,184 verifyMfaCode,185 maskEmail,186 maskPhone,187 maskMfaValue,188 setAtPath,189 UserAuthError,190} from "@aooth/user";191import type {192 UserCredentials,193 PasswordData,194 AccountData,195 MfaData,196 MfaMethod,197 UserServiceConfig,198 PasswordConfig,199 LockoutConfig,200 PasswordPolicyDef,201 PasswordPolicyInstance,202 UserStoreUpdate,203 DeepPartial,204 LoginResult,205 LockStatus,206 PolicyCheckResult,207 TransferablePolicy,208 MfaMethodInfo,209 TotpConfig,210 TrustedDeviceRecord,211 UserAuthErrorType,212} from "@aooth/user";213214// — @aooth/user/atscript-db215import { UsersStoreAtscriptDb } from "@aooth/user/atscript-db";216import type { UserCredentialsRow, AuthUserTable } from "@aooth/user/atscript-db";217import { AoothUserCredentials } from "@aooth/user/atscript-db/model.as";218219// — @aooth/arbac (re-exports @aooth/arbac-core)220import {221 Arbac,222 arbacPatternToRegex,223 defineRole,224 definePrivilege,225 allowTableRead,226 allowTableWrite,227 allowTableAction,228 mergeScopeFilters,229 unionProjections,230 restrictProjection,231 getProjectionMode,232 isFieldAllowed,233 unionControlsPolicy,234 extractResourceActions,235 generateResourceTypes,236} from "@aooth/arbac";237import type {238 TArbacRole,239 TArbacRule,240 TArbacEvalResult,241 RoleBuilder,242 TPrivilegeFunction,243 TProjection,244 TProjectionMode,245 TScopeFilter,246 ControlGate,247 TCodegenOptions,248 TResourceActionMap,249} from "@aooth/arbac";250251// — @aooth/auth + subpaths252import {253 AuthCredential,254 AuthError,255 CredentialStoreMemory,256 CredentialStoreJwt,257 CredentialStoreEncapsulated,258 DenylistStoreMemory,259 generateMagicLinkToken,260 generateOpaqueToken, // the shared CSPRNG mint (magic links / client secrets / one-shot tokens)261 defaultClock,262} from "@aooth/auth";263import type {264 AuthContext,265 CredentialMetadata, // framework keys: credentialKind, authzClientId, accessTtl, refreshRotation266 CredentialState,267 IssueResult,268 RefreshResult, // refresh()'s return — IssueResult + userId269 IssueOptions, // per-mint ttl/expiresAt/kind/refresh (refresh: false | { ttl? })270 RefreshCallOptions, // refresh(token, { guard }) — pre-rotation gate271 RefreshConfig,272 CredentialStore,273 DenylistStore,274 SessionInfo,275 EnrichedSession,276 SessionEnricher,277 EmailSender,278 AuthEmailEvent,279 AuthEmailKind,280 SmsSender,281 AuthSmsEvent,282 AuthSmsKind,283 BuildMagicLinkUrl,284 Clock,285 AuthErrorType,286} from "@aooth/auth";287import { CredentialStoreRedis, DenylistStoreRedis } from "@aooth/auth/redis";288import { CredentialStoreAtscriptDb } from "@aooth/auth/atscript-db";289import type { AuthCredentialRow, AuthCredentialTable } from "@aooth/auth/atscript-db";290import { AoothAuthCredential } from "@aooth/auth/atscript-db/model.as";291292// — @aooth/idp (federated login — see references/idp.md for the full surface)293import {294 OidcProvider,295 GoogleProvider,296 FakeIdentityProvider,297 OAuthProviderRegistry,298 FederatedLoginService,299 createPkcePair,300 generateNonce,301 signState,302 verifyState,303 resolveFederatedPolicy,304 OAuthError,305} from "@aooth/idp";306import type {307 IdentityProvider,308 NormalizedProfile,309 FederatedPolicy,310 ResolveOutcome,311 OidcProviderOptions,312 OAuthProviderRegistryOptions,313 FederatedLoginServiceDeps,314 OAuthStatePayload,315 OAuthErrorType,316} from "@aooth/idp";317// account-linking store ships in @aooth/user (NOT @aooth/idp):318import {319 FederatedIdentityStore,320 FederatedIdentityStoreMemory,321 pickDefinedProfile,322} from "@aooth/user";323import type {324 FederatedIdentity,325 NewFederatedIdentity,326 FederatedProfileSnapshot,327} from "@aooth/user";328import { FederatedIdentityStoreAtscriptDb } from "@aooth/user/atscript-db";329330// — @aooth/auth-moost331import {332 AuthController,333 authGuardInterceptor,334 AuthGuarded,335 Public,336 UserId,337 useAuth,338 getAuthMate,339 AuthWorkflow,340 ConsentStore,341 SessionsController,342 SessionEnricherProvider,343 deriveWfStateSecret,344 WfTrigger,345 WfTriggerProvider,346 createAuthEmailOutlet,347 DEFAULT_AUTH_WORKFLOWS,348 buildInviteAlreadyAcceptedEnvelope,349 parseInviteRoles,350 stripReservedUserKeys,351 RESERVED_USER_KEYS,352 haversineKm,353 humanizeUserAgent,354} from "@aooth/auth-moost";355import type {356 AuthOptions,357 ResolvedAuthOptions,358 ResolvedAuthCookieConfig,359 AuthBindings,360 AuthLoginResponse,361 AuthLogoutBody,362 AuthRefreshBody,363 AuthOkResponse,364 AuditEvent,365 AuditEmitter,366 AuthDeliveryPayload,367 AuthWorkflowOpts,368 ResolvedAuthWorkflowOpts,369 AuthWfCtx,370 ConsentDescriptor,371 ConsentEvent,372 WfTriggerOpts,373} from "@aooth/auth-moost";374375// — @aooth/arbac-moost + subpaths376import {377 MoostArbac,378 arbacAuthorizeInterceptor,379 ArbacResource,380 ArbacAction,381 ArbacAuthorize,382 useArbac,383 ArbacUserProvider,384 ArbacUserProviderToken,385 AsArbacDbController,386 AsArbacDbReadableController,387} from "@aooth/arbac-moost";388import type { TArbacMeta, ArbacBindings, ArbacDbScope } from "@aooth/arbac-moost";389import { AtscriptArbacUserProvider } from "@aooth/arbac-moost/atscript";390import type { ArbacUserTable } from "@aooth/arbac-moost/atscript";391import { AoothArbacUserCredentials } from "@aooth/arbac-moost/atscript/models";392import arbacPlugin from "@aooth/arbac-moost/plugin";393394// — @aooth/login-client (CLI side — zero deps, see references/login-client.md)395import { authorize, AuthorizeError } from "@aooth/login-client";396import type { AuthorizeOptions, AuthorizeResult, AuthorizeErrorCode } from "@aooth/login-client";397```398399## References — load only what's needed400401| Domain | File | When |402| ------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |403| First contact | [getting-started.md](references/getting-started.md) | Install matrix, minimum wiring (with + without moost), atscript-db wiring, choosing token/user stores, testing patterns |404| Ecosystem map | [ecosystem.md](references/ecosystem.md) | Package responsibility matrix, dep graph, peer-dep requirements, subpath export map |405| Annotation reference | [annotations.md](references/annotations.md) | Every `@arbac.*` annotation + how aoothjs reads `@db.*` / `@meta.*` / `@ui.form.*` / `@expect.*` / `@wf.*` from atscript |406| **User domain** | [user.md](references/user.md) | `@aooth/user` overview: `UserService` quick start, full invariants table, key imports |407| `UserService` reference | [user-service.md](references/user-service.md) | Every public method, config defaults, login flow, lockout, MFA methods, backup codes, trusted devices, seen-device recognition ledger, correspondence email (`setVerifiedEmail` / `getCorrespondenceEmail`) |408| Password subsystem | [password.md](references/password.md) | Scrypt + pepper + history, `generatePassword`, `PasswordPolicy` DSL, transferable policies, built-in `ppHas*` factories |409| MFA primitives | [mfa.md](references/mfa.md) | TOTP secret/URI/code/verify, MFA-code helpers, backup codes, trusted-device tokens |410| User stores | [user-stores.md](references/user-stores.md) | `UserStore` contract, `UserStoreMemory`, custom-store skeleton, `UsersStoreAtscriptDb` wiring |411| **ARBAC domain** | [arbac.md](references/arbac.md) | `@aooth/arbac` + `arbac-core` overview: quick start, full invariants, key imports |412| Engine + builder | [builder.md](references/builder.md) | `Arbac` class, `defineRole` chain, `definePrivilege` double-call, `allowTable*` helpers + action vocabulary |413| Scope merging | [scopes.md](references/scopes.md) | `ArbacDbScope` shape, `mergeScopeFilters`, `unionProjections` truth table, `restrictProjection`, `unionControlsPolicy`; attenuation conjunction (`conjoinScopeFilters` / `intersectControlsPolicy` / `conjoinArbacDbScopes` / `extractAttenuation` — scoped tokens / PATs) |414| Codegen | [codegen.md](references/codegen.md) | Library API + CLI: `extractResourceActions`, `generateResourceTypes`, `aoothjs-arbac-codegen --roles ... --output ...` |415| **Auth domain** | [auth.md](references/auth.md) | `@aooth/auth` overview: quick start, full invariants, key imports |416| Tokens & sessions | [tokens.md](references/tokens.md) | `CredentialStoreJwt` algorithms, claim layout, `CredentialStoreEncapsulated`, sessions vs tokens |417| Refresh & rotation | [refresh.md](references/refresh.md) | `RefreshConfig`, three rotation modes, per-mint `IssueOptions.refresh` (the `refresh()` guard, `RefreshResult`, `metadata.refreshRotation`/`accessTtl` stamps), reuse detection, stateless degradation, `maxConcurrent`, epoch revocation |418| Client (silent refresh) | [client.md](references/client.md) | `@aooth/auth/client` browser subpath: `createAuthedFetch` — credentials forwarding, single-flight `/auth/refresh` on 401, retry-once, `onLogout`, status probe |419| Magic links | [magic-links.md](references/magic-links.md) | `generateMagicLinkToken`, single-use guarantees, stateless `DenylistStore` requirement, recovery recipe |420| Auth stores | [auth-stores.md](references/auth-stores.md) | `CredentialStore` + `DenylistStore` contracts, Memory / Redis / atscript-db, shipped `AoothAuthCredential` model |421| Sessions / devices | [sessions.md](references/sessions.md) | Active-sessions screen: `sessionId` token-family, `listSessions` / `revokeSession` / `revokeOtherSessions`, `SessionEnricher`, `trackLastSeen`, `SessionsController` + `useAuth()` facade, `getSessionId` 422423…(truncated)