Megabit / Blazorise

Blazorise is a component library built on top of Blazor with support for CSS frameworks like Bootstrap, Tailwind, Bulma, AntDesign, and Material.
https://blazorise.com/
Other
3.31k stars 533 forks source link

Blazorise.Captcha.{Provider} #4954

Closed David-Moreira closed 9 months ago

David-Moreira commented 1 year ago

New component: Blazorise.Captcha.{Provider}

For now GoogleReCaptcha is on the plans.

pekatete commented 1 year ago

Not sure haw you get this done, but I actually was working on a .net core captcha controller for one of my projects just last week.

`using Microsoft.Maui.Graphics; using Microsoft.Maui.Graphics.Skia;

namespace ProjectNameSpace { [Route("/api/[controller]")] public class CaptchaController : Controller { private readonly Random random = new(); [HttpGet] [AllowAnonymous] [ApiExplorerSettings(IgnoreApi = true)] public async Task IndexAsync() { var (content, contentType, payload) = Draw(); HttpContext.Response.ContentType = contentType; HttpContext.Response.Headers.ETag = payload; await HttpContext.Response.BodyWriter.WriteAsync(content); } private (byte[] content, string contentType, string payload) Draw() { SkiaBitmapExportContext bmp = new(150, 50, 2.0f); Font myFont = new("GenericMonospace", 500, FontStyleType.Normal); bmp.Canvas.Font = myFont; bmp.Canvas.FontSize = 25; ICanvas canvas = bmp.Canvas;

        Rect myTextRectanglex = new(new(x: 0, y: 0), new SizeF(150));
        canvas.FillColor = Colors.White;
        canvas.FillRectangle(myTextRectanglex);
        canvas.StrokeSize = 2;
        canvas.StrokeColor = Color.FromRgb(0x28, 0xa7, 0x45);
        for (int i = 0; i < 10; i++)
        {
            canvas.DrawLine(new Point(random.Next(0, bmp.Width - 1), random.Next(0, bmp.Height - 1)), new Point(random.Next(0, bmp.Width - 1), random.Next(0, bmp.Height - 1)));
        }

        string text = GenerateRandomText(5);
        canvas.Font = myFont;
        SizeF fulltextSize = canvas.GetStringSize(text, myFont, 25);
        var pos = (150 - ((int)fulltextSize.Width + 20))/2;
        for (int i = 0; i < text.Length; i++)
        {
            var myText = text[i].ToString();
            SizeF textSize = canvas.GetStringSize(myText, myFont, 25);
            Point point = new(x: pos, y: random.Next(0,30));
            Rect myTextRectangle = new(point, textSize);
            canvas.DrawString(myText, myTextRectangle, HorizontalAlignment.Center, VerticalAlignment.Center, TextFlow.OverflowBounds);
            pos += (int)textSize.Width + 5;
        }

        canvas.ResetState();
        using var ms = new MemoryStream();
        bmp.SKImage.Encode(SkiaSharp.SKEncodedImageFormat.Webp, 100);
        bmp.WriteToStream(ms);
        return (ms.ToArray(), "image/webp", text);
    }

    private string GenerateRandomText(int length)
    {
        string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string text = "";
        for (int i = 0; i < length; i++)
        {
            text += chars[random.Next(chars.Length)];
        }
        return text;
    }
}

}`

David-Moreira commented 1 year ago

Hello @pekatete We'll take any suggestions, of course. :) We're still going to plan and implement.

Thank you for providing an example implementation code, from what I can tell, this is a custom implementation of yours?

What we were thinking on implementing, is not our own Captcha implementation, as we're not really interested in reinventing the wheel if there are already dedicated captcha providers that certainly have way more experience and have developed through off cases, security holes, etc...

So basically we are thinking on creating a wrapper that makes it easier to interact with other Captcha Providers like the Google ReCaptcha.

pekatete commented 1 year ago

It is a custom implementation, yes. The pool of characters can be tweaked to suit (case insensitive, none alpha/numeric etc), can choose image format, etc. Works as intended for my purposes but would need a few tweeks for general use. I do not think there are any security or other considerations that 3rd party providers offer over ana above what a custom implementation does - it is basically a few random (and graphically slightly obfuscated) characters presented to a user - but if the direction you've chosen is for a wrapper, that works too.

David-Moreira commented 1 year ago

It is a custom implementation, yes. The pool of characters can be tweaked to suit (case insensitive, none alpha/numeric etc), can choose image format, etc. Works as intended for my purposes but would need a few tweeks for general use. I do not think there are any security or other considerations that 3rd party providers offer over ana above what a custom implementation does - it is basically a few random (and graphically slightly obfuscated) characters presented to a user - but if the direction you've chosen is for a wrapper, that works too.

I see.

I do not think there are any security or other considerations that 3rd party providers offer over ana above what a custom implementation doe

