pundit
Version, currently 0.6.011 versions
github.com/LuckyCasts/pundit
A simple authorization library for Lucky Crystal apps
19 stars
0 dependents
License: MIT
Nothing has been indexed for 0.6.0 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
pundit:
github: LuckyCasts/pundit
version: ~> 0.6.0Then run:
shards installshard.yml
No shard.yml has been indexed for 0.6.0. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
# Pundit

[](https://stephendolan.github.io/pundit)
[](https://github.com/stephendolan/pundit/releases)
A simple Crystal shard for managing authorization in [Lucky](https://luckyframework.org) applications. Intended to mimic the excellent Ruby [Pundit](https://github.com/varvet/pundit) gem.
## Lucky Installation
1. Add the dependency to your `shard.yml`:
```yaml
# shard.yml
dependencies:
pundit:
github: stephendolan/pundit
```
1. Run `shards install`
1. Require the shard in your Lucky application
```crystal
# shards.cr
require "pundit"
```
1. Require the tasks in your Lucky application
```crystal
# tasks.cr
require "pundit/tasks/**"
```
1. Require a new directory for policy definitions
```crystal
# app.cr
require "./policies/**"
```
1. Include the `Pundit::ActionHelpers` module in `BrowserAction`:
```crystal
# src/actions/browser_action.cr
include Pundit::ActionHelpers(User)
```
1. Run the initializer to create your `ApplicationPolicy` if you don't want [the default](src/pundit/application_policy.cr):
```sh
lucky pundit.init
```
## Usage
### Creating policies
The easiest way to create new policies is to use the built-in Lucky task! After following the steps in the Installation section, simply run `lucky gen.policy Book`, for example, to create a new `BookPolicy` in your application.
Your policies must inherit from the provided [`ApplicationPolicy(T)`](src/pundit/application_policy.cr) abstract class, where `T` is the model you are authorizing against.
For example, the `BookPolicy` we created with `lucky gen.policy Book` looks like this:
```crystal
class BookPolicy < ApplicationPolicy(Book)
def index?
false
end
def show?
false
end
def create?
false
end
def update?
false
end
def delete?
false
end
end
```
The following methods are provided in [`ApplicationPolicy`](src/pundit/application_policy.cr):
| Method Name | Default Value |
| ----------- | ------------- |
| `index?` | `false` |
| `show?` | `false` |
| `create?` | `false` |
| `new?` | `create?` |
| `update?` | `false` |
| `edit?` | `update?` |
| `delete?` | `false` |
### Authorizing actions
Let's say we have a `Books::Index` action that looks like this:
```crystal
class Books::Index < BrowserAction
get "/books/index" do
html IndexPage, books: BookQuery.new
end
end
```
To use Pundit for authorization, simply add an `authorize` call:
```crystal
class Books::Index < BrowserAction
get "/books/index" do
authorize
html IndexPage, books: BookQuery.new
end
end
```
Behind the scenes, this is using the action's class name to check whether the `BookPolicy`'s `index?` method is permitted for `current_user`. If the call fails, a `Pundit::NotAuthorizedError` is raised.
The `authorize` call above is identical to writing this:
```crystal
BookPolicy.new(current_user).index? || raise Pundit::NotAuthorizedError.new
```
You can also leverage specific records in your authorization. For example, say we have a `Books::Update` action that looks like this:
```crystal
post "/books/:book_id/update" do
book = BookQuery.find(book_id)
SaveBook.update(book, params) do |operation, book|
redirect Home::Index
end
end
```
We can add an `authorize` call to check whether or not the user is permitted to update this specific book like this:
```crystal
post "/books/:book_id/update" do
book = BookQuery.find(book_id)
authorize(book)
SaveBook.update(book, params) do |operation, book|
redirect Home::Index
end
end
```
### Authorizing views
Say we have a button to create a new book:
```crystal
def render
button "Create new book"
end
```
To ensure that the `current_user` is permitted to create a new book before showing the button, we can wrap the button in a policy check:
```crystal
def render
if BookPolicy.new(current_user).create?
button "Create new book"
end
end
```
### Overriding the User model
If your application doesn't return an instance of `User` from your `current_user` method, you'll need to make the following updates (we're using `Account` as an example):
- Run `lucky pundit.init --user-model {Account}`, or modify your `ApplicationPolicy`'s `initialize` content like this:
```crystal
abstract class ApplicationPolicy(T)
getter account
getter record
def initialize(@account : Account?, @record : T? = nil)
end
end
```
- Update the `include` of the `Pundit::ActionHelpers` module in `BrowserAction`:
```crystal
# src/actions/browser_action.cr
include Pundit::ActionHelpers(Account)
```
### Handling authorization errors
If a call to `authorize` fails, a `Pundit::NotAuthorizedError` will be raised.
You can handle this elegantly by adding an overloaded `render` method to your `src/actions/errors/show.cr` action:
```crystal
# This class handles error responses and reporting.
#
# https://luckyframework.org/guides/http-and-routing/error-handling
class Errors::Show < Lucky::ErrorAction
DEFAULT_MESSAGE = "Something went wrong."
default_format :html
# Capture Pundit authorization exceptions to handle it elegantly
def render(error : Pundit::NotAuthorizedError)
if html?
# We might want to throw an appropriate status and message
error_html "Sorry, you're not authorized to access that", status: 401
# Or maybe we just redirect users back to the previous page
# redirect_back fallback: Home::Index
else
error_json "Not authorized", status: 401
end
end
end
```
## Contributing
1. Fork it (<https://github.com/stephendolan/pundit/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## Contributors
- [Stephen Dolan](https://github.com/stephendolan) - creator and maintainer
## Inspiration
- The [Pundit](https://github.com/varvet/pundit) Ruby gem was what formed my need as a programmer for this kind of simple approach to authorization
- The [Praetorian](https://github.com/ilanusse/praetorian) Crystal shard took an excellent first step towards proving out the Pundit model in Crystal
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.6.0- Tagged
- Jan 7, 2026
- Commit
e6edb06614e1- Indexed
- not yet
Dependents
No indexed shard depends on this one yet.
Repository
github.com/LuckyCasts/pundit
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 12, 2026
- Synced
- Aug 12, 2026
- Versions
- 11