Skip to main content

stacks-auth

Use when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.

Informations de source

Dépôt
stacksjs/commshq
Dernière activité de la source
28 septembre 2026 à 17:19
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
stacks-auth
description
Use when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks Authentication & Authorization The `@stacksjs/auth` package provides comprehensive authentication and authorization for Stacks applications, built on `@stacksjs/ts-auth`. ## Key Paths - Core package source: `storage/framework/core/auth/src/` - Configuration: `config/auth.ts` - Security config: `config/security.ts` - Hashing config: `config/hashing.ts` - Application gates: `app/Gates.ts` - Application middleware: `app/Middleware/` - Middleware aliases: `app/Middleware.ts` - Auth types: `storage/framework/core/types/src/auth.ts` ## Source Files ``` auth/src/ ├── index.ts # All re-exports ├── authentication.ts # Auth class - core auth logic ├── authenticator.ts # 2FA and personal access client ├── client.ts # Client re-exports ├── middleware.ts # Auth middleware handler ├── rate-limiter.ts # RateLimiter class (5 attempts, 15min lockout) ├── passkey.ts # WebAuthn/Passkey support ├── password/reset.ts # Password reset flow ├── register.ts # User registration ├── user.ts # Auth user helpers ├── tokens.ts # Token CRUD, scopes, refresh tokens, OAuth clients ├── gate.ts # Authorization gates & policies ├── policy.ts # BasePolicy class + discovery ├── authorizable.ts # User authorization mixin ├── rbac.ts # Full RBAC system ├── email-verification.ts # Email verification flow └── session-auth.ts # Session-based SPA auth ``` ## Auth Class (authentication.ts) — Static Methods ### Login & Authentication - `Auth.attempt(credentials: AuthCredentials): Promise<boolean>` — validate credentials without creating token - `Auth.validate(credentials: AuthCredentials): Promise<boolean>` — alias for attempt - `Auth.login(credentials: AuthCredentials, options?: TokenCreateOptions): Promise<{ user, token } | null>` — login and create token - `Auth.loginUsingId(userId: number, options?: TokenCreateOptions): Promise<{ user, token } | null>` — login by user ID - `Auth.logout(): Promise<void>` — revoke current token ### Personal access tokens (Sanctum-shaped) `oauth_access_tokens` is polymorphic: `tokenable_type` holds the owner's TABLE name (`users`, `authors`) and `tokenable_id` its id there, so any model declaring `useAuth` can hold tokens - not only `User`. - `createToken(id, name, scopes, { tokenableType })` — mint one. Returns the plaintext ONCE (`plainTextToken`); the table stores a hash and nothing can recover it afterwards. `tokenableType` defaults to `users`. - `tokens(id, tokenableType?)` — list an owner's live tokens. - `tokenCan(scope)` / `tokenCanAll` / `tokenCanAny` / `tokenAbilities` — check the current request's token. - `revokeToken`, `revokeTokenById`, `revokeAllTokens(id, type?)`, `revokeOtherTokens(id, type?)` — revocation also revokes the paired refresh token, which a raw row delete does not. - `setTrailActor(id)` — attribute writes in a queue job or CLI run that has no request to read a user from. The `PersonalAccessToken` model maps the same table, so `owner.with('tokenable')` lists exactly what `createToken` minted. It deliberately generates no CRUD routes: minting and revoking both carry semantics a generic route does not. - `Auth.once(credentials: AuthCredentials): Promise<boolean>` — one-time auth without token - `Auth.requestUserTokenWithClient(credentials, clientId, clientSecret): Promise<{ token } | null>` authenticates both a legacy OAuth client and an end user password, then issues a user token. It is not the client credentials grant. - `Auth.requestToken(...)` is the deprecated compatibility alias for that same legacy exchange. New delegated integrations use the authorization-code provider. ### User State - `Auth.user(): Promise<UserModel | undefined>` — get authenticated user from bearer token - `Auth.check(): Promise<boolean>` — is user authenticated? - `Auth.guest(): Promise<boolean>` — is user a guest? - `Auth.id(): Promise<number | undefined>` — get authenticated user ID - `Auth.setUser(user: UserModel): void` — manually set user ### Token Creation - `Auth.createTokenForUser(user, options?: TokenCreateOptions): Promise<NewAccessToken>` - `Auth.createToken(user, name?, abilities?): Promise<AuthToken>` ### Token Validation - `Auth.validateToken(token: string): Promise<boolean>` — validate bearer token - `Auth.getUserFromToken(token: string): Promise<UserModel | undefined>` - `Auth.currentAccessToken(): Promise<PersonalAccessToken | undefined>` ### Token Abilities (Scopes) - `Auth.tokenCan(ability: string): Promise<boolean>` - `Auth.tokenCant(ability: string): Promise<boolean>` - `Auth.tokenAbilities(): Promise<string[]>` - `Auth.tokenCanAll(abilities: string[]): Promise<boolean>` - `Auth.tokenCanAny(abilities: string[]): Promise<boolean>` ### Token Management - `Auth.tokens(userId?: number): Promise<PersonalAccessToken[]>` - `Auth.revokeToken(token: string): Promise<void>` - `Auth.revokeTokenById(tokenId: number): Promise<void>` - `Auth.revokeAllTokens(userId?: number): Promise<void>` - `Auth.revokeOtherTokens(userId?: number): Promise<void>` - `Auth.pruneExpiredTokens(): Promise<number>` - `Auth.pruneRevokedTokens(): Promise<number>` - `Auth.rotateToken(oldToken: string): Promise<AuthToken | null>` - `Auth.findToken(tokenId: number): Promise<PersonalAccessToken | null>` ### Utility - `Auth.guard(name?: string): typeof Auth` — select guard (returns self) - `Auth.viaRemember(): boolean` — always false currently - `Auth.clearState(): void` — clear cached user/token ## Token System (tokens.ts) ### Access Tokens - `tokens(userId: number): Promise<AccessToken[]>` - `findToken(plainTextToken: string): Promise<AccessToken | null>` - `currentAccessToken(): Promise<AccessToken | null>` - `createToken(userId, name?, scopes?, options?): Promise<PersonalAccessTokenResult>` - Options: `{ expiresInMinutes?, withRefreshToken?, refreshExpiresInDays? }` ### Refresh Tokens - `refreshToken(refreshTokenPlain, options?): Promise<RefreshTokenResult>` - `validateRefreshToken(refreshTokenPlain): Promise<boolean>` - `revokeRefreshToken(refreshTokenPlain): Promise<void>` - `revokeAllRefreshTokens(userId): Promise<void>` - `deleteExpiredRefreshTokens(): Promise<number>` - `deleteRevokedRefreshTokens(daysOld?): Promise<number>` ### Token Revocation - `revokeToken(plainTextToken): Promise<void>` - `revokeTokenById(tokenId): Promise<void>` - `revokeAllTokens(userId): Promise<void>` - `revokeOtherTokens(userId): Promise<void>` - `deleteExpiredTokens(): Promise<number>` - `deleteRevokedTokens(daysOld?): Promise<number>` ### Token Scopes - `tokenCan(scope): Promise<boolean>` - `tokenCant(scope): Promise<boolean>` - `tokenCanAll(scopes): Promise<boolean>` - `tokenCanAny(scopes): Promise<boolean>` - `tokenAbilities(): Promise<string[]>` - `parseScopes(scopes: string | string[] | null | undefined): TokenScopes` ### OAuth Clients - `clients(userId): Promise<OAuthClient[]>` - `findClient(clientId): Promise<OAuthClient | null>` - `createClient(options: CreateClientOptions): Promise<CreateClientResult>` - `revokeClient(clientId): Promise<void>` ### OAuth Provider and PKCE The authorization server is opt-in through `config/auth.ts` under `oauthProvider`. It is separate from social sign-in, where Stacks is the OAuth client. The provider profile is Authorization Code with S256 PKCE and rotating refresh tokens. It does not support implicit or password grants. - `resolveOAuthProviderConfig(options)` returns `null` unless explicitly enabled - `generatePkceVerifier()` creates a 256-bit RFC 7636 verifier - `createS256CodeChallenge(verifier)` derives its S256 challenge - `verifyS256CodeChallenge(verifier, challenge)` validates without a plain fallback - `isValidPkceVerifier(value)` checks the required 43 to 128 character syntax ## Two-Factor Authentication (authenticator.ts) - `generateTwoFactorSecret(): string` - `generateTwoFactorToken(secret: Secret): Promise<Token>` - `verifyTwoFactorCode(token: Token, secret: Secret): Promise<boolean>` - `generateTwoFactorUri(user?, service?, secret?): string` - `createPersonalAccessClient(): Promise<Result<string, never>>` ### Re-exported from @stacksjs/ts-auth - `generateTOTP`, `verifyTOTP`, `generateTOTPSecret`, `totpKeyUri` ## Authorization Gates (gate.ts) ### Gate Functions - `define<T>(ability: string, callback: GateCallback<T>): void` - `policy(model: string | { name }, policyClass: new () => Policy): void` - `before(callback): void` — run before any gate check - `after(callback): void` — run after any gate check - `allows(ability, user, ...args): Promise<boolean>` - `denies(ability, user, ...args): Promise<boolean>` - `can(ability, user, ...args): Promise<boolean>` - `cannot(ability, user, ...args): Promise<boolean>` - `any(abilities[], user, ...args): Promise<boolean>` - `all(abilities[], user, ...args): Promise<boolean>` - `none(abilities[], user, ...args): Promise<boolean>` - `authorize(ability, user, ...args): Promise<AuthorizationResponse>` — throws on deny - `inspect(ability, user, ...args): Promise<AuthorizationResponse>` — never throws - `has(ability): boolean` - `hasPolicy(model): boolean` - `abilities(): string[]` - `getPolicyFor<T>(model: T): Policy<T> | null` - `flush(): void` — clear all gates ### Gate Facade — `Gate.define()`, `Gate.can()`, etc ### AuthorizationResponse Class - `static allow(message?): AuthorizationResponse` - `static deny(message?, code?): AuthorizationResponse` - `allowed(): boolean`, `denied(): boolean` - `authorize(): void` — throws AuthorizationException if denied ### Policy Interface Methods: `before?`, `viewAny?`, `view?`, `create?`, `update?`, `delete?`, `restore?`, `forceDelete?` ### BasePolicy Abstract Class Protected helpers: `allow(message?)`, `deny(message?, code?)`, `denyIf(condition)`, `denyUnless(condition)`, `allowIf(condition)` ## RBAC System (rbac.ts) ### Role Management - `Rbac.createRole(name, guardName?, description?): Promise<RoleRecord>` - `Rbac.findRole(name, guardName?): Promise<RoleRecord | null>` - `Rbac.deleteRole(name, guardName?): Promise<void>` - `Rbac.getAllRoles(guardName?): Promise<RoleRecord[]>` ### Permission Management - `Rbac.createPermission(name, guardName?, description?): Promise<PermissionRecord>` - `Rbac.findPermission(name, guardName?): Promise<PermissionRecord | null>` - `Rbac.deletePermission(name, guardName?): Promise<void>` - `Rbac.getAllPermissions(guardName?): Promise<PermissionRecord[]>` ### User-Role Operations - `Rbac.getUserRoles(user): Promise<RoleRecord[]>` - `Rbac.assignRole(user, roleName, guardName?): Promise<void>` - `Rbac.removeRole(user, roleName, guardName?): Promise<void>` - `Rbac.removeAllRoles(user): Promise<void>` - `Rbac.syncRoles(user, roleNames[], guardName?): Promise<void>` - replaces assignments for that guard and preserves roles from other guards - `Rbac.hasRole(user, roleName, guardName?): Promise<boolean>` - `Rbac.hasAnyRole(user, roleNames[], guardName?): Promise<boolean>` - `Rbac.hasAllRoles(user, roleNames[], guardName?): Promise<boolean>` ### User-Permission Operations - `Rbac.getUserPermissions(user): Promise<PermissionRecord[]>` - `Rbac.givePermission(user, permissionName, guardName?): Promise<void>` - `Rbac.revokePermission(user, permissionName, guardName?): Promise<void>` - `Rbac.revokeAllPermissions(user): Promise<void>` - `Rbac.syncPermissions(user, permissionNames[], guardName?): Promise<void>` - `Rbac.hasPermission(user, permissionName, guardName?): Promise<boolean>` - `Rbac.hasAnyPermission(user, permissionNames[], guardName?): Promise<boolean>` - `Rbac.hasAllPermissions(user, permissionNames[], guardName?): Promise<boolean>` ### Role-Permission Operations - `Rbac.getRolePermissions(roleId): Promise<PermissionRecord[]>` - `Rbac.givePermissionToRole(roleName, permissionName, guardName?): Promise<void>` - `Rbac.revokePermissionFromRole(roleName, permissionName, guardName?): Promise<void>` - `Rbac.syncRolePermissions(roleName, permissionNames[], guardName?): Promise<void>` ### withRbac Mixin
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub