Let's say I am trying to follow the Elm architecture and split my workflow into Users and Invoices while using StartApp.
Users have invoices, but they must log in to access them.
The model may look like something along these lines:
type Model
= NotLoggedIn Credentials
| LoggedIn RealName (Maybe Invoices)
type alias State =
{ login : Model
, notification : ......
, ......
type alias Invoices = { invoices: List Invoice, ...... }
User module has actions:
type Action
= Login (Result Http.Error String)
| Logout
| Submit
...
and update function:
update : Action -> Model -> (Model, Effects Action, Notification)
update action user =
case (action, user) of
(Login res, _) ->
case res of
Ok name ->
(LoggedIn name Nothing, Effects.none, Info "Welcome!")
...
I skip the details of authentication, it's all good. The interesting part is Login action. The tuple is sent to the step function in main:
step : Action -> State -> (State, Effects Action)
step action state =
case action of
UserAction a ->
let (newstate, ef, n) = User.update a state.login
in ({ state | login = newstate, notification = n }, Effects.map UserAction ef)
InvoiceAction a -> ......
So the user has logged in. Next we want to call some init action in the Invoice module.
But how should this be done properly? How to initiate the action of Invoice to preserve incapsulation? Shall I return sometihng other than Effects.none?
This might be a case that could be solved by different modeling of your app's data.
The way I understand it, you have actions that require as user and actions that do not require an user. The InvoiceAction seams to me that it should belong to the UserAction.
So, you could have
type MainAction = UserAction UAction | NonUserAction NonUAction
type UAction = AuthAction Credentials | InvoiceAction Invoice.Action
The user model would encapsulate both login details and invoice details. And then, after a successful log in you could redirect to an InvoiceAction.
update action model =
case action of
AuthAction credentials ->
let
(isLoggedIn, notifications) = Authentication.check credentials
model' = { model | credentials = credentials, notifications = notifications}
in
if isLoggedIn
then update (Invoice.initialize model'.credentials) model'
else (model', Effects.none)
InvoiceAction act ->
let
(invoices, fx) = Invoice.update model.credentials act model.invoices
in
({model | invoices = invoices}, Effects.map InvoiceAction fx)
The actual action is provided by the Invoice module via a function initialize with a signature like initialize: Credentials -> Action. This is done to maintain encapsulation. The User module need not know about particular Invoice actions, only that there is one related to initialization and it can get it via that function.
Also, please note that I've simplified the update signature and moved the notifications inside the model. This is a personal preference as I see notifications as nothing special. They are like any other piece of data in the model. Of course, if the notifications are tasks that get routed via some custom StartApp into a port and get displayed by some JS mechanism, it might make sense to keep them in the return.
One approach will be:
Create a mailbox in the parent component
Pass the address of that mailbox to User update
User update returns an effect that sends a message to this address
Upon receiving a message this mailbox triggers an action that flows to Invoice
This chapter in elm-tutorial shows this pattern.
Related
I'm building an application in Elm where most API calls are protected; i.e. the user needs to be logged in for the API call to work. If the user is not logged in, they will receive a 401 Unauthorized response. I want the application to redirect to the login page if any response is a 401.
Currently, I only have this redirect set up for a single API call. Here is a stripped-down version of the code to give an idea of how it's set up:
-- Util/Api.elm
type alias Data data =
{ data : data
}
-- Resources/Expense.elm
getExpenses : (Progress (Api.Data (List Expense)) -> msg) -> Sub msg
getExpenses msg =
(dataDecoder expenseListDecoder)
|> Http.get expensesEndpoint
|> Progress.track expensesEndpoint msg
-- Main/Msg.elm
type Msg
= ExpenseListMsg ExpenseListMsg
| RedirectToLogin
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ExpenseListMsg msg ->
ExpenseList.Update.update msg model
GoTo path ->
model ! [ Navigation.newUrl path ]
RedirectToLogin ->
model ! [ Navigation.load "path/to/login" ]
-- ExpenseList/Msg.elm
type ExpenseListMsg
= GetExpensesProgress (Progress (Api.Data (List Expense)))
| SetLoading
-- ExpenseList/Update.elm
update : ExpenseListMsg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetLoading ->
{ model | expenses = setExpensesLoading model.expenses } ! []
GetExpensesProgress (Done { data }) ->
{ model | expenses = addExpenses model.expenses data } ! []
GetExpensesProgress (Fail (BadStatus { status })) ->
case status.code of
401 ->
model ! [ msgToCmd RedirectToLogin ]
_ ->
model ! []
GetExpensesProgress (Fail error) ->
model ! []
GetExpensesProgress progress ->
{ model | expenses = setExpensesLoading model.expenses } ! []
Essentially, I want to move the logic around 401 responses from ExpenseList/Update.elm up to Main/Update.elm so that I can use it for any request I want.
I attempted a number of things, but nothing quite worked with Elm's type system. For example, one thing I wanted to do was try to do a nested pattern match with missing specificity in the middle, e.g.:
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ApiCall (messageType (msg (Fail (BadStatus { status })))) ->
case status of ->
. . .
. . .
I was hoping something like this would work and would match a message that looked like: ApiCall (ExpenseListMsg (GetExpensesProgress (Fail (BadStatus)))). Unfortunately, it's not proper Elm syntax, so my code didn't compile.
How can I write something that will allow me to mark an API call as protected and catch 401 errors at the top level in Main.Update.update?
Currently, the API call is encapsulated by the ExpenseList/Update module. This encapsulation is what makes the API call results unavailable to the Main module. The interaction works like this: Main -> FeatureModule -> API
Because the API is what provides the information needed to determine whether the app should redirect to the login page, and you want the Main module to perform the redirect, the Main module needs access to the API. Hence, the encapsulation needs to go. Instead, you can:
Have an API module which provides the low-level API functionality by producing Tasks. Unlike producing Cmds, this allows the caller, such as the Main module to decide how to handle the result of the Task, which can be acquired by converting the Task to a Cmd and giving it to the Elm runtime for execution.
Have the ExpenseList.Update module use the API module to create Tasks.
With this arrangement:
The Main module sends high-level commands to a feature module, which then uses the API module to produce the low-level instructions, which are then provided to the Main module.
The Main module doesn't need to care what those low-level instructions are, it simply converts the Task to a Cmd and waits for the result.
When the result comes back it's in a low-level format (ex. Success/Fail). At this point the Main module can jump in and handle the redirect on a 401 error. Otherwise, it can pass the result to a feature module so it may handle the result.
I'm trying to see if it's possible in an ASP.NET-Core 2 web app, that if a User is authenticated in a request, we can also check in some Filter/ActionMethod Attribute:
They have a specific claim
The route has an string id segment (e.g. HttpPut[("{id}")] ) and that id segment needs to match the Auth'd User's Id.
Request includes a JWT header with the bearer token in it, which is used to 'create' the Authenticated Identity (which works 100% fine).
e.g.
HTTP PUT /accounts/PureKrome | User Id:PureKrome | Claim: Irrelivant. => Can continue. [You are updating yourself. Don't need any special claim when updating yourself].
HTTP PUT /accounts/PureKrome | User is Anonymous or Id:SomethingElse | Claim: irrelivant => Failure (Forbidden response) [Someone else is trying to update you and doesn't have the correct overriding claim. So fail]
HTTP PUT /accounts/SomeoneElse | User is Id:PureKrome | Claim: correct claim. => Can continue [Trying to update a different user BUT you have a claim that allows you to do that]
Right now, I do this in my ActionMethod code ... one of the first things. So I was just curious to see if this could be achieved using an Attribute that decorates the ActionMethod, instead.
That isn’t actually too complicated. All you need to do is have an authorization filter that looks at the route values and then checks it with the current user.
Something simple like this should already work fine:
public class ValidateUserIdRouteAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
var requestedUserId = context.RouteData.Values["id"] as string;
var currentUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);
if (requestedUserId != currentUserId &&
!user.HasClaim(c => c.Type == "may-edit" && c.Value == requestedUserId))
{
context.Result = new UnauthorizedResult();
}
}
}
And used on a route it would look like this:
[ValidateUserIdRoute]
[HttpGet("/account/update/{id}")]
public IActionResult UpdateAccount(string id)
{
// …
}
That’s all. If you have authentication set up properly, the Bearer token will be used to authenticate the user which may or may not set up the claims properly, and then you just check against those claims to see if accessing the route is allowed or not.
Of course, you can expand on this idea and add some more functionality to it, e.g. support different route data keys or something like that.
Since i'm new to CakePHP, I have simple problems I cannot figure out.
I use CakePHP 3.4. I try to write a simple logger functionality. Every change applied to a record, I want to be logged to the ChangeLog model.
Using afterSave() event, I have following code:
public function afterSave($event, $entity, $options) {
$logTable = TableRegistry::get('ChangeLogs');
foreach ($entity->getDirty() as $key) {
if($key != 'modified') {
$record = $logTable->newEntity();
$record->previous_value = $entity->getOriginal($key);
$record->new_value = $entity[$key];
$record->table_name = 'Stars';
$record->column_name = $key;
$record->row_id = $entity->id;
$record->user_id = [what should i put here?]
$record->user_id = $_SESSION['Auth']['user']['id'];
$logTable->save($record);
}
}
It works well, but I also want to know which user performed operation and I don't know how can I obtain current user in the Model.
I try to avoid passing argument in controller, because I want user to be detected automaticly, and as a developer I don't want to remember about it every time I try change/add new functionalities in controller.
Do not fiddle with superglobals directly in CakePHP, this will surely bite you at some point, especially in the test environment! Always use the abstracted methods (like the session object) to access such data!
That being said, you could use events to inject the current user into the model callback/event flow. For example register globally to Model.afterSave, and pass the current user into the options.
Here's a basic example to demonstrate the principle. Imagine somthing like this in your app controller:
use Cake\Datasource\EntityInterface;
use Cake\Event\Event;
use Cake\Event\EventManager;
// ...
public function initialize()
{
parent::initialize();
// ...
EventManager::instance()->on(
'Model.afterSave',
['priority' => -1],
function (Event $event, EntityInterface $entity, \ArrayObject $options) {
// retrieve the user id from the auth component
$options['user_id'] = $this->Auth->user('id');
}
);
}
Given the priority of -1 (the default priority is 10) it will be invoked before the model callback for that event, so that in your table class you'll have access to user_id via the $options argument.
$record->user_id = $options['user_id'];
For something more reusable you'd probably use a custom listener class. Also check out events like Auth.afterIdentify, Model.initialize, and Controller.intialize/startup, these could be leaveraged to register your model events listener and to retrieve the current user.
See also
Awesome CakePHP > Auditing / Logging
Cookbook > Events System
Cookbook > Events System > Registering Listeners
Cookbook > Events System > Establishing Priorities
Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks
Cookbook > Controllers > Request Life-cycle Callbacks
This solution seems to allow you to pass the logged in user into the model layer:
https://github.com/UseMuffin/Footprint
It is not hooked into the model layer through events like the solution above.
I am just starting out learning Elm and I'm having some trouble understanding why I'm getting a type mismatch when passing a custom type into a method that expects... well, what I'm calling a partial type annotation.
Here's the code I'm using:
import Graphics.Element exposing (show)
import Debug
type User =
User { username : String, followers : List User }
type Action = Follow
fromJust : Maybe a -> a
fromJust x = case x of
Just y -> y
Nothing -> Debug.crash "error: fromJust Nothing"
update : User
-> Action
-> { user | followers : List User }
-> { user | followers : List User }
update actor action user =
case action of
Follow -> { user | followers = user.followers ++ [actor] }
getUsers : List User
getUsers =
[
User { username = "UserA", followers = [] },
User { username = "UserB", followers = [] }
]
main =
let
users = getUsers
first = fromJust (List.head users)
last = fromJust (List.head (List.reverse users))
in
show (update first Follow last)
And the error output from elm-lang.org/try:
Type Mismatch
The 3rd argument to function update is causing a mismatch.
43| show (update first Follow last)
Function update is expecting the 3rd argument to be:
{ a | followers : List User }
But it is:
User
Hint: I always figure out the type of arguments from left to right. If an
argument is acceptable when I check it, I assume it is "correct" in subsequent
checks. So the problem may actually be in how previous arguments interact with
the 3rd.
If I change the type annotation for update to expect a User instead, I get a different Type Mismatch, saying I should change the types back. :confused:
It's because of the recursive type. Elm doesn't seem to have particularly intuitive (to a Haskeller anyway) handling of these, and you have to put some explicit unwraps and wraps in there to make it all work. I assume the compiler guided you to the definition of the User type that you have, as it did me when I started playing around with it.
The result of it all is though, values of type User in your example aren't records, they're data constructors which take records as a parameter. You have to pattern match against those values in order to get the record out, and remember to include the constructor whenever you return such a value. Fortunately Elm has good pattern matching support or this would be horrific.
So if you change your update function to
update : User
-> Action
-> User
-> User
update actor action (User user) =
case action of
Follow -> User { user | followers = user.followers ++ [actor] }
it all works. Note that the parameters are now all User types, and the user parameter is being deconstructed so we can get at the record inside. Finally, the result is reconstructed with the User data constructor.
In my app that based on the StartApp package I have a port to communicate from inside the to JS. At the moment I call this port using a mailbox
requestPalette :
{ address : Signal.Address String
, signal : Signal String
}
requestPalette = Signal.mailbox ""
requestPaletteFilter : Signal String
requestPaletteFilter =
Signal.filter (String.isEmpty >> not) "" requestPalette.signal
|> settledAfter (300 * Time.millisecond)
port request : Signal String
port request = requestPaletteFilter
and using it like this:
[on "input" targetValue (\str -> Signal.message requestPalette.address str)
I wonder if there is a way to this inside of the update function instead of sending the message from the view.
This applies to elm 0.16 (and before), in elm 0.17 subscriptions have changed into ports
In order to send a signal to a mailbox from an update, you'll need to use StartApp as opposed to StartApp.Simple, since the former allows for Effects in the update function.
At a bare minimum, you're going to probably have an Action like this, which defines a No-Op and an action for sending the string request:
type Action
= NoOp
| SendRequest String
Your update function will now include something like the following case for the new SendRequest action. Since you're using StartApp, which deals in Effects, you must call Effects.task, and the Task you're mapping to an Effect must be of type Action, which is why we have the Task.succeed NoOp return value.
update action model =
case action of
NoOp ->
(model, Effects.none)
SendRequest str ->
let
sendTask =
Signal.send requestPalette.address str
`Task.andThen` (\_ -> Task.succeed NoOp)
in
(model, sendTask |> Effects.task)
Now your click event handler in the view can go back to using the address passed into the view:
[ on "input" targetValue (Signal.message address << SendRequest) ]
I've got a working example of the above in this gist. You'll just need to subscribe to the request port in javascript to see it in action.