All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
3.0.2 - 2026-08-09
Changed
- Require spek ~> 0.4.0.
- Add the check module under the
check_modulekey to the metadata of the warning that is logged when a permission is checked for a rule that does not exist.
Fixed
- The warning for a rule that does not exist logged the check module under the
policy_modulemetadata key. It logs the policy module now.
3.0.1 - 2026-06-05
Changed
- Require spek ~> 0.3.0.
3.0.0 - 2026-05-27
Added
- Add
LetMe.Policy.fetch_expression/1. - Add
LetMe.Policy.fetch_expression!/1. - Add
LetMe.Policy.get_expression/1.
Changed
- Extract the expression structs, the evaluation logic and the optimization
logic introduced in LetMe 2.0.0 into a separate library called
Spek, and use that library instead.
How to upgrade
The DSL and callback functions are unchanged compared to version 2.0.0. Only
the representation of expressions under the expression key in the
LetMe.Rule and LetMe.UnauthorizedError structs was changed to use the
Spek structs.
LetMe.AllOf->Spek.AllOfLetMe.AnyOf->Spek.AnyOfLetMe.Check->Spek.CheckLetMe.Literal->Spek.LiteralLetMe.Not->Spek.Not
The structs have mostly the same structure, except for these differences:
- All structs:
passed?->satisfied? Spek.Literal: additionalresultkeySpek.Check:name->fun- additional
modulekey, which contains the name of the check module. arg->args- Because of the way Spek maps the evaluation context to function arguments,
the
argskey holds a list in the format[{:ctx, :subject}, {:ctx, :object}, arg], whereargis the value that was previously under theargkey.
2.0.0 - 2026-03-31
Added
- Add
LetMe.AllOf,LetMe.AnyOf,LetMe.Check,LetMe.Literal, andLetMe.Notstructs andt:LetMe.expression/0type to represent policy expressions. - Add
erroroption touse LetMe.Policy,LetMe.Policy.authorize/4to switch between error structs without evaluation details, error structs with evaluation details, and arbitrary custom error values. - Support check matcher function as argument for
LetMe.filter_rules/2andLetMe.Policy.list_rules/1.
Changed
- Evaluate authorization checks lazily.
- Change
LetMe.Policy.authorize/4to always return an{:error, LetMe.UnauthorizedError.t()}tuple when authorization checks fail. - Add
expressionfield toLetMe.UnauthorizedError.t(), which contains the policy expression and evaluation results until a decision was made. - Replace
allowanddenyfields onLetMe.Rulestruct with a singleexpressionfield that contains a combined logical expression. - Optimize the combined logical expression at compile time.
- Support check functions that return
:ok,:error,{:ok, term}, or{:error, term}. These return values can be read from the expression in theLetMe.UnauthorizedErrorstruct. - Replace the
allowanddenyoptions inLetMe.filter_rules/2andLetMe.Policy.list_rules/1with a singlecheckoption.
Removed
- Remove
error_reasonanderror_messageoptions fromLetMe.Policy.
How to upgrade
Replace the error_reason and error_message options with the error option:
- use LetMe.Policy, error_reason: :forbidden, error_message: "Forbidden"
+ use LetMe.Policy, error: :forbiddenYou can opt-in to detailed error structs by setting the value to :detailed
or simple error structs by setting the value to :simple.
use LetMe.Policy, error: :detailedIf you do that, change all pattern matches on {:error, :unauthorized} or your
custom error reason and update your type specifications accordingly.
@spec update_article(Scope.t(), Article.t(), map) ::
- {:ok, Article.t()} | {:error, :unauthorized}
+ {:ok, Article.t()} | {:error, LetMe.Unauthorized.t()}
def update_article(scope, article, params)
with MyApp.Policy.authorize(:article_update, scope, article) do
# ...
end
end
case update_article(scope, article, params) do
{:ok, article} ->
# ...
- {:error, :unauthorized} ->
+ {:error, %LetMe.UnauthorizedError{}} ->
# ...
endReplace the allow and deny option in LetMe.filter_rules/2 and
LetMe.Policy.list_rules/1 with the check option. The value is unchanged.
- MyApp.Policy.filter_rules(allow: {:role, :admin})
+ MyApp.Policy.filter_rules(check: {:role, :admin})
- MyApp.Policy.filter_rules(deny: :suspended)
+ MyApp.Policy.filter_rules(check: :suspended)If you were working directly with the allow and deny fields of the
LetMe.Rule struct, update your code to work with the expression field and
t:LetMe.expression/0 type instead.
1.2.5 - 2025-03-26
Changed
- Improve documentation.
1.2.4 - 2024-04-22
Fixed
- Redacting nested lists within structs resulted in a
CaseClauseError.
1.2.3 - 2023-11-11
Changed
- Update documentation.
1.2.2 - 2023-06-28
Added
- Add
error_messageoption touse LetMe.Policyto override the exception message used byLetMe.Policy.authorize!/4, e.g.use LetMe.Policy, error_message: "Not today, chap.".
1.2.1 - 2023-06-28
Added
- Define
actiontype when youuse LetMe.Policy. - Add type specifications for generated
authorizefunctions.
1.2.0 - 2023-06-19
Added
- Add an optional
optsargument to the authorize functions, so that additional options can be passed to pre-hooks. - Support filtering by metadata in
LetMe.filter_rules/2.
Changed
- Expect pre-hook options to be passed as a keyword list.
Fixed
- Fix deprecation warning about
Logger.warn/2in Elixir 1.15.
1.1.0 - 2023-05-08
Added
- Add a
metadatamacro to add metadata to actions. The metadata can be read from theLetMe.Rulestruct.
1.0.3 - 2023-03-21
Changed
- Update
ex_docand other dev dependencies.
1.0.2 - 2023-01-05
Added
- Add a cheat sheet for rules and checks.
Fixed
- A code example for rule introspection in the readme was wrong.
1.0.1 - 2022-11-06
Fixed
LetMe.redact/3raised aKeyErrorwhen you passed options without theredact_valueoption.
1.0.0 - 2022-11-06
Added
- Add
LetMe.Policy.filter_allowed_actions/3andLetMe.filter_allowed_actions/4. - Add
LetMe.Policy.get_object_name/1.
Changed
- Rename
c:LetMe.Policy.authorized?/3toc:LetMe.Policy.authorize?/3, because consistency is more important than grammar, maybe. - Replace the
c:LetMe.Schema.scope/2callback withLetMe.Schema.scope/3. The__using__macro defined default implementations for both functions that returned the given query unchanged, in case you only needed theredactcallback of the behaviour. In practice, this made it all too easy to call the 2-arity version when only the 3-arity version was defined, and vice versa, which would lead the query to not be scoped. So in order to reduce the room for error at the cost of a minor inconvenience, you will now always need to implement the 3-arity function, even if you don't need the third argument. - Replace
c:LetMe.Schema.redacted_fields/2withLetMe.Schema.redacted_fields/3to allow passing additional options, and to be consistent withLetMe.Schema.scope/3.
0.2.0 - 2022-07-12
Added
- Support nested field redactions, either by explicitly listing the fields or by
referencing a module that also implements
LetMe.Schema.
Fixed
reject_redacted_fields/3called theredact/2callback with the wrong argument order.
0.1.0 - 2022-07-11
Added
- Initial release.