Role- and attribute-based access control, native to moost + atscript. Sessions, tokens, MFA, and magic links wired in
Scrypt with self-describing hashes, salt + pepper, history checks. Password policies expressed as serializable rules — same predicate runs on server and client. TOTP, backup codes, trusted devices. All behind a pluggable store.
Explore @aooth/user →import { UserService } from "@aooth/user";
import {
UsersStoreAtscriptDb,
type AuthUserTable,
} from "@aooth/user/atscript-db";
import {
ppHasMinLength,
ppHasUpperCase,
ppHasNumber,
} from "@aooth/user";
const users = new UserService(
new UsersStoreAtscriptDb({
table: db.getTable(AppUser) as unknown as AuthUserTable,
}),
{
password: {
pepper: process.env.PEPPER!,
historyLength: 5,
policies: [ppHasMinLength(12), ppHasUpperCase(1), ppHasNumber(1)],
},
lockout: { threshold: 5, duration: 15 * 60_000 },
},
);
await users.createUser("alice@app.dev", "P4ssphrase!");
const { user, mfaRequired } = await users.login(
"alice@app.dev",
"P4ssphrase!",
);Roles + privileges + dynamic scopes. Wildcard matchers (* single-segment, ** any depth), deny-wins evaluation. Each allow rule can return a SQL or Mongo filter — a single grant narrows queries automatically. Multi-role unions merge to $in or $or.
Explore @aooth/arbac →import {
Arbac,
defineRole,
allowTableRead,
allowTableWrite,
} from "@aooth/arbac";
type Attrs = { tenantId: string; department: string };
type Scope = { filter?: { tenantId?: string; department?: string } };
const manager = defineRole<Attrs, Scope>()
.id("com.role.manager")
.use(
allowTableWrite("articles", {
scope: (a) => ({ filter: { department: a.department } }),
}),
)
.use(allowTableRead("reports"))
.deny("articles", "publish")
.build();
const arbac = new Arbac<Attrs, Scope>();
arbac.registerRole(manager);
const result = await arbac.evaluate(
{ resource: "articles", action: "update" },
{ id: "u1", roles: ["com.role.manager"], attrs },
);
// → { allowed: true, scopes: [{ filter: { department: "sales" } }] }Stateful or stateless — same API. JWT via jose, encapsulated AES-256-GCM, in-memory, Redis, atscript-db. Sliding refresh with grace window and reuse detection. Magic links with atomic single-use guarantees. Per-user epoch revocation.
Explore @aooth/auth →import {
AuthCredential,
CredentialStoreJwt,
DenylistStoreMemory,
} from "@aooth/auth";
const auth = new AuthCredential<{ roles: string[] }>({
store: new CredentialStoreJwt({
algorithm: "HS256",
secret: process.env.JWT_SECRET!,
denylist: new DenylistStoreMemory(),
}),
accessTtl: 60 * 60_000,
refresh: {
ttl: 30 * 24 * 3600_000,
rotation: "sliding",
rotationGraceMs: 30_000,
onRotationReuse: (state) => log.warn("refresh reuse", state),
},
});
const issued = await auth.issue("alice", {
claims: { roles: ["admin"] },
metadata: { ip: req.ip },
});
// On every request:
const ctx = await auth.validate(issued.accessToken);
// ctx → { userId, method, credentialId, expiresAt, claims }AuthGuard interceptor, useAuth and useArbac composables, an AuthController with a single /trigger entry-point covering three batteries-included workflows: login, recovery, invite. Each pauses for forms and emits a unified WfFinished envelope to the client.
Explore @aooth/*-moost →import {
AuthController,
authGuardInterceptor,
Public,
UserId,
LoginWorkflow,
type LoginWorkflowOpts,
type DeliverPayload,
} from "@aooth/auth-moost";
import {
ArbacAuthorize,
ArbacResource,
ArbacAction,
arbacAuthorizeInterceptor,
} from "@aooth/arbac-moost";
import type { AuthCredential } from "@aooth/auth";
import type { UserService } from "@aooth/user";
import { Controller, Inherit, Injectable } from "moost";
import { Get } from "@moostjs/event-http";
app.applyGlobalInterceptors(authGuardInterceptor());
app.applyGlobalInterceptors(arbacAuthorizeInterceptor);
app.registerControllers(AuthController, MyLoginWorkflow, ReportsController);
@Controller("reports")
@ArbacResource("reports")
class ReportsController {
@Get(":id")
@ArbacAction("read")
@ArbacAuthorize()
async read(@UserId() userId: string) {
return { userId };
}
}
@Inherit() @Injectable("FOR_EVENT") @Controller()
class MyLoginWorkflow extends LoginWorkflow {
// Subclasses MUST re-declare the ctor — TS emits fresh design-paramtypes per class.
constructor(opts: LoginWorkflowOpts, users: UserService, auth: AuthCredential) {
super(opts, users, auth);
}
protected override async deliver(payload: DeliverPayload) {
if (payload.channel === "email") await emailSender.send(payload);
}
} One command teaches Claude Code, Cursor, Windsurf, and Codex the entire aoothjs stack — UserService, AuthCredential, defineRole, AsArbacDbController, the workflow envelope, and the shipped .as models.
UserService · password · MFA primitivesdefineRole · allowTableRead · scope merging AuthCredential · stores · magic links