integrativesoft / lara

Lara Web Engine is a lightweight C# framework for web user interface development.
Apache License 2.0
153 stars 9 forks source link

[Question] How to do a proper Login page #98

Closed Seji64 closed 4 years ago

Seji64 commented 4 years ago

hi,

i am trying to handle a login with LaraUI. My "problem" is that the form submit overrides the "onclick" which i declared in Lara. When i set the button type to button then it works but then there serveral other issues. For example you can't login using the 'Enter' key cause browser will only "click" the button if the button type is submit

I tried to implement a WebService but i don't know how to handle the json response.

My LoginPage:

using Integrative.Lara;
using LAPS_WebUI.PageData;
using SimpleImpersonation;
using System;
using System.Threading.Tasks;

namespace LAPS_WebUI.Pages
{
    [LaraPage(Address = "/login")]
    class Login : IPage
    {

        internal LoginData m_logindata = new LoginData();

        public Task OnGet()
        {

            var thisDocument = LaraUI.Page.Document;

            if (UserSession.LoggedIn)
            {
                LaraUI.Page.Navigation.Replace("/laps");
            }
            else
            {
                Bootstrap.AppendTo(thisDocument.Head);
                FontAwesome.AppendTo(thisDocument.Head);

                thisDocument.Body.AddClass("text-center");

                var builder = new LaraBuilder(thisDocument.Body);

                builder
                    .Push("div", "card mx-auto mt-5")
                        .Attribute("style", "width: 24rem;")
                        .Push("article", "card-body")
                            .Push("h4", "card-title text-center mb-4 mt-1")
                                .InnerText("Sign in")
                            .Pop()
                            .Push("i", "fas fa-user-circle fa-5x")
                            .Pop()
                            .Push("hr")
                            .Pop()
                            .Push("div", "alert alert-warning text-center")
                                .Attribute("id", "loginmessage")
                                .Attribute("role", "alert")
                                .FlagAttribute("hidden", true)
                            .Pop()
                            .Push("form")
                                .Attribute("id","loginForm")
                                .Attribute("action", "/login")
                                .Attribute("method", "post")
                                .Push("div", "form-group")
                                    .Push("div", "input-group")
                                        .Push("span", "input-group-text")
                                            .Push("i", "fa fa-user")
                                            .Pop()
                                        .Pop()
                                        .Push("input", "form-control")
                                            .Attribute("type", "email")
                                            .Attribute("placeholder", "E-Mail or login")
                                            .Attribute("name", "email")
                                            .BindInput("value", m_logindata, x => x.Username)
                                        .Pop()
                                    .Pop() // Input Group
                                .Pop() // Form-Group
                                .Push("div", "form-group")
                                    .Push("div", "input-group")
                                        .Push("span", "input-group-text")
                                            .Push("i", "fa fa-key")
                                            .Pop()
                                        .Pop()
                                        .Push("input", "form-control")
                                            .Attribute("type", "password")
                                            .Attribute("placeholder", "Password")
                                            .Attribute("name","password")
                                            .BindInput("value", m_logindata, x => x.Password)
                                        .Pop()
                                    .Pop() // Input Group
                                .Pop() // Form-Group
                                .Push("div", "form-group")
                                    .Push("button", "btn btn-primary btn-block")
                                        .Attribute("id", "loginbutton")
                                        .Attribute("type","submit")
                                        .InnerText("Login")
                                        .On(new EventSettings
                                        {
                                            EventName = "click",
                                            Block = true,
                                            LongRunning = true,
                                            BlockOptions = new BlockOptions
                                            {
                                                ShowHtmlMessage = "Please Wait while logging in",

                                            },
                                            Handler = OnLoginSubmit
                                        })
                                    .Pop() // Button
                                .Pop() // form-group
                            .Pop() //form
                        .Pop()
                    .Pop();
            }

            return Task.CompletedTask;
        }

