Scope Registry
Auto-generated at build time • API scopes & restrictions • Role/Flag-based access control
Table of Contents
Overview
The Scope Registry (core/lib/registries/scope-registry.ts) is an auto-generated Data-Only registry that manages API scopes, role-based permissions, and access restrictions for the authentication system.
Architecture Pattern: Data-Only Registry + Service Class
Key Benefits:
- Data-Only Registry - Only exports data constants (SCOPE_CONFIG, API_CONFIG)
- ScopeService - 12 static methods for scope queries (
core/lib/services/scope.service.ts) - Role-based scopes - Define scopes per role (superadmin, admin, manager, member)
- Flag-based scopes - Extend scopes for user flags (beta_tester, vip, etc.)
- Restriction rules - Remove or allow-only scope patterns
- Zero runtime I/O - All scopes resolved at build time
- O(1) lookups - Instant access to scope configurations
Generated by: core/scripts/build/registry.mjs
Registry Location: core/lib/registries/scope-registry.ts
Service Location: core/lib/services/scope.service.ts
Key Concepts: Scopes vs Permissions
Scopes and Permissions are fundamentally different concepts:
| Aspect | Scopes | Permissions |
|---|---|---|
| Focus | API access control | UI/Feature authorization |
| Context | User session / API key | Team membership |
| Granularity | API endpoints | Entity actions |
| Assignment | Role + User flags | Team roles |
| Service | ScopeService |
PermissionService |
Scopes control what API endpoints a user can access based on their role and flags. Permissions control what actions a team member can perform on entities.
Registry Structure
ScopeConfig Interface
export interface ScopeConfig {
base: string[] // Base scopes for all users
roles: Record<string, string[]> // Scopes per role
flags: Record<string, string[]> // Scopes per user flag
restrictions: Record<string, RestrictionRule> // Restriction rules
}
export interface RestrictionRule {
remove?: string[] // Patterns to remove from scopes
allow_only?: string[] // Only allow scopes matching these patterns
}
ApiConfig Interface
export interface ApiConfig {
filters: {
allowed: string[] // Allowed API filter names
}
entityPaths: string[] // Entity API paths
}
Auto-Generated Registry Example
/**
* Auto-generated API Scope Registry
* DO NOT EDIT - This file is auto-generated by core/scripts/build/registry.mjs
*/
export const SCOPE_CONFIG: ScopeConfig = {
"base": [],
"roles": {
"superadmin": ["admin:users", "tasks:delete", "products:write", "products:delete"],
"admin": ["admin:users", "tasks:delete", "products:write", "products:delete"],
"manager": [],
"member": []
},
"flags": {
"beta_tester": ["beta:features"],
"vip": ["vip:features", "advanced:export"],
"early_adopter": ["early:features"],
"power_user": ["advanced:features", "bulk:operations"]
},
"restrictions": {
"restricted": { "remove": ["delete", "admin"] },
"limited_access": { "allow_only": ["read", "tasks:write"] }
}
}
export const API_CONFIG: ApiConfig = {
"filters": {
"allowed": ["status", "role", "completed", "userId"]
},
"entityPaths": []
}
ScopeService API
The ScopeService provides 12 static methods for scope queries:
Core Methods (6)
import { ScopeService } from '@/core/lib/services'
// Get base scopes for all authenticated users
const baseScopes = ScopeService.getBaseScopes()
// Returns: string[]
// Get scopes for a specific role
const adminScopes = ScopeService.getRoleScopes('admin')
// Returns: ['admin:users', 'tasks:delete', 'products:write', 'products:delete']
// Get scopes for a user flag
const vipScopes = ScopeService.getFlagScopes('vip')
// Returns: ['vip:features', 'advanced:export']
// Get allowed API filters
const filters = ScopeService.getAllowedFilters()
// Returns: ['status', 'role', 'completed', 'userId']
// Get restriction rules for a flag
const rules = ScopeService.getRestrictionRules('restricted')
// Returns: { remove: ['delete', 'admin'] }
// Get entity API paths
const paths = ScopeService.getEntityApiPaths()
// Returns: string[]
Helper Methods (6)
// Get all available roles
const roles = ScopeService.getRoles()
// Returns: ['superadmin', 'admin', 'manager', 'member']
// Get all available flags
const flags = ScopeService.getFlags()
// Returns: ['beta_tester', 'vip', 'early_adopter', 'power_user']
// Check if a role exists
ScopeService.hasRole('admin') // true
ScopeService.hasRole('invalid') // false
// Check if a flag exists
ScopeService.hasFlag('vip') // true
ScopeService.hasFlag('invalid') // false
// Get full configurations
const scopeConfig = ScopeService.getScopeConfig()
const apiConfig = ScopeService.getApiConfig()
Common Patterns
Building User Scopes
import { ScopeService } from '@/core/lib/services'
function buildUserScopes(role: string, flags: string[]): string[] {
// Start with base scopes
const scopes = [...ScopeService.getBaseScopes()]
// Add role scopes
scopes.push(...ScopeService.getRoleScopes(role))
// Add flag scopes
for (const flag of flags) {
scopes.push(...ScopeService.getFlagScopes(flag))
}
return [...new Set(scopes)] // Deduplicate
}
// Example usage
const userScopes = buildUserScopes('admin', ['vip', 'beta_tester'])
// Returns: ['admin:users', 'tasks:delete', ..., 'vip:features', 'beta:features']
Applying Restrictions
function applyRestrictions(scopes: string[], flags: string[]): string[] {
let filteredScopes = [...scopes]
for (const flag of flags) {
const rules = ScopeService.getRestrictionRules(flag)
// Remove matching patterns
if (rules.remove) {
filteredScopes = filteredScopes.filter(scope =>
!rules.remove!.some(pattern => scope.includes(pattern))
)
}
// Allow only matching patterns
if (rules.allow_only) {
filteredScopes = filteredScopes.filter(scope =>
rules.allow_only!.some(allowed =>
scope.includes(allowed) || scope === allowed
)
)
}
}
return filteredScopes
}
Validating API Filters
import { ScopeService } from '@/core/lib/services'
function validateFilters(requestedFilters: string[]): string[] {
const allowed = ScopeService.getAllowedFilters()
return requestedFilters.filter(f => allowed.includes(f))
}
// Example: Only allow valid filters in API queries
const safeFilters = validateFilters(['status', 'role', 'hack'])
// Returns: ['status', 'role'] (removes 'hack')
Performance Characteristics
| Operation | Complexity | Description |
|---|---|---|
getBaseScopes() |
O(n) | Returns copy of base array |
getRoleScopes(role) |
O(1) lookup + O(n) copy | Hash lookup + array copy |
getFlagScopes(flag) |
O(1) lookup + O(n) copy | Hash lookup + array copy |
getAllowedFilters() |
O(n) | Returns copy of filters array |
getRestrictionRules(flag) |
O(1) | Direct hash lookup |
getEntityApiPaths() |
O(n) | Returns copy of paths array |
getRoles() |
O(n) | Object.keys() |
getFlags() |
O(n) | Object.keys() |
hasRole(role) |
O(1) | in operator |
hasFlag(flag) |
O(1) | in operator |
Memory: Registry is loaded once, shared across all requests. Immutability: All array returns are copies to prevent mutation.
Testing
Test Location
core/tests/jest/services/scope.service.test.ts
Test Coverage
| Metric | Coverage |
|---|---|
| Statements | 100% |
| Branches | 100% |
| Functions | 100% |
| Lines | 100% |
Test Categories (70 tests)
- getBaseScopes - 4 tests (return value, immutability)
- getRoleScopes - 8 tests (valid roles, invalid roles, case sensitivity)
- getFlagScopes - 8 tests (valid flags, invalid flags, case sensitivity)
- getAllowedFilters - 5 tests (return value, immutability)
- getRestrictionRules - 5 tests (valid flags, invalid flags)
- getEntityApiPaths - 4 tests (return value, immutability)
- getRoles - 3 tests (all roles returned)
- getFlags - 3 tests (all flags returned)
- hasRole - 4 tests (exists/not exists, case sensitivity)
- hasFlag - 4 tests (exists/not exists, case sensitivity)
- getScopeConfig - 4 tests (structure validation)
- getApiConfig - 4 tests (structure validation)
- Backward Compatibility - 6 tests (deprecated exports)
- Integration - 5 tests (cross-method consistency)
- Edge Cases - 3 tests (whitespace, special chars, null-like values)
Running Tests
# Run scope service tests
pnpm jest core/tests/jest/services/scope.service.test.ts
# Run with coverage
pnpm jest core/tests/jest/services/scope.service.test.ts --coverage
Backward Compatibility
The service exports deprecated function aliases for backward compatibility:
// ❌ Deprecated (still works)
import { getBaseScopes } from '@/core/lib/services/scope.service'
const scopes = getBaseScopes()
// ✅ Recommended
import { ScopeService } from '@/core/lib/services'
const scopes = ScopeService.getBaseScopes()
Deprecated exports:
getBaseScopes→ UseScopeService.getBaseScopes()getRoleScopes→ UseScopeService.getRoleScopes()getFlagScopes→ UseScopeService.getFlagScopes()getAllowedFilters→ UseScopeService.getAllowedFilters()getRestrictionRules→ UseScopeService.getRestrictionRules()getEntityApiPaths→ UseScopeService.getEntityApiPaths()