policy
| Kind | kit |
|---|---|
| Categories | security web |
| Keywords | authorization auth policy permissions security access-control |
A flexible, composable authorization framework for Kit (inspired by Action Policy)
Files
| File | Description |
|---|---|
.editorconfig | Editor formatting configuration |
.gitignore | Git ignore rules for build artifacts and dependencies |
.tool-versions | asdf tool versions (Zig, Kit) |
LICENSE | MIT license file |
README.md | This file |
examples/blog-policy.kit | Example: blog policy |
kit.toml | Package manifest with metadata and dependencies |
src/core.kit | Core module |
src/error.kit | Authorization error with detailed failure information |
src/main.kit | Main module |
src/scope.kit | Apply a scope function to a collection |
tests/policy.test.kit | Tests for policy |
tests/types.test.kit | Tests for types |
Dependencies
None - kit-policy has no external dependencies.
Installation
kit add gitlab.com/kit-lang/packages/kit-policy.gitQuick Start
import Kit.Policy.Core as PolicyCore
# Define your domain types
type Post = {id: Int, author-id: Int, published?: Bool}
type User = {id: Int, admin?: Bool}
type AuthContext = {user: User}
# Define a policy function
post-policy = fn(post, ctx, action) =>
if ctx.user.admin? then
PolicyCore.allow
else
match action
| :show -> PolicyCore.allow-if post.published?
| :update -> PolicyCore.allow-if (ctx.user.id == post.author-id)
| :destroy -> PolicyCore.allow-if (ctx.user.id == post.author-id)
| _ -> PolicyCore.no-rule action
# Check authorization
main = fn =>
user = {id: 1, admin?: false}
ctx = {user: user}
post = {id: 1, author-id: 1, published?: true}
if PolicyCore.can-with? post-policy post ctx :update then
print "Can update post"
else
print "Cannot update post"Modules
Policy.Core
Core authorization helpers for building policy functions.
Authorization Checks
# Check if action is allowed (returns Bool)
PolicyCore.can-with? policy resource context action
# Check if action is allowed (returns Option Bool)
PolicyCore.may-with? policy resource context actionRule Builders
# Simple allow/deny
PolicyCore.allow # Ok true
PolicyCore.deny # Ok false
# Conditional rules
PolicyCore.allow-if condition # Ok condition
PolicyCore.deny-if condition # Ok (not condition)
# Rule not found error
PolicyCore.no-rule action # Err (RuleNotFound {action: action})
# Allow with custom denial reason
PolicyCore.allow-or-deny condition resource-name reason actionPre-check Helpers
Pre-checks return Option Bool: Some true to allow, Some false to deny, None to continue to main rule.
# Admin bypass - allows if user is admin
PolicyCore.admin-bypass is-admin? ctx
# Owner check - allows if user owns the resource
PolicyCore.owner-check is-owner? resource ctx
# Combine pre-checks (first Some wins)
PolicyCore.first-pre-check [check1, check2, check3]Action Helpers
# Action lists
PolicyCore.crud-actions # [:index, :show, :create, :update, :destroy]
PolicyCore.read-actions # [:index, :show]
PolicyCore.write-actions # [:create, :update, :destroy]
# Action predicates
PolicyCore.read-action? action # true for :index, :show
PolicyCore.write-action? action # true for :create, :update, :destroy
# Alias resolution
PolicyCore.resolve-alias :new # :create
PolicyCore.resolve-alias :edit # :update
PolicyCore.resolve-alias :delete # :destroy
PolicyCore.resolve-alias :view # :showPolicy Composition
# All must allow
PolicyCore.all-allowed? [result1, result2, result3]
# Any must allow
PolicyCore.any-allowed? [result1, result2, result3]Policy.Scope
Helpers for filtering collections based on authorization context.
import Kit.Policy.Scope as PolicyScope
# Apply a scope function
post-scope = fn(posts, ctx) =>
if ctx.user.admin? then
posts
else
posts |> List.filter (fn(p) => p.published?)
visible-posts = PolicyScope.scope-with post-scope all-posts ctxPagination
# Paginate a list (1-indexed pages)
page1 = PolicyScope.paginate 1 10 items # First 10 items
page2 = PolicyScope.paginate 2 10 items # Items 11-20
# Get pagination info
info = PolicyScope.pagination-info 1 10 items
# Returns: {page: 1, per-page: 10, total: 100, pages: 10}
# Get total pages
pages = PolicyScope.total-pages 10 itemsScope Helpers
# Filter by predicate
PolicyScope.filter-by predicate items
# Predicate combinators
PolicyScope.both? pred1 pred2 item # pred1 AND pred2
PolicyScope.either? pred1 pred2 item # pred1 OR pred2
PolicyScope.not-matching? pred item # NOT predPolicy.Error
Error types for authorization failures.
import Kit.Policy.Error as PolicyError
# Error types
type PolicyError =
| NotAuthorized {resource: String, action: Keyword, reason: String}
| RuleNotFound {action: Keyword}
| ContextMissing {field: String}
| PolicyNotFound {resource-type: String}
| CustomError String
# Authorization result type
type AuthResult =
| Allowed
| Denied String
# Failure reason tracking
type FailureReason = FailureReason {
policy: String,
action: Keyword,
details: String
}Design Philosophy
- Framework-agnostic: Works with any Kit application, not tied to a specific web framework
- Functional: Policies are pure functions, easy to test and compose
- Type-safe: Leverages Kit's type system for compile-time safety
- Explicit: Results are
Result Bool PolicyError, not exceptions - Composable: Pre-checks, scopes, and policies can be combined
Examples
See the examples/ directory for complete examples:
blog-policy.kit- Blog application with posts and authorization
Running Tests
kit testLicense
MIT License - see LICENSE for details.
Exported Functions & Types
scope-with
Apply a scope function to a collection
(collection -> context -> collection) -> collection -> context -> collection
filter-by
Filter a list using a predicate
(a -> Bool) -> List a -> List a
is-owned-by?
Check if item is owned by user (using extractor functions)
(a -> Int) -> (context -> Int) -> a -> context -> Bool
is-published?
Check if item is published (using extractor function)
(a -> Bool) -> a -> Bool
is-in-state?
Check if item is in a specific state (using extractor function)
(a -> Keyword) -> Keyword -> a -> Bool
is-admin-or?
Check with admin bypass (shows all if admin)
(context -> Bool) -> (a -> Bool) -> context -> a -> Bool
both?
Combine two predicates with AND
(a -> Bool) -> (a -> Bool) -> a -> Bool
either?
Combine two predicates with OR
(a -> Bool) -> (a -> Bool) -> a -> Bool
not-matching?
Negate a predicate
(a -> Bool) -> a -> Bool
paginate
Paginate a list (1-indexed pages)
Int -> Int -> List a -> List a
total-pages
Get total pages
Int -> List a -> Int
pagination-info
Create pagination info
Int -> Int -> List a -> {page: Int, per-page: Int, total: Int, pages: Int}
PolicyError
Authorization error with detailed failure information
Variants
NotAuthorized {resource, action, reason}RuleNotFound {action}ContextMissing {field}PolicyNotFound {resource-type}CustomError {String}FailureReason
A failure reason with source policy and action
Variants
FailureReason {policy, action, details}AuthResult
Authorization result type
Variants
AllowedDenied {String}PolicyError
Authorization error with detailed failure information
FailureReason
Failure reason with source policy and action
AuthResult
Authorization result type (Allowed or Denied)
NotAuthorized
Not authorized error variant
RuleNotFound
Rule not found error variant
ContextMissing
Context missing error variant
PolicyNotFound
Policy not found error variant
CustomError
Custom error variant
Allowed
Allowed result variant
Denied
Denied result variant with reason
can-with?
Check authorization using a policy function, returning true/false
(r -> c -> Keyword -> Result Bool e) -> r -> c -> Keyword -> Bool
may-with?
Check authorization using a policy function, returning Option
(r -> c -> Keyword -> Result Bool e) -> r -> c -> Keyword -> Option Bool
admin-bypass
Admin bypass pre-check - allows if user is admin
(context -> Bool) -> context -> Option Bool
owner-check
Owner check pre-check - allows if user owns the resource
(resource -> context -> Bool) -> resource -> context -> Option Bool
first-pre-check
Combine pre-checks (first Some wins)
List (Option Bool) -> Option Bool
crud-actions
CRUD actions list
List Keyword
read-actions
Read-only actions list
List Keyword
write-actions
Write actions list
List Keyword
read-action?
Check if action is a read action
Keyword -> Bool
write-action?
Check if action is a write action
Keyword -> Bool
resolve-alias
Map aliased actions to canonical actions
Keyword -> Keyword
all-allowed?
Check if all results allow
List (Result Bool e) -> Result Bool e
any-allowed?
Check if any result allows
List (Result Bool e) -> Result Bool e
allow
Simple allow rule
Result Bool e
deny
Simple deny rule
Result Bool e
no-rule
Rule-not-found error result
Keyword -> Result Bool PolicyError
allow-if
Conditionally allow based on predicate
Bool -> Result Bool e
deny-if
Conditionally deny based on predicate
Bool -> Result Bool e
allow-or-deny
Allow with custom reason on denial
Bool -> String -> String -> Keyword -> Result Bool PolicyError