        private Task OnLoginSubmit()
        {
            var loginMessage = LaraUI.Document.GetElementById("loginmessage");
            bool authResult = false;

            try
            {

                if (string.IsNullOrWhiteSpace(m_logindata.Username) || string.IsNullOrWhiteSpace(m_logindata.Password))
                {
                    throw new Exception("Empty fileds");
                }

                var credentials = new UserCredentials(m_logindata.Username, m_logindata.Password);
                authResult = Impersonation.RunAsUser(credentials, LogonType.Interactive, () =>
                {
                    // do whatever you want as this user.
                    return true;
                });

                if (!authResult)
                {
                    loginMessage.InnerText = "Login failed - Wrong Username or Password";
                    loginMessage.SetFlagAttribute("hidden", false);
                }
            }
            catch (Exception)
            {
                loginMessage.InnerText = "Login failed";
                loginMessage.SetFlagAttribute("hidden", false);
            }
            finally
            {
                if (authResult)
                {
                    UserSession.LoggedIn = true;
                    UserSession.loginData = m_logindata;
                    LaraUI.Page.JSBridge.Submit(@"window.location.replace('/laps');");
                }

            }

            return Task.CompletedTask;
        }
    }
}

In this version of my Login page the OnLoginSubmit function didn't get called cause the Login button type is 'submit'.

pablocar80 commented 4 years ago

Is it possible to leave the button type=button instead of type=submit? The framework will pick up the input values on the screen without the need to submit them.

Seji64 commented 4 years ago

Sure its possible, but many other Browser related things rely on a submit button. Apart from the example with the Enter key, password manager would not work correctly, because they don't "know" how to submit the form.

the only solution that currently comes to my mind is to use a webservice, set a SessionProperty (e.g. "loggedIn") to true and then send a 302 (aka redirect) instead of returning a JSON.

pablocar80 commented 4 years ago

Interesting, I never thought I needed the “submit” type. I never tried this, but maybe not add code to the click event, and instead use the submit event of the form?

Seji64 commented 4 years ago

hm, yeah using the submit event itself could work. i will try this later and will report back.

pablocar80 commented 4 years ago

Ok, let me know, otherwise there’s other things we can try (custom js code for the button that calls into event.preventDefault() and then messages the server)

pablocar80 commented 4 years ago

One more idea, also we can try this 1 line change: setting Propagation = StopInmediatePropagation in the event settings of the event.

Seji64 commented 4 years ago
                            .Push("form")
                                .Attribute("id","loginForm")
                                //.Attribute("action", "/login")
                                //.Attribute("method", "post")
                                .On("submit",() =>
                                {
                                    Debug.WriteLine("test");
                                })

==> does sadly not work / trigger in Firefox. In edge its working....strange

pablocar80 commented 4 years ago

I’ll be able to get to the computer tonight. Could you try the other change, setting propagation property for the click event on the button?

Seji64 commented 4 years ago

I’ll be able to get to the computer tonight. Could you try the other change, setting propagation property for the click event on the button?

Makes no difference. The funny thing is, that with MS Edge the onclick Event is working even the type is submit. Maybe it is a firefox specific problem.

pablocar80 commented 4 years ago

Ok I’ll take a look. I’m also curious why those 2 changes don’t work and will debug them.

pablocar80 commented 4 years ago

I got it to work by using the "submit" event:

using Integrative.Lara;
using System;
using System.Threading.Tasks;

namespace LAPS_WebUI.Pages
{
    class Program
    {
        static async Task Main()
        {
            using var app = new Application();
            await app.Start(new StartServerOptions
            {
                PublishAssembliesOnStart = true,
                Port = 8182
            });
            await app.WaitForShutdown();
        }
    }

    [LaraPage(Address = "/")]
    class Login : IPage
    {
        private Element _test;