I can't speak so much as I'm not very acquainted to the security holes there may be with captchas. But I wouldn't be so dismissive... bad actors are very creative... I would never assume anything is 100% secure.

For example, on your implementation could someone intercept & steal the captcha payload and reuse it from a different location? How many times could it be reused? Just once? Does it check user agent/ip, whatever else... that could mean something about this payload has changed?

Does it show captcha always? Maybe it would be beneficial if it's smart and recognizes it's very likely it's a regular user, and the captcha does not need to be presented, giving the user a better experience... etc...

I think there are alot of stuff like this that providers have already thought and developed for us.

pekatete commented 1 year ago

For example, on your implementation could someone intercept & steal the captcha payload and reuse it from a different location?

The code I provided simply generates the captcha image. In any case, the attack surface for interception is the same as that of a 3rd party solution, and is near none-existent with https

Does it check user agent/ip, whatever else... that could mean something about this payload has changed?

These are issues that you handle when you initiate the request to the API endpoint, i.e noting the IP / user agent to check against when verifying the captcha

Does it show captcha always? Maybe it would be beneficial if it's smart and recognizes it's very likely it's a regular user ...

If you called the endpoint then yes, it would show the captcha always - as for recognising whether it was a regular user, this is up to the developer, but if I can say, the same would afflict a wrapped 3rd party implementation).

There's really not much to a captcha that 3rd party providers offer over what can be implemented in a library. I'm not claiming to be an expert and in the same breath not being dismissive of potential security holes, however, for what a captcha is intended for, a generic captcha implementation should be sufficiently secure otherwise there is 2FA et all.

David-Moreira commented 1 year ago

Alright. Thanks for your input in this. :) I think we'll still prefer to go with an external provider. But if you'd like to contribute an implementation of captcha, let us know. Seems to me you've got a good grasp of it already.

There's really not much to a captcha that 3rd party providers offer over what can be implemented in a library.

Haha doesn't that apply to pretty much anything? The point is that you have to put in the time... solve the same problems, etc...

pekatete commented 1 year ago

But if you'd like to contribute an implementation of captcha, let us know.

Wouldn't know where to start . Do you have an example that I can look at?

doesn't that apply to pretty much anything? The point is that you have to put in the time.

Not really that much time if I knew how to go about it in your repository, more-so since it is a feature

David-Moreira commented 1 year ago

doesn't that apply to pretty much anything? The point is that you have to put in the time.

Not really that much time if I knew how to go about it in your repository, more-so since it is a feature

I meant that as in relation that we can solve the same problems that a 3rd party library solves ourselves by making a library ourselves... just that we put in time solving something that is already "solved". Sometimes it's worth it, sometimes it may be not.

David-Moreira commented 1 year ago

But if you'd like to contribute an implementation of captcha, let us know.

Wouldn't know where to start . Do you have an example that I can look at?

https://github.com/Megabit/Blazorise#contributing https://github.com/Megabit/Blazorise/wiki/Contributing

David-Moreira commented 1 year ago

But if you'd like to contribute an implementation of captcha, let us know.

Wouldn't know where to start . Do you have an example that I can look at?

https://github.com/Megabit/Blazorise#contributing https://github.com/Megabit/Blazorise/wiki/Contributing

Other then that, that would be a new "optional" component under Extensions in the solution. image

@stsrki anything additional worth nothing?

stsrki commented 1 year ago

@stsrki anything additional worth nothing?

Not much except that the plan is to do it similar to Blazorise core. Have a default project with the standardized API:

Blazorise.Captcha

Then for each of providers add the implementation:

Blazorise.Captcha.GoogleReCaptcha

And include it in the project as

.AddGoogleReCaptcha(options => { // optional options })

pekatete commented 1 year ago

Can you give an example of how/where you add the line:

.AddGoogleReCaptcha(options => { // optional options })

I have the skeleton Config class and implemented the generic captcha (still trying to resolve skiasharp for WASM) ...

public static class Config
{
    public static IServiceCollection AddGenericCaptcha(this IServiceCollection serviceCollection )
    {
        var classProvider = new CaptchaGenerator();

        serviceCollection.AddSingleton<ICaptchaGenerator>( classProvider );

        return serviceCollection;
    }

    public static IServiceCollection AddGoogleReCaptcha( this IServiceCollection serviceCollection )
    {
        //TODO
        return serviceCollection;
    }
}
stsrki commented 1 year ago

Hello, the .AddGoogleReCaptcha(options => { // optional options }) API does not exist yet. This opened ticked is still in the planning stage, and we still need to start working on it. The AddGoogleReCaptcha will probably be one way of using it.

David-Moreira commented 9 months ago

Hello @pekatete With the introduction of Google ReCaptcha support in #5289. The groundwork has been laid for the implementation of different captcha providers. If you are still interested and you want to take your shot at a new captcha provider, you can base yourself off the existing work after the Pull Request is merged into master.