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 | Blog authorization example |
kit.toml | Package manifest with metadata, tasks, and lint configuration |
src/core.kit | Core authorization helpers |
src/error.kit | Authorization errors, result types, and failure reasons |
src/main.kit | Package root module |
src/scope.kit | Scope, predicate, and pagination helpers |
tests/policy.test.kit | End-to-end policy behavior tests |
tests/types.test.kit | Policy type and helper tests |
Dependencies
No Kit package dependencies.
Installation
kit add gitlab.com/kit-lang/packages/kit-policy.gitUsage
import Kit.Policy.Core as PolicyCore
import Kit.Policy.Error as PolicyError
import Kit.Policy.Scope as PolicyScope
type Post = {id: Int, author-id: Int, published?: Bool, title: String}
type User = {id: Int, admin?: Bool}
type AuthContext = {user: User}
post-policy = fn(post, ctx, action) =>
if ctx.user.admin? then
PolicyCore.allow
else
match PolicyCore.resolve-alias 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
post-scope = fn(posts, ctx) =>
if ctx.user.admin? then
posts
else
posts |> List.filter (fn(post) => post.published?)
main = fn =>
user = {id: 1, admin?: false}
ctx = {user: user}
post = {id: 1, author-id: 1, published?: true, title: "Hello"}
posts = [post]
if PolicyCore.can-with? post-policy post ctx :update then
println "Can update post"
else
println "Cannot update post"
visible-posts = PolicyScope.scope-with post-scope posts ctx
page = PolicyScope.paginate 1 10 visible-posts
info = PolicyScope.pagination-info 1 10 visible-posts
println "Visible posts: ${page}"
println "Pages: ${info.pages}"
err = PolicyError.not-authorized "Post" :update "not the author"
println (PolicyError.message err)
mainAPI Overview
Policy.Core
Core helpers for policy functions that return Result Bool PolicyError.
PolicyCore.can-with? policy resource context action
PolicyCore.may-with? policy resource context action
PolicyCore.allow
PolicyCore.deny
PolicyCore.allow-if condition
PolicyCore.deny-if condition
PolicyCore.no-rule action
PolicyCore.allow-or-deny condition resource-name reason actionPre-check helpers return Option Bool: Some true allows, Some false denies, and None continues to the main rule.
PolicyCore.admin-bypass is-admin? ctx
PolicyCore.owner-check is-owner? resource ctx
PolicyCore.first-pre-check [check1, check2, check3]Action helpers provide common action groups and aliases.
PolicyCore.crud-actions
PolicyCore.read-actions
PolicyCore.write-actions
PolicyCore.read-action? action
PolicyCore.write-action? action
PolicyCore.resolve-alias :new # :create
PolicyCore.resolve-alias :edit # :update
PolicyCore.resolve-alias :delete # :destroy
PolicyCore.resolve-alias :view # :showPolicy composition helpers combine several authorization results.
PolicyCore.all-allowed? [result1, result2, result3]
PolicyCore.any-allowed? [result1, result2, result3]Policy.Scope
Scope helpers filter collections before returning data to a caller.
PolicyScope.scope-with scope-fn items ctx
PolicyScope.filter-by predicate items
PolicyScope.is-owned-by? get-owner-id get-user-id item ctx
PolicyScope.is-published? get-published item
PolicyScope.is-in-state? get-state target-state item
PolicyScope.is-admin-or? check-admin fallback-check ctx itemPredicate combinators are useful for building reusable scope checks.
PolicyScope.both? pred1 pred2 item
PolicyScope.either? pred1 pred2 item
PolicyScope.not-matching? pred itemPagination helpers are 1-indexed.
page1 = PolicyScope.paginate 1 10 items
pages = PolicyScope.total-pages 10 items
info = PolicyScope.pagination-info 1 10 itemsPolicy.Error
Error and result types for authorization failures.
type PolicyError =
| NotAuthorized {resource: String, action: Keyword, reason: String}
| RuleNotFound {action: Keyword}
| ContextMissing {field: String}
| PolicyNotFound {resource-type: String}
| CustomError String
type FailureReason = FailureReason {
policy: String,
action: Keyword,
details: String
}
type AuthResult =
| Allowed
| Denied StringHelper functions are exported from the module, so when imported as PolicyError they are called as module functions.
PolicyError.not-authorized resource action reason
PolicyError.rule-not-found action
PolicyError.context-missing field
PolicyError.policy-not-found resource-type
PolicyError.custom message
PolicyError.message err
PolicyError.kind err
PolicyError.is-not-authorized? err
PolicyError.is-rule-not-found? err
PolicyError.new policy action
PolicyError.with-details policy action details
PolicyError.policy reason
PolicyError.action reason
PolicyError.details reason
PolicyError.format reason
PolicyError.allowed
PolicyError.denied reason
PolicyError.is-allowed? result
PolicyError.is-denied? result
PolicyError.reason result
PolicyError.to-result resource-name action resultDesign Notes
- Policies are plain functions, so they are easy to test and compose.
- Authorization is explicit: helpers return
Result Bool PolicyErrorinstead of throwing exceptions. - Scopes are separate from policy checks so list filtering can happen before rendering or serialization.
- Common actions use keywords such as
:index,:show,:create,:update, and:destroy. - The package is framework-agnostic and can be used with any Kit application code.
Development
Running Examples
Run the blog policy example with the interpreter:
kit run examples/blog-policy.kitCompile the example to a native binary:
kit build examples/blog-policy.kit && ./blog-policyRunning Tests
Run the test suite:
kit testRun the test suite with coverage:
kit test --coverageRunning kit dev
Run the standard development workflow (format, check, test):
kit devThis will:
- Format and check source files in
src/ - Type check examples in
examples/ - Run tests in
tests/with coverage
Checking Interpreter/Compiler Parity
Run parity checks for examples:
kit parity --failures-onlyUse --no-spinner in automation or logs:
kit parity --no-spinner --failures-onlyGenerating Documentation
Generate API documentation from doc comments:
kit docNote: Kit sources with doc comments (##) will generate HTML documents in docs/*.html.
Cleaning Build Artifacts
Remove generated files, caches, and build artifacts:
kit task cleanNote: Defined in kit.toml.
Local Installation
To install this package locally for development:
kit installThis installs the package to ~/.kit/packages/@kit/policy/, making it available for import as Kit.Policy in other projects.
License
This package is released under the 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}not-authorized
Create a not-authorized error
String -> Keyword -> String -> PolicyError
rule-not-found
Create a rule-not-found error
Keyword -> PolicyError
context-missing
Create a context-missing error
String -> PolicyError
policy-not-found
Create a policy-not-found error
String -> PolicyError
custom
Create a custom error
String -> PolicyError
message
Get human-readable message from error
PolicyError -> String
kind
Get error kind as keyword
PolicyError -> Keyword
is-not-authorized?
Check if error is not-authorized
PolicyError -> Bool
is-rule-not-found?
Check if error is rule-not-found
PolicyError -> Bool
FailureReason
A failure reason with source policy and action
Variants
FailureReason {policy, action, details}new
Create a failure reason
String -> Keyword -> FailureReason
with-details
Create a failure reason with details
String -> Keyword -> String -> FailureReason
policy
Get the policy name from a failure reason
FailureReason -> String
action
Get the action from a failure reason
FailureReason -> Keyword
details
Get details string from a failure reason
FailureReason -> String
format
Format failure reason as string
FailureReason -> String
AuthResult
Authorization result type
Variants
AllowedDenied {String}allowed
Create an allowed result
AuthResult
denied
Create a denied result with a reason
String -> AuthResult
is-allowed?
Check if result is allowed
AuthResult -> Bool
is-denied?
Check if result is denied
AuthResult -> Bool
reason
Get denial reason (empty string if allowed)
AuthResult -> String
to-result
Convert AuthResult to Result Bool PolicyError
String -> Keyword -> AuthResult -> Result Bool PolicyError
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