        public Task OnGet()
        {

            var thisDocument = LaraUI.Page.Document;

            thisDocument.Body.AddClass("text-center");

            var builder = new LaraBuilder(thisDocument.Body);

            builder
            .Push("div", "card mx-auto mt-5")
                .Attribute("style", "width: 24rem;")
                .Push("article", "card-body")
                    .Push("h4", "card-title text-center mb-4 mt-1")
                        .InnerText("Sign in")
                    .Pop()
                    .Push("i", "fas fa-user-circle fa-5x")
                    .Pop()
                    .Push("hr")
                    .Pop()
                    .Push("div", "alert alert-warning text-center")
                        .Attribute("id", "loginmessage")
                        .Attribute("role", "alert")
                        .FlagAttribute("hidden", true)
                    .Pop()
                    .Push("form")
                        .Attribute("id", "loginForm")
                        .On(new EventSettings
                        {
                            EventName = "submit",
                            Propagation = PropagationType.StopImmediatePropagation,
                            Handler = OnLoginSubmit
                        })
                        //.Attribute("action", "/login")
                        //.Attribute("method", "post")
                        .Push("div", "form-group")
                            .Push("div", "input-group")
                                .Push("span", "input-group-text")
                                    .Push("i", "fa fa-user")
                                    .Pop()
                                .Pop()
                                .Push("input", "form-control")
                                    .Attribute("type", "email")
                                    .Attribute("placeholder", "E-Mail or login")
                                    .Attribute("name", "email")
                                    .GetCurrent(out _test)
                                .Pop()
                            .Pop() // Input Group
                        .Pop() // Form-Group
                        .Push("div", "form-group")
                            .Push("div", "input-group")
                                .Push("span", "input-group-text")
                                    .Push("i", "fa fa-key")
                                    .Pop()
                                .Pop()
                                .Push("input", "form-control")
                                    .Attribute("type", "password")
                                    .Attribute("placeholder", "Password")
                                    .Attribute("name", "password")
                                .Pop()
                            .Pop() // Input Group
                        .Pop() // Form-Group
                        .Push("div", "form-group")
                            .Push("button", "btn btn-primary btn-block")
                                .Attribute("id", "loginbutton")
                                .Attribute("type", "submit")
                                .InnerText("Login")
                            .Pop() // Button
                        .Pop() // form-group
                    .Pop() //form
                .Pop()
            .Pop();

            return Task.CompletedTask;
        }

        private Task OnLoginSubmit()
        {
            Console.WriteLine(_test.GetAttribute("value"));
            return Task.CompletedTask;
        }
    }
}
pablocar80 commented 4 years ago

So in short it's: (1) use submit event, and (2) use option to stop propagation to prevent the button from posting/reloading the page

Seji64 commented 4 years ago

Hi,

what Browser do you use? Have you tried it with firefox? Firefox totally ignores the stop propagation flag. Browser's which using Chrome Engine seems working fine. The only hint i got is the following from the firefox JS console: Internal Server Error on AJAX call. Detailed exception information on the client is turned off.

This gets logged when i press the login button.

EDIT:

If you add .Attribute("action","/lol") to the form you can see stop propagation seems not to be working ( ff and chrome). because as far as I understand it, the change to page /lol should be suppressed. but this takes place anyway. So the stop propagation flag seems to work in chrome and FF not correctly.

EDIT2:

Seems to stop propagation is not enough, you must also return false

https://jsfiddle.net/7jwrxe4m/

pablocar80 commented 4 years ago

I’ll take a look into this as soon as possible, when back at home with the computer tonight. We’ll either have a way to do this with the current version or with a new version.

pablocar80 commented 4 years ago

I uploaded a new version 0.8.7. In this version, there's the additional PreventDefault setting that executes the corresponding event.PreventDefault() in JavaScript. The form submission is avoided by using PreventDefault (that is, prevents the default behavior of submitting a form) instead of using StopPropagation (allows for default behavior and stops bubbling the event in DOM tree).

After updating Lara's NuGet package to the latest version, the code above has to be modified to use PreventDefault instead of StopPropagation:

            .Push("form")
                .Attribute("id", "loginForm")
                .On(new EventSettings
                {
                    EventName = "submit",
                    PreventDefault = true,
                    Handler = OnLoginSubmit
                })
Seji64 commented 4 years ago

It works! Thanks a lot :-)

pablocar80 commented 4 years ago

@Seji64 thank you for submitting this issue and helping improve Lara.