In web programming, a common pattern for form submission is:
How do I express this in Ocsigen?
I've been reading the documentation, but could not figure out how to accomplish this simple (and very common) task.
Example use case:
Suppose I have a login form that guards against unauthorized access to the admin panel (a page that should only be accessed by administrators). If the user provides the correct credentials at the login form, the user should be redirected to the admin panel. If the credentials are incorrect, the user should be redirected back to the login form. So far, I have successfully implemented this functionality.
However, I am at loss when it comes to implementing the following: if the user is already logged in and tries to access the login form, the user should be redirected to the admin panel. Also, if the user is not logged in and tries to access the admin panel, the user should be redirected to the login form.
Below is my code up the point I got stuck:
(* Secret login credentials. *)
let username = "admin"
let password = "123456"
(* Create Eliom services. *)
let login_form_service = Eliom_service.create
~path:(Eliom_service.Path ["login"])
~meth:(Eliom_service.Get Eliom_parameter.unit)
()
let login_service = Eliom_service.create_attached_post
~fallback:login_form_service
~post_params:Eliom_parameter.(string "user" ** string "pass")
()
let admin_panel_service = Eliom_service.create
~path:(Eliom_service.Path ["admin-panel"])
~meth:(Eliom_service.Get Eliom_parameter.unit)
()
let session_username : string option Eliom_reference.eref =
Eliom_reference.eref ~scope:Eliom_common.default_session_scope None
(* Register Eliom services. *)
let () = Eliom_content.Html.D.(
Eliom_registration.Html.register
~service:login_form_service
(fun () () -> Lwt.return
(Eliom_tools.D.html
~title:"Login"
(body [h1 [pcdata "Login"];
Form.post_form
~service:login_service
(fun (user, pass) ->
[fieldset
[label [pcdata "Username: "];
Form.input ~input_type:`Text
~name:user
Form.string;
br ();
label [pcdata "Password: "];
Form.input ~input_type:`Password
~name:pass
Form.string;
br ();
Form.input ~input_type:`Submit
~value:"Login"
Form.string
]]) ()])));
Eliom_registration.Redirection.register
~service:login_service
(fun () (user, pass) ->
if user = username && pass = password then (
Eliom_reference.set session_username (Some username);
Lwt.return (Eliom_registration.Redirection admin_panel_service))
else
Lwt.return (Eliom_registration.Redirection login_form_service));
Eliom_registration.Html.register
~service:admin_panel_service
(fun () () ->
(* Admin panel html here ... *))
);
What is the proper method of solving this problem?
Thank you.
I don't know if you found the solution as your question is kind of old by now but here is what I found:
I used "Registering services that decide what they want to send" from https://ocsigen.org/eliom/6.3/manual/server-outputs#redirections
The idea is to register you services with Eliom_registration.Any, this allows the handler attached to the service to decide on the fly the type of answer to provide by using the appropriate send function.
(* Secret login credentials. *)
let username = "admin"
let password = "123456"
(* Create Eliom services. *)
let login_form_service = Eliom_service.create
~path:(Eliom_service.Path ["login"])
~meth:(Eliom_service.Get Eliom_parameter.unit)
()
let login_service = Eliom_service.create_attached_post
~fallback:login_form_service
~post_params:Eliom_parameter.(string "user" ** string "pass")
()
let admin_panel_service = Eliom_service.create
~path:(Eliom_service.Path ["admin-panel"])
~meth:(Eliom_service.Get Eliom_parameter.unit)
()
let session_username : string option Eliom_reference.eref =
Eliom_reference.eref ~scope:Eliom_common.default_session_scope None
let login_panel () = Eliom_content.Html.D.(
Eliom_tools.D.html
~title:"Login"
(body [
h1 [pcdata "Login"];
Form.post_form
~service:login_service
(fun (user, pass) ->
[fieldset
[label [pcdata "Username: "];
Form.input ~input_type:`Text
~name:user
Form.string;
br ();
label [pcdata "Password: "];
Form.input ~input_type:`Password
~name:pass
Form.string;
br ();
Form.input ~input_type:`Submit
~value:"Login"
Form.string
]]) ()])
)
let admin_panel () = Eliom_content.Html.F.(
Eliom_tools.F.html
~title:"Fake Admin Panel"
(body [h1 [pcdata "Fake Admin Panel"];])
)
(* Some helper functions *)
let admin_credentials ~user_is_admin ~user_is_not_admin =
let%lwt usr = Eliom_reference.get session_username in
if usr = Some username then user_is_admin ()
else user_is_not_admin ()
let send_redirection service =
Eliom_registration.Redirection.send (Eliom_registration.Redirection service)
let send_page page =
Eliom_registration.Html.send page
(* Register Eliom services. *)
let () = Eliom_content.Html.D.(
Eliom_registration.Any.register
~service:login_form_service
(fun () () ->
admin_credentials
~user_is_admin:(fun () -> send_redirection admin_panel_service)
~user_is_not_admin:(fun () -> send_page (login_panel ()))
);
Eliom_registration.Redirection.register
~service:login_service
(fun () (user, pass) ->
if user = username && pass = password then (
let%lwt _ = Eliom_reference.set session_username (Some username) in
Lwt.return (Eliom_registration.Redirection admin_panel_service))
else
Lwt.return (Eliom_registration.Redirection login_form_service));
Eliom_registration.Any.register
~service:admin_panel_service
(fun () () ->
admin_credentials
~user_is_admin:(fun () -> send_page (admin_panel ()))
~user_is_not_admin:(fun () -> send_redirection login_form_service))
)