SkillKit.Authorization (SkillKit v0.1.0)

Copy Markdown View Source

Pure-function authorization API for SkillKit.

This module provides scope-based authorization for skills. It is a pure-function module — no process state, no GenServer, no registry coupling. All functions are safe to call from concurrent processes without synchronization.

ALL-of Semantics

Authorization uses ALL-of multi-scope semantics: a caller must hold every required scope in order to be authorized. Holding any subset is insufficient.

Scope coverage uses SkillKit.Scope.Validation.any_covers?/2 (OR primitive): a single granted wildcard scope (e.g. "admin:*") can satisfy a required exact scope (e.g. "admin:read"). ALL-of wraps this OR primitive across each required scope.

Error Signal Distinction

  • {:error, :unauthorized} — caller lacks required scopes
  • {:error, reason} — provider returned an error (passed through unchanged)
  • {:error, :not_found}never returned by this module; that atom is exclusive to SkillKit.Registry.get_skill/2

Usage

# authorize/2 — direct scope list
skill = %SkillKit.Skill{required_scope: ["admin:read"]}
{:ok, ^skill} = SkillKit.Authorization.authorize(skill, ["admin:read", "admin:write"])
{:error, :unauthorized} = SkillKit.Authorization.authorize(skill, ["tools:read"])

# authorize/3 — provider-resolved scopes
{:ok, ^skill} = SkillKit.Authorization.authorize(skill, MyApp.TokenProvider, %{token: "..."})
{:error, :token_expired} = SkillKit.Authorization.authorize(skill, MyApp.TokenProvider, %{token: "expired"})

# authorized?/2 — boolean convenience
true = SkillKit.Authorization.authorized?(skill, ["admin:*"])
false = SkillKit.Authorization.authorized?(skill, [])

Summary

Functions

Authorizes skill given a flat list of granted scope strings.

Authorizes skill by resolving scopes via provider.resolve_scopes(context).

Returns true when authorize(skill, granted_scopes) would return {:ok, _}.

Functions

authorize(skill, granted_scopes)

@spec authorize(SkillKit.Skill.t(), [String.t()]) ::
  {:ok, SkillKit.Skill.t()} | {:error, :unauthorized}

Authorizes skill given a flat list of granted scope strings.

Returns {:ok, skill} when every required scope is covered by at least one granted scope (ALL-of semantics). Returns {:error, :unauthorized} when any required scope is not covered.

Fast-paths to {:ok, skill} immediately when required_scope is [].

authorize(skill, provider, context)

@spec authorize(SkillKit.Skill.t(), module(), map()) ::
  {:ok, SkillKit.Skill.t()} | {:error, :unauthorized} | {:error, term()}

Authorizes skill by resolving scopes via provider.resolve_scopes(context).

provider must be an atom (module name) implementing SkillKit.AuthorizationProvider. context is an opaque map passed through to the provider unchanged.

Fast-paths to {:ok, skill} without calling the provider when required_scope is []. When the provider returns {:error, reason}, that error passes through unchanged — it is never normalized to {:error, :unauthorized}.

Provider exceptions are NOT rescued (let it crash).

authorized?(skill, granted_scopes)

@spec authorized?(SkillKit.Skill.t(), [String.t()]) :: boolean()

Returns true when authorize(skill, granted_scopes) would return {:ok, _}.

Convenience wrapper for use in boolean contexts (guards, filters, pipeline conditions).