rajt
Version:
A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.
67 lines (53 loc) • 1.69 kB
text/typescript
import { Ability } from './ability'
export class Authnz<T extends object> {
#abilities: string[]
#roles: string[]
#data: T
constructor(data: T, abilities: string[], roles: string[]) {
this.#abilities = abilities
this.#roles = roles
this.#data = data
}
can(...abilities: string[]): boolean {
if (this.#abilities.includes('*')) return true
return abilities.flat().every(ability => {
if (this.#roles.includes(ability)) return true
return this.#abilities.some(rule => this.#match(rule, ability))
})
}
cant(...abilities: string[]): boolean {
return !this.can(...abilities)
}
hasRole(...roles: string[]): boolean {
return roles.flat().every(role => this.#roles.includes(role))
}
static fromToken<T extends object>(user: any): Authnz<T> | null {
if (!user || user?.isInvalid()) return null
user = user.get()
const roles = [...(user?.role ? [user.role] : []), ...(user?.roles ?? [])]
const combined = [...(user?.perms ?? []), ...roles.flatMap(role => {
const perms = Ability.roles[role]
if (!perms) return []
return perms === '*' ? ['*'] : perms;
})]
const abilities = combined.includes('*') ? ['*'] : Array.from(new Set(combined))
return new Authnz(user as T, abilities, roles)
}
#match(rule: string, ability: string): boolean {
if (rule === ability) return true
if (rule.endsWith('.*')) {
const prefix = rule.slice(0, -2)
return ability.startsWith(`${prefix}.`) || ability === prefix
}
return false
}
get abilities() {
return this.#abilities
}
get roles() {
return this.#roles
}
get data() {
return this.#data
}
}