This package aims to provide a guard mechanism to a project using go_router
and riverpod
.
Main usage is to enhance the state based declarative routing with certain subtrees being protected by some logic.
A simple abstract class that the specific guards extend and has to override its passes(): bool
function.
A GoRouter
proxy. This has a routes: List<RouteBase>
just like the GoRouter
.
It accepts not only the standard routes (GoRoute
and ShellRoute
) but also some custom route types defined by this package:
GuardAwareGoRoute
(extendsGoRoute
)- there is a simple
goRoute
function which returns aGuardAwareGoRoute
, to make it easy to switch fromGoRoute
togoRoute
- there is a simple
GuardShell<T>
andDiscardShell<T>
(extendsShellRoute
)
A helper class to supply to the GoRouter
's refreshListenable
.
Providers (only always alive ones) can be given to it, whom notifications are triggering the reevaluation of the current route.
-
A certain subtree can be "protected" by a guard. (only allow
/dashboard
whenAuthGuard
passes)- can be defined via
GuardShell<T>([...routes])
- can be defined via
-
All guards must have one assigned route which is the shield route. A "shield" is where the guard can be resolved. (eg: solving the requirement of
AuthGuard
can be done on the/login
path)- can be defined via the
GuardAwareGoRoute
'sshieldOf: Type
parameter - if missing then
ShieldRouteMissingException
is thrown, - if multiple shield path are defined for a guard then
MultipleShieldRouteException
is thrown.
- can be defined via the
-
A certain subtree can be "discarded" by a guard. Meaning that some paths are irrelevant once a guard is passing. (eg
/get-started
or/signed-out
does not make sense whenAuthGuard
passes)- can be defined via
- the
GuardAwareGoRoute
'sdiscardedBy: List<Type>
parameter - or the
DiscardShell<T>([...routes])
shell route
- the
- In case of such discarding guard is defined for a subtree, then a "followUp" route has to be defined for said guard using the
followUp: Type
parameter. (otherwiseFollowUpRouteMissingException
is thrown)- the followUp route is used when
- the current route is discarded by all it's parent guards. (in other words all the guards which are defined to be discarding the current route are passing)
- the followUp route is used when
- In case there is a
followUp
route defined for a guard, but nodiscardedBy
orDiscardShell
, thenMissingDiscardingRouteForFollowUpException
is thrown.
- can be defined via
The routes
given to the GuardedGoRouter
are analyzed and based on what is their current context, each route's redirect
is set to a function (the explicitly defined route level redirect
has higher precedence) which implements above described logic.
The buildRouter
required parameter is a function which has to return the actual GoRouter
and in its params there are the routes
. These routes are the post processed routes that were given to the GuardedGoRouter
. (with the redirect
appending logic, and with pruning the GuardShell
and DiscardShell
routes)
When the user tries to access a path that is protected by some guards, then all guards must pass in order to end up on that path. If there is a guard which is not passing, then a redirect happens to first guard's shield route, with also appending the current path as continue
query parameter. (eg: user wants to go to /profile/settings
but the AuthGuard
throws them back to /login?continue=/profile/settings
)
This continue
saving logic can be disabled if not desired using:
GuardShell([...routes], savesLocation: false)
(defining on the guard)GuardAwareGoRoute(ignoreAsContinueLocation: false)
(defining on the route)- Invoking the navigation logic inside
guardedGoRouter.neglectContinue(() { ... })
NOTE: continue
is only appended as query param, if the path in question is not a shield path of some route.
## Caveat
Consider the following two scenarios:
- When the user tries to navigate to
/dashboard/:id
but it is protected byAuthGuard
. Then the system redirects the user to/login?continue=/dashboard/:id
which is desired in this case (user should get back to where they initially wanted to go once they log in successfully) - When the user is at the
/dashboard/:id
and clicks the logout button the redirecting should be triggered (via theGoNotifier
) and recognize that the current route is guarded, so it has to redirect the user to/login
although in this case appending thecontinue
parameter is undesired, since the user simply wanted to log out.
The redirect mechanism of go_router
doesn't really know anything about "prior" locations or the "direction" of the user (going from outside -> inside, or inside -> outside)
Because of this in some cases the url has to be cleared manually to discard this continue
behavior.