Skip to content
Agentmart
Submit a tool

authorization

Identity and Access Management, policy evaluation engine, declarative route guards, and permissions.

Skill by Mihir-Rabari

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/mihir-rabari-authorization-1b6c6c

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Identity and Access Management, policy evaluation engine, declarative route guards, and permissions.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/authorization

What's inside

Skill name
authorization
Description
Identity and Access Management, policy evaluation engine, declarative route guards, and permissions.
License
MIT
Instructions
69 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Authorization & IAM Skill

## 1. When to Use
Use this skill when defining permissions, roles, groups, IAM policies, policy evaluation statements, route guards, or resource ownership checks.

## 2. Policy Evaluation Invariants
1. **Tenant ROOT Authority**: If `identity.identityType === 'ROOT'`, access is unconditionally granted across all actions *within that identity's own tenant*. ROOT is the root of one workspace, never of the platform.
2. **Explicit Deny Precedence**: If any statement (direct, role, or group) contains `effect: 'deny'` matching the requested action, access is immediately blocked.
3. **Allow Statements & Ownership**: If an allow statement matches:
   - For `:self` actions (e.g. `profile:update:self`), verify `context.resourceOwnerId === identity.id`.
4. **Implicit Deny**: If no matching statement is found, access is denied by default.

## 2a. Multi-tenancy
Every signup creates a workspace (tenant) and makes its creator that workspace's ROOT. Everyone invited afterwards is an `EXTERNAL_USER` in the same tenant.

- **Users** belong to one tenant.
- **Roles, groups and policies** are either *platform-owned* (`tenant_id` NULL — shared, visible to every tenant, immutable) or *owned by one tenant* (invisible to all others, editable only by it). Two tenants may use the same name.
- **Permissions** (the catalog) are global.
- `IamService` methods take `tenantId` as a required first argument. A record outside the caller's tenant is **404, never 403** — confirming it exists would leak across the boundary. Routes obtain it with `tenantIdOf(request.user)`, which refuses an identity with no tenant.
- A ROOT cannot grant, attach or read another tenant's records either: `assertCanGrantPolicy` checks tenant visibility *before* the ROOT shortcut.
- **Granting needs holding.** Assigning a role or adding a user to a group requires the actor to hold every permission those confer (`assertCanGrantRole`), not merely `roles:update`.

## 2b. The Access-Control Console (`/iam`)
The web app's `/iam` area is its own route group with its own sign-in (`/iam/login`). It admits the workspace ROOT and holders of `admin:access`. That gate only decides whether to render; the API's scoping is what protects data. Users are added by invitation (`POST /api/v1/iam/users`): no password is accepted or returned, and the invitee chooses their own from an emailed link.

## 3. Policy Condition Operators
`PolicyStatement.conditions` supports exactly four operators, each mapping a context key to the value(s) it must satisfy. The canonical list lives in `SUPPORTED_CONDITION_OPERATORS` (`@packages/validation`, `packages/validation/src/iam.ts`); `packages/iam/src/policy/policy-engine.ts` imports it rather than redeclaring it, since `@packages/validation` has no dependency on `@packages/iam` and must not gain one.

| Operator | Example | Meaning |
|---|---|---|
| `StringEquals` | `{ "department": "finance" }` | `context.attributes.department === "finance"` |
| `StringNotEquals` | `{ "department": "contractors" }` | `context.attributes.department !== "contractors"` |
| `Bool` | `{ "mfaPresent": true }` | `Boolean(context.attributes.mfaPresent) === true` |
| `OwnerEquals` | `{ "resourceOwnerId": "@identity" }` | `context.resourceOwnerId === identity.id` |

- **Write-time rejection**: `PolicyStatementSchema.conditions` validates the outer keys (the operator names) against `SUPPORTED_CONDITION_OPERATORS` via `superRefine`. `POST /api/v1/iam/policies` and `PUT /api/v1/iam/policies/:id` return `400 VALIDATION_ERROR` naming the offending operator and listing the supported set — an admin can no longer save a policy with an operator the engine can't evaluate.
- **Fail-closed at evaluation time**: `evaluateConditions` still treats any operator outside this set as a denial (defense in depth, in case a statement reaches the engine by a path other than the write API — e.g. a stale row).

## 4. Operational Logging vs Audit Trail
- **Operational Logs**: Diagnostic logs for developer visibility (e.g. `iam.permission.evaluated`, `iam.user.status_updated`).
- **Audit Logs**: Durable database records for security compliance (`iam_audit_logs` table tracking administrative mutations).

## 5. Route Guards Convention
```typescript
import { requirePermission, requireAuthentication } from '@packages/iam';

// Require login
fastify.get('/profile', { preHandler: [requireAuthentication()] }, handler);

// Require specific permission
fastify.get('/users', { preHandler: [requirePermission('users:read')] }, handler);

// Require self-resource ownership
fastify.put('/users/:id', {
  preHandler: [
    requirePermission('users:update', (req) => ({ resourceOwnerId: req.params.id })),
  ],
}, handler);
```

## 6. Mandatory Testing Expectations
Every authorization change requires:
1. **Allow Cases**: Legitimate permissions allow operation.
2. **Deny Precedence**: Explicit `DENY` statement strictly overrides any matching `ALLOW`.
3. **Ownership Verification**: Resource-level ownership tests (`owner -> allowed`, `other user -> 403`, `unauthenticated -> 401`).
4. **Privilege Escalation Defense**: Negative tests proving normal external users cannot grant themselves permissions or access admin routes.
5. **Tenant Isolation**: For every new IAM method or route, a test where tenant A reaches for tenant B's user, role, group or policy and gets 404 — read, update, delete, and attach. Run it against a real database (`createTestDatabase()`), and confirm it fails when the tenant filter is removed.

From Mihir-Rabari/create-odoo-app, MIT.

More from Mihir-Rabari/create-odoo-app

Signals

Stars
0
Forks
1
Open issues
3
Last commit (this folder)
Oct 2, 2026 (26 days ago)
Last release
Sep 4, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

Same skill elsewhere

Copies of this SKILL.md published by others; search shows only one of them.

More from Mihir-Rabari

Cite this page

authorization skill. Agent Mart. https://agentmart.dev/skills/mihir-rabari-authorization-1b6c6c (last updated 2026-10-24)