npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ataccess

v0.0.1

Published

Standalone RBAC/ACL authorization engine with URN-based permissions

Readme

ataccess

Authoritative contract for the ataccess authorization library.
This README is the single source of truth for responsibilities, guarantees, public API, and integration rules. When it conflicts with any other document, this README overrides.


1. Overview

What ataccess is

  • A standalone authorization library for Node.js and for browser bundles (same public API; used by SPAs for client-side checks) that implements:

    • RBAC (role-based access control)
    • HRBAC (hierarchical roles with inheritance)
    • URN-based ACL (permissions as resource:action:target with wildcards)
    • ABAC (attribute-based conditions on permissions when a resource context is provided)
    • Target semantics for :own (resource owner) and :tenant (resource in user’s tenant)
  • It answers one question: “Is this user allowed to perform this action on this resource (or target)?” by:

    • Resolving the user’s effective permissions (roles + direct permissions)
    • Matching a required URN against those permissions
    • Applying target rules (:own, :tenant) and optional ABAC conditions when a resource object is provided

What problems it solves

  • Centralized authorization logic: One place to define and evaluate permissions.
  • Deterministic decisions: Same inputs always yield the same allow/deny result (subject to config).
  • URN-based permissions: Consistent, parseable format (resource:action:target) with wildcard matching.
  • Multi-tenancy and ownership: Built-in handling for “own” and “tenant” targets when resource context is supplied.
  • Extensible audit: Optional auditLogger hook for logging access decisions.

What it explicitly does NOT do

  • Authentication: It does not identify users or validate tokens. The consumer must supply a User (or null).
  • Persistence: It does not store roles or permissions. The consumer registers roles (and optional permissions) in memory or from their own store.
  • Policy resolution from external systems: It does not fetch policies from external services. All decisions are driven by in-memory roles and permission definitions.
  • Implicit grants: Access is never granted by any path other than an explicit matching permission (or the explicit defaultAllow config).

2. Responsibilities & Guarantees

Authorization scope

  • ataccess is responsible only for:
    • Resolving a user’s effective permissions from roles and direct permissions.
    • Matching a required URN against those permissions (with defined wildcard and normalization rules).
    • Applying :own and :tenant checks when a resource object is provided.
    • Evaluating ABAC conditions on permission definitions when a resource is provided.
  • It is not responsible for: authentication, session management, rate limiting, or business logic beyond the above.

Deterministic behavior

  • For a given (user, requiredUrn, resource?) and engine configuration (including registered roles and permissions), the result of check / checkSync is deterministic.
  • URN normalization (e.g. lowercase, trim) is deterministic and applied consistently for matching.

Security invariants (non-negotiable)

These invariants must never be weakened. They define the security semantics of the library.

  1. Deny by default
    If no permission matches the required URN, or the user is null/undefined, the result is deny unless the engine is configured with defaultAllow === true. For security-critical use, configure defaultAllow: false.

  2. URN semantics

    • Permissions and required URNs have exactly three segments: resource:action:target.
    • Matching is component-wise; * in a segment matches any value in that segment.
    • Normalization is deterministic and documented (see URN utilities).
  3. :own and :tenant target semantics

    • For target own: access is allowed only if the resource’s owner matches the user. Owner is resolved in this order: userIdownerIdcreatedBy (first present on the resource object). No other field order is supported.
    • For target tenant: access is allowed only if resource.tenantId matches user.tenantId.
    • These field orders and semantics are fixed; changing them would be a breaking change.
  4. Role resolution

    • Inheritance is acyclic (cycle detection via visited set).
    • Recursion depth is bounded by maxDepth.
    • Inactive or expired UserRole entries are excluded from resolution.
  5. No implicit grant
    Access is granted only by:

    • An explicit permission that matches the required URN (and passes any target or ABAC checks), or
    • The explicit configuration defaultAllow: true.
      There are no other code paths that grant access.

3. Feature Set

  • RBAC: Roles with a list of permission URNs; user has a list of role names (or UserRole[]). Effective permissions are the union of role permissions plus user’s direct permissions.
  • HRBAC: Roles may declare inherits: [roleName, ...]. Resolution is acyclic and depth-limited. UserRole can carry context, active, and expiresAt; inactive or expired roles are excluded.
  • URN-based ACL: Permissions are strings in the form resource:action:target (e.g. profile:read:own, *:*:*). Matching is component-wise with * as wildcard. Use buildUrn, matchUrn, matchAnyUrn for construction and matching.
  • Policy evaluation model: The primary decision is engine.check(user, requiredUrn, resource?). The engine:
    1. Resolves the user’s effective permissions (via internal use of role resolution and direct permissions).
    2. Checks if any permission matches the required URN.
    3. If the URN target is own or tenant and resource is provided, applies the corresponding check (owner or tenant).
    4. If a matching permission has ABAC conditions and resource is provided, evaluates conditions (with $user.* placeholder support).
    5. Returns an AccessResult: { allowed, reason?, matchedBy?, matchedUrn?, duration? }.

