Tesserae
e‑ink 디스플레이용 무료 오픈소스 대시보드 서버, 브라우저에서 대시보드 구성·프레임 렌더링·MQTT·REST 전송, 자체 호스팅·추적 없음
NestJS @nestjs/authorization 위에 추가 기능을 제공해요
Extras on top of NestJS's first-party @nestjs/authorization — persisted RBAC as official policies, @Can helpers, a service-layer Gate, super-admin, external engines and query scopes.
개발 도구TypeScript · MDXGitHub @DavideCarvalho가동 상태 재확인 필요운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Laravel-style authorization for NestJS — gates, policies, @Can guard, and opt-in TypeORM RBAC. Pairs with nestjs-context; part of the @dudousxd/nestjs-* ecosystem.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗@dudousxd/nestjs-authzExtras on top of NestJS's first-party @nestjs/authorization.
Policies, @Can(), the global deny-by-default guard and AuthorizationService come from the
official package: read its docs first. This package adds what it does not have:
RolePolicy and PermissionPolicy (wildcards,
tenant-scoped roles, direct permissions, role sources), backed by the TypeORM / MikroORM /
Prisma / Drizzle adapters, fetched once per request.@Can(): byId(Post), roles(...), permission(...),
ability(...), staticCanArgs(...); sugar @Roles(...) / @Permission(...).AuthzPolicy.Gate for services: string abilities, batches (allowsMany), ad-hoc gates
(define), roles, query scopes (scope + the adapters' applyScope).can() from codegen, client/React
helpers, test helpers, and an allow + deny decision event for Telescope.See DESIGN.md for the design and the trade-offs.
pnpm add @nestjs/authorization @dudousxd/nestjs-authz
Requires NestJS 12 and Node 22+. Two optional peers: @nestjs/authentication gives the Gate
its ambient user (AuthenticationContext.user); @dudousxd/nestjs-context gives it one too
(outside an authentication scope), plus the per-request permission cache and
tenant-scoped roles.
@Module({
imports: [
AuthorizationModule.forRoot(), // official: guard, @Can, AuthorizationService
AuthzModule.forRoot({ superAdmin: (user) => user?.isRoot === true }),
],
providers: [PostPolicy],
})
export class AppModule {}
@PolicyForimport { Can, Policy } from '@nestjs/authorization';
import { AuthzPolicy, PolicyFor, authorizedResource, byId } from '@dudousxd/nestjs-authz';
@Policy()
@PolicyFor(Post) // optional: links Post → PostPolicy for the extras
export class PostPolicy extends AuthzPolicy { // optional: honors `superAdmin`
update(user: User | null, post: Post) {
return post.authorId === user?.id;
}
}
@Controller('posts')
export class PostController {
@Patch(':id')
@Can(PostPolicy, 'update', byId(Post)) // byId loads the Post named by :id
update(@Req() req: Request) {
const post = authorizedResource(req, Post); // the record byId loaded, no second query
}
}
byId uses the ResourceResolver given to AuthzModule.forRoot({ resourceResolver })
(default: an { id } object from the route param; idParam renames it, byId(Post, { param })
per route). A record that is not found is a 403 before the policy runs.
@PolicyFor(Post) is what lets the extras go from a resource to its policy:
gate.allows('update', post), gate.scope(Post), the client ability map. The official API
never needs it, and @PolicyFor applies the official @Policy() itself.
@Roles('admin', 'editor') // any of
@Can(RolePolicy, 'hasAll', roles('admin', 'editor')) // all of
@Permission('posts.publish') // any of; posts.* grants it
@Can(PermissionPolicy, 'hasAll', permission('posts.publish', 'posts.delete'))
Roles come from user.roles / user.role (or resolveRoles) and from the RBAC adapter's role
store; permissions from the adapter:
AuthzRbacModule.forRootAsync({
inject: [DataSource],
useFactory: (ds: DataSource) => ({ store: new TypeOrmAuthzStore(ds) }),
});
Within one request (a nestjs-context store), a user's permissions and the adapter's roles are
fetched once, however many checks run: the official guard's RolePolicy / PermissionPolicy
and the handler's Gate calls share the fetch. The entry is keyed by the row the user names
(its class or type, plus id); a plain object without type is keyed by identity. A grant
made mid-request applies from the next request. Adapters set exhaustivePermissions, so a
permission the set lacks is answered without another query.
roles(), permission(), ability() and staticCanArgs() return resolvers whose arguments do
not depend on the call. They carry them on a non-enumerable Symbol.for('@dudousxd/static-can-args')
property, so tools such as the Aviary agent can evaluate a requirement without a request.
The official guard runs only each policy's own before(). Extend AuthzPolicy (or call
superAdminBefore(user, ability, resource) from your before()) and the superAdmin hook
grants every ability of that policy. The hook receives the resource (the ability's first
argument), so it can scope the bypass. RolePolicy and PermissionPolicy extend it.
Gateconstructor(private readonly gate: Gate) {}
await this.gate.forUser(user).authorize(PostPolicy, 'update', post); // = AuthorizationService.authorize
await this.gate.forUser(user).allows('update', post); // the policy linked to Post
await this.gate.forUser(user).allows('posts.publish'); // a persisted permission
this.gate.define('access-admin', (user) => user.isStaff);
// on a route: @Can(GatePolicy, 'allows', ability('access-admin'))
const results = await this.gate.forUser(user).allowsMany([
{ ability: 'update', resource: post },
{ policy: ReportPolicy, ability: 'export', args: ['csv'] },
]); // one permission fetch
const where = await this.gate.forUser(user).scope(Post); // from PostPolicy.scope(user)
여기까지가 README 앞부분입니다. 나머지는 원문에서 읽을 수 있습니다. README 전문 보기 ↗