Gates
Gates are small, specific authorization checks you can reuse throughout Iridium. You define a gate with a name, and target(s), and then can call that gate throughtout your application to perform a permissions check.
INFO
If you need a set of permissions for your resource that map directly to traditional CRUD application and a particular model (rules for who can create, delete, edit, etc, a particular model), please see policies instead.
Creating a basic gate
To create a basic gate, you will provide a name and a callback function to the perform your permission check. That callback returns a bool indicating if the permission check was passed, or it failed.
You can do this by calling the DefineGate method as so:
import "github.com/iridiumgo/iridium/bootstrap/auth/gate"
gate.DefineGate("my-custom-gate", func(ctx context.CustomContext, args ...interface{}) bool {
// Your permission check here.
// return true or false based on the results
})context.CustomContext-> A general context interface. You can access the http request & writer objects, as well as your logged in user's model.args-> A list of arguments you provide when calling your gate. You can pass anything in here, but you are required to cast them to use them.
Call a gate
You can call a gate simply with:
import "github.com/iridiumgo/iridium/bootstrap/auth/gate"
gate.Call(ctx, "my-custom-gate", "Joe", 25)Explanation:
ctx-> thecontext.CustomContextinterface. You can provide any of Iridium's contexts here."my-custom-gate"-> The name of your gate you defined elsewhere"Joe"-> A string argument used in your gate's definition25-> A random int used in your gate's definition
Gate arguments
Gate arguments can by anything since they map directly to ...interface{}. You'll want to cast them to use them properly in your definition however.
Gate without arguments
If you do not require arguments for your gate, you can instead call the DefineSimpleGate method.
gate.DefineSimpleGate("my-custom-gate", func(ctx context.CustomContext) bool {
// No arguments required for your gate
})Calling a Simple Gate
You can call a simple gate the same as a traditional gate. All arguments are ignored, so you can set these to nil.
gate.Can(ctx, "my-custom-gate", nil)Gate Example
Here is an example of defining and using a gate.
A traditional gate example
In this example, we're defining a custom gate to check if the currently logged in user can rent a car.
To be able to rent a car, you need to:
- Have the
adminrole - Be above the age of 25
Notes:
- Please not that in this example we're casting the
ctx.User()method to a custommodels.User. Thatmodels.Useris not an iridium type. That is your custom user struct you define. You need to register it to perform this cast. Please see more here
import "github.com/iridiumgo/iridium/bootstrap/auth/gate"
// Defining a gate that makes sure a user has the `admin` role, and is over a certain age
gate.DefineGate("rent-car", func(ctx context.CustomContext, args ...interface{}) bool {
// Get the age from the first gate argument (must cast to an int)
age := args[0].(int)
// Get the value of the current user in our ctx.
// Cast that user to your custom model struct to access its fields
user := ctx.User().(*models.User)
if user.Role != "admin" {
return false
}
if user.Age <= age {
return false
}
return true
})
// form.go <- An example form
Form().
Schema(
FormInput("age").
Decimal(),
// Users should only be able to see this if they are an Admin and over 25
FormSwitch("rent-a-car").
HiddenFn(func (ctx FormContext) bool {
// Get the age the user entered
selectedAge, err := ctx.Get("age")
// If err or age is empty, then hide this switch
if err != nil || selectedAge == "" {
return false
}
var ageInt int
ageInt, err = strconv.Atoi(s)
// If age is not a integer, then return false
if err != nil {
return false
}
// check if the current user can rent a car
return gate.Can(ctx, "rent-car", ageInt)
}),
)Typing Gates
Iridium offers methods when defining gates to statically type your expected context, user model, and an argument. There is some important information here about how Iridium handles this.
Fully typing gates does not allow for variadic arguments. You need to provide one argument struct in order to preserve your typing.
DefineTypedGate
You can fully type your gate definition with the DefineTypedGate method as so:
gate.DefineTypedGate[Ctx, Model, Argument]("custom-gate", func(ctx Ctx, model Model, arg Argument)bool{
// You know have access to a typed context, model, and argument in here
})DefineSimpleTypedGate
If your gate does not require arguments but you still want to enforce the context and user type, you can do so as follows:
gate.DefineSimpleTypedGate[Ctx]("simple-custom-gate", func(ctx Ctx) bool {
// You now have access to a typed context in here.
})Naming
Gate names must be unique across your application.
Gate Methods
Can
Can will check whether the current user is authorized for an ability.
Cannot
Cannot will check whether the current user is not authorized for an ability.
All
All will check whether the current user is authorized for all listed abilities
All will apply the same user and arguments to each listed ability. If you require different arguments for each ability, you'll need to write your own implementation.
Inspect
Inspect will return additional information in the form of a Response object about the particular check.
This is useful for debugging to see where, either via a policy, gate, or hook, your authorization check passed or failed.
HasAbilities
HasAbilities will check to see if the user can pass all listed checks. This short-circuits on a failure.
HasAbilities will apply the same user and arguments to each listed ability. If you require different arguments for each ability, you'll need to write your own implementation.