4. Public API Contract

Inputs

  • User: { userId: string; tenantId?: string; roles: …; permissions: string[]; metadata?: Record<string, unknown> }. Supplied by the consumer; may be null/undefined (then result is deny unless defaultAllow is true). The exported User type declares roles as string[]; at runtime roles may be string[] or UserRole[] (see RoleResolver.resolveUserPermissions and getPermissionsForUser). Use toRoleNames when you need a flat role-name array from either form.
  • requiredUrn: Non-empty string in the form resource:action:target. Invalid format behavior is controlled by strictMode (see config).
  • resource (optional): Plain object. Used for :own / :tenant and for ABAC conditions. Owner fields: userId, ownerId, createdBy (first present wins). Tenant field: tenantId. Other fields may be used by ABAC conditions.

Outputs

  • AccessResult: { allowed: boolean; reason?: string; matchedBy?: string; matchedUrn?: string; duration?: number }.
    allowed is the decision; other fields are informational.

Decision semantics

  • Allow: At least one effective permission matches the required URN, and any applicable :own / :tenant check passes, and any applicable ABAC conditions evaluate to true.
  • Deny: Otherwise (no match, or user null, or target/condition check failed). Unless defaultAllow === true, no match or null user yields deny.

Error behavior

  • Invalid URN format: If strictMode is true, the engine denies immediately without matching. If strictMode is false, behavior is defined by the implementation (see URN utilities for normalization).
  • The library does not throw for “permission denied”; it returns { allowed: false, reason?: string, ... }. Throwing is reserved for programming errors (e.g. invalid arguments) as defined by the implementation.

Stable surface (contract)

  • Decision: AccessEngine#check(user, requiredUrn, resource?), checkSync(user, requiredUrn, resource?); AccessResult shape as above.
  • URN: parseUrn, buildUrn, matchUrn, matchAnyUrn, isValidUrn, normalizeUrn; format resource:action:target; wildcard *.
  • Types: User, Role, UserRole, AccessResult, ParsedUrn, AccessEngineConfig, and other exported types as in the package.
  • RoleResolver: constructor(roles), resolveUserPermissions(user, options) returning ResolvedPermissions (e.g. permissions Set, roles Set, hasWildcard).
  • Config: AccessEngineConfig (e.g. defaultAllow, strictMode, enableCache, enableAudit, auditLogger, …).
  • Resource helpers: getResourceOwnerId(resource), getResourceTenantId(resource) (same field order as :own / :tenant).
  • Factories: createAccessEngine(config?), createAccessEngineWithDefaults(config?), createAccessEngineWithRoles(roles, config?).

Consumers must rely only on this stable surface and the documented invariants. Do not depend on unexported or @internal APIs.


5. Integration Guide

Backend vs frontend

The library is the same package everywhere; how you import it depends on the tier:

| Tier | Import rule | Typical use | |------|-------------|-------------| | Backend (Node services, gateway) | Use your shared backend access layer—the module that re-exports this library together with persistence, JWT/catalog resolution, gateway middleware, policies, and ACL grants. Do not import this package directly in microservice application code; import buildUrn, rule helpers, and types from that layer so checks stay aligned with the store and gateway. | Facade-first: seed roles, resolve JWT to a RoleResolver, call the facade check(resource, action, { target, resourceContext }) (or requirePermission in resolvers) instead of reaching into store/engine internals. Low-level paths still use buildUrn + engine.check (often via a single checkAccess-style helper). | | Frontend (browser) | Import this package directly (public API only). | Map your UI user to User; use buildUrn, toRoleNames, and rule helpers (can, canAny, canAll, urnOrRoles, …). Prefer a thin wrapper (e.g. canDo(user, resource, action, target) = buildUrn + can) so URNs are never hand-built. Permissions shown in the UI usually mirror the JWT/session token. |

Standalone or tests may call createAccessEngine / createAccessEngineWithRoles / createAccessEngineWithDefaults and engine.check directly.

