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/.

معلومات المصدر

المستودع
stacksjs/commshq
آخر نشاط في المصدر
٢٨ سبتمبر ٢٠٢٦ في ١٧:١٩
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٠
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub