{"files": {"main.ts": "import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n", "core.ts": "import {asciiLower, grantEffect, getParents} from './helpers.ts';\nfunction result(allowed: boolean, reason: string, matched: number[], policy: any): any { return {allowed: allowed, reason: reason, matched: matched, audit: (!allowed && policy.audit_denials) ? [reason] : []}; }\nexport function run(payload: any, policy: any): any {\n  const subject: any = (payload && payload.subject) ? payload.subject : {};\n  const resource: any = (payload && payload.resource) ? payload.resource : {};\n  const now: any = payload ? payload.now : undefined;\n  const issuedAt: any = payload ? payload.issued_at : undefined;\n  const requestedAction: any = payload ? payload.action : undefined;\n  if (subject.tenant !== resource.tenant) { return result(false, 'tenant', [], policy); }\n  if (now - issuedAt > policy.token_ttl) { return result(false, 'token', [], policy); }\n  const suspended: any = (subject.suspended !== undefined) ? subject.suspended : subject.is_suspended;\n  if (policy.suspended_denies && suspended) { return result(false, 'suspended', [], policy); }\n  const owner: any = (subject.owner !== undefined) ? subject.owner : subject.is_owner;\n  if (policy.owner_bypass && owner) { return result(true, 'owner', [], policy); }\n  const roleSet: Set<string> = new Set<string>();\n  const initialRoles: any[] = [];\n  if (Array.isArray(subject.roles)) { for (const r of subject.roles) { initialRoles.push(r); } }\n  if (policy.group_union && Array.isArray(subject.groups)) { for (const g of subject.groups) { initialRoles.push(g); } }\n  for (const r of initialRoles) { roleSet.add(r); }\n  if (policy.inherit_roles) {\n    const queue: any[] = Array.from(roleSet);\n    const visited: Set<string> = new Set<string>(roleSet);\n    while (queue.length > 0) {\n      const cur: any = queue.shift();\n      const parents: any[] = getParents(payload ? payload.role_parents : undefined, cur);\n      for (const p of parents) {\n        if (!visited.has(p)) { visited.add(p); roleSet.add(p); queue.push(p); }\n      }\n    }\n  }\n  const grants: any[] = (payload && Array.isArray(payload.grants)) ? payload.grants : [];\n  const resourceId: any = resource.id;\n  const matched: number[] = [];\n  let anyAllow: boolean = false;\n  let anyDeny: boolean = false;\n  for (let i: number = 0; i < grants.length; i++) {\n    const grant: any = grants[i] || {};\n    const grantRoles: any[] = Array.isArray(grant.roles) ? grant.roles : ((grant.role !== undefined) ? [grant.role] : []);\n    let roleOk: boolean = false;\n    for (const gr of grantRoles) { if (roleSet.has(gr)) { roleOk = true; break; } }\n    if (!roleOk) { continue; }\n    const grantAction: any = grant.action;\n    let actionOk: boolean = false;\n    if (typeof grantAction === 'string' && typeof requestedAction === 'string') {\n      if (policy.action_casefold) { actionOk = asciiLower(grantAction) === asciiLower(requestedAction); } else { actionOk = grantAction === requestedAction; }\n      if (!actionOk && policy.wildcard_action && grantAction === '*') { actionOk = true; }\n    } else if (grantAction === requestedAction) { actionOk = true; } else if (policy.wildcard_action && grantAction === '*') { actionOk = true; }\n    if (!actionOk) { continue; }\n    const scope: any = (grant.scope !== undefined) ? grant.scope : ((grant.resource !== undefined) ? grant.resource : grant.resource_id);\n    if (scope !== undefined && scope !== null) {\n      let scopeOk: boolean = false;\n      if (policy.resource_scope === 'exact') { scopeOk = scope === resourceId; } else { scopeOk = (typeof scope === 'string' && typeof resourceId === 'string') ? resourceId.startsWith(scope) : scope === resourceId; }\n      if (!scopeOk) { continue; }\n    }\n    const expires: any = (grant.expires !== undefined) ? grant.expires : ((grant.expires_at !== undefined) ? grant.expires_at : ((grant.expiry !== undefined) ? grant.expiry : grant.exp));\n    if (expires !== null && expires !== undefined) {\n      let expiryOk: boolean = false;\n      if (policy.expiry_inclusive) { expiryOk = now <= expires; } else { expiryOk = now < expires; }\n      if (!expiryOk) { continue; }\n    }\n    matched.push(i);\n    const eff: string = grantEffect(grant);\n    if (eff === 'deny') { anyDeny = true; } else { anyAllow = true; }\n  }\n  if (matched.length === 0) { return result(!!policy.default_allow, 'default', [], policy); }\n  let allowed: boolean = false;\n  if (policy.deny_wins) { allowed = anyAllow && !anyDeny; } else { allowed = anyAllow; }\n  return result(allowed, 'grants', matched, policy);\n}\n", "policy.ts": "export const POLICY: any = {\"action_casefold\": false, \"audit_denials\": false, \"default_allow\": false, \"deny_wins\": false, \"expiry_inclusive\": false, \"group_union\": false, \"inherit_roles\": false, \"owner_bypass\": false, \"resource_scope\": \"prefix\", \"suspended_denies\": false, \"token_ttl\": 200, \"wildcard_action\": false};\n", "helpers.ts": "export function asciiLower(s: string): string { let out: string = ''; for (let i: number = 0; i < s.length; i++) { const c: number = s.charCodeAt(i); if (c >= 65 && c <= 90) { out += String.fromCharCode(c + 32); } else { out += s.charAt(i); } } return out; }\nexport function grantEffect(grant: any): string { if (grant && typeof grant.effect === 'string') { const e: string = asciiLower(grant.effect); if (e === 'deny') { return 'deny'; } return 'allow'; } if (grant && typeof grant.allow === 'boolean') { return grant.allow ? 'allow' : 'deny'; } if (grant && typeof grant.allowed === 'boolean') { return grant.allowed ? 'allow' : 'deny'; } if (grant && typeof grant.deny === 'boolean') { return grant.deny ? 'deny' : 'allow'; } if (grant && typeof grant.decision === 'string') { return asciiLower(grant.decision) === 'deny' ? 'deny' : 'allow'; } if (grant && typeof grant.type === 'string') { const t: string = asciiLower(grant.type); if (t === 'deny') { return 'deny'; } } return 'allow'; }\nexport function getParents(roleParents: any, role: string): string[] { if (!roleParents) { return []; } if (Array.isArray(roleParents)) { const out: string[] = []; for (const entry of roleParents) { if (Array.isArray(entry) && entry.length >= 2 && entry[0] === role) { if (Array.isArray(entry[1])) { for (const p of entry[1]) { out.push(p); } } else { out.push(entry[1]); } } else if (entry && typeof entry === 'object') { const child: any = (entry.role !== undefined) ? entry.role : entry.child; if (child === role) { if (Array.isArray(entry.parents)) { for (const p of entry.parents) { out.push(p); } } else if (entry.parent !== undefined) { out.push(entry.parent); } else if (Array.isArray(entry.parent_roles)) { for (const p of entry.parent_roles) { out.push(p); } } } } return out; } const v: any = roleParents[role]; if (Array.isArray(v)) { return v.slice(); } if (typeof v === 'string') { return [v]; } return []; }\n"}}