How a backend service must consume the library (via the shared layer)

  1. Single decision path
    For “can user do X?” use one path: build the required URN with buildUrn(resourceType, action, target) (e.g. target '*' or 'own' or 'tenant') and call engine.check(user, urn, resourceContext?), or use your backend’s facade/helper that does the same internally. Do not construct URNs via string concatenation or template literals; use buildUrn only.

  2. Optional resource context
    When the check involves :own, :tenant, or ABAC, pass the resource object as the third argument to engine.check(user, urn, resource). The engine will use getResourceOwnerId / getResourceTenantId and ABAC condition evaluation when applicable. Production backends often pass resourceContext into facade check so ACL grants can match on resourceProperty / resourceValue.

  3. Role and permission loading
    This package does not persist data. The backend loads roles (and optionally permission definitions) from its own store, maps them to the engine’s Role shape if needed, and registers them with engine.addRole(...) or by constructing an engine with createAccessEngineWithRoles(roles, config).

  4. User shape
    The consumer must supply a User that matches the contract (e.g. userId, tenantId?, roles, permissions). Normalize role names from your user model with toRoleNames(roles) when adapting string[] | UserRole[] to the format the engine expects.

Responsibilities of the consuming backend

  • Authentication: Provide a valid User or null; this library does not authenticate.
  • Persistence: Load and persist roles/permissions; register them with the engine (or your access store behind the facade).
  • URN construction: Use only buildUrn for building URNs.
  • Single gate: Route all “can user do X?” checks through one helper that calls buildUrn and engine.check (and optionally passes resource context), or through the facade equivalent.
  • No use of internals: Do not import or rely on unexported APIs or @internal implementation details (e.g. cache, private methods).

Explicit anti-patterns (what must NOT be done)

  • Do not build URNs with template literals or string concatenation. Use buildUrn(resource, action, target) only.
  • Do not call internal or unexported APIs. Use only the public, documented surface.
  • Do not assume access is granted when user is null unless you have explicitly set defaultAllow: true (not recommended for security-critical use).
  • Do not rely on any behavior that is not documented in this README (e.g. undocumented field order for :own / :tenant, or undocumented normalization).
  • Do not use this library to enforce non-authorization concerns (e.g. rate limiting or business rules that are not “can this user perform this action on this resource?”).

6. Extensibility Model

What can be extended safely

  • auditLogger (config): Set auditLogger in AccessEngineConfig to a function that receives AuditEvent (e.g. user, urn, resource, result). Invoked after each check; return value is ignored.
  • PermissionRule and rule helpers: Use allow, deny, hasRole, can, isOwner, sameTenant, and, or, not, custom, rule(), rateLimit, etc., in your own code (e.g. gateway or middleware). They are not invoked inside the engine for check; they are for composing rules outside the engine. (Use AccessEngine.check only for allow/deny on URNs; use rateLimit and similar helpers in composed rules, not as a substitute for engine checks.)
  • ABAC conditions: Attach conditions to permission definitions and pass a resource into engine.check; conditions may reference $user.* in values. Operators and semantics are defined in the implementation (see package types and engine implementation).

What is intentionally fixed and immutable

  • URN format: Exactly three segments resource:action:target; no other structure is supported.
  • Owner field order for :own: userIdownerIdcreatedBy only.
  • Tenant field for :tenant: resource.tenantId and user.tenantId only.
  • Deny-by-default: The invariant that “no match or null user ⇒ deny” unless defaultAllow === true cannot be relaxed without a breaking change.
  • No external policy resolvers: The engine does not accept plug-in policy resolvers; decisions are driven only by in-memory roles and permission definitions.

7. Non-Goals

  • Legacy or backward-compatibility layers: The library does not provide compatibility shims or fallbacks for deprecated behavior. Use the current public API only.
  • Fallback behavior: There is no fallback path that grants access when the primary path denies. The only way to allow is an explicit match (or defaultAllow).
  • Backward compatibility guarantees: Contract changes that break the documented stable surface are made only with a major version bump. Within that, consumers must not rely on undocumented or internal behavior.

8. Documentation Map

  • This README is the only place for authorization contract, invariants, and integration (including backend vs frontend). Other repository documents must link here and must not duplicate those rules.
  • Tests: test/ataccess.spec.ts — contract tests (invariants, stable API) and feature coverage; tests use only the public API.
  • Repo coding standards may still describe non-contract topics (for example gateway authorization/ folder layout); those sections should point here for anything that touches this library’s semantics.

This README is the authoritative source for ataccess behavior and integration.