Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement email confirmation #80

Open
pedroadaodev opened this issue Aug 22, 2017 · 8 comments
Open

implement email confirmation #80

pedroadaodev opened this issue Aug 22, 2017 · 8 comments

Comments

@pedroadaodev
Copy link

Hi,

I've check this code/link

// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link

but I cannot use this
if (!await UserManager.IsEmailConfirmedAsync(user.Id))

gives me this error
The method or operation is not implemented.

What can I do?

Best regards,
Pedro

@Shazwazza
Copy link
Owner

This is a known limitation see https://github.com/Shazwazza/UmbracoIdentity/wiki/Known-Issues

If you need these features, you need to extend the implementation and implement these yourself.

Of course any PR would be hugely appreciated!

@Shazwazza Shazwazza changed the title need email confirmation implement email confirmation Jan 15, 2019
@JamesSherborne
Copy link

JamesSherborne commented May 4, 2020

Hi @Shazwazza,

I am looking to implement ConfirmEmailAsync(...) in my application. I have created the following, which just inherits from the original UmbracoMembersUserManager so that I can implement ConfirmEmailAsync(...):

public class CustomUmbracUserManager<TUser> : UmbracoMembersUserManager<TUser> where TUser : UmbracoIdentityMember, IUser<int>, new()
    {
        public CustomUmbracUserManager(IUserStore<TUser, int> store) : base(store)
        {
        }

        public override Task<IdentityResult> ConfirmEmailAsync(int userId, string token)
        {
            // TODO - implement.
            return base.ConfirmEmailAsync(userId, token);
        }
    }

I am having trouble when I try to hook this up in UmbracoIdentityOwinStartup:

app.ConfigureUserManagerForUmbracoMembers<CustomUmbracUserManager<UmbracoApplicationMember>, UmbracoApplicationMember>();

I have no idea how I am supposed to fulfil the arguments to that method. Can you advise? Thanks.

@Shazwazza
Copy link
Owner

@knnithyanand
Copy link

Hi @Shazwazza, I am looking to implement similar functionality.
I want to implement the following functions:

  • Email Verification
  • Phone Number Verification
  • Forgot/reset password

I am still getting a grip on UmbracoIdentity, and I understand AspNet Identity to an extent. I am willing to contribute these features, seems to me like essential features for Identity.

Here is what I have done so far for Email Verification

  1. I have created a custom UserManager and UserStore inheriting from UmbracoMembersUserManager and UmbracoMembersUserStore respectively
  2. I have override for GenerateEmailConfirmationTokenAsync & ConfirmEmailAsync that generates and validates UserToken (for now)
  3. In controller HandleRegisterMember method I am calling GenerateEmailConfirmationTokenAsync & SendEmailAsync

So far these seem to be working able to generate a unique code and validate the code, but I want to set account active only after email verified.

I am thinking of implementing Umbraco.Core.Models.Membership.IMembershipUser on UmbracoIdentityMember class, do you have any thought on this?

@Shazwazza
Copy link
Owner

Hi @knnithyanand,

but I want to set account active only after email verified.
I am thinking of implementing Umbraco.Core.Models.Membership.IMembershipUser on UmbracoIdentityMember class, do you have any thought on this?

Can you explain a little bit more about this change and why you think it's necessary?

@knnithyanand
Copy link

Hi @Shazwazza,
I think the email verification can use the IsApproved property, which to me sounds similar and Umbraco already respects this flag. I thought would be easier to implement the interface instead of having another custom property, also looking to implement the following members from Umbraco.Core.Models.Membership.IMembershipUser even if they expose or wrap other attributes.

bool IsApproved { get; set; }
bool IsLockedOut { get; set; }
DateTime LastLockoutDate { get; set; }
int FailedPasswordAttempts { get; set; }

Let me know if this is not necessary or complicate things moving forward.

@Shazwazza
Copy link
Owner

Shazwazza commented Dec 15, 2020

Hi @knnithyanand sorry for the delay. I don't see it being necessary to implement IMembershipUser since there is no references whatsoever to this interface in this project so it's not going to add any value. If you need to add additional properties to UmbracoIdentityMember then you should just add the properties that you need and manage them appropriately which would be handled inside the UmbracoMembersUserStore. The base class IdentityMember has most of the properties you'd need though, it's all about mapping these to the IMember when saving and retrieving.

I can see that the underlying method to map the values from UmbracoIdentityMember to the underlying IMember is done in the UmbracoMembersUserStore<TMember>.UpdateMemberProperties but this is private which is probably problematic and would add a lot of trouble of trying to extend this, same goes for the MapFromMember method. So changing these methods to being protected virtual would allow you to override it and then map additional properties from UmbracoIdentityMember to and from the underlying IMember.

Another thing I've noticed that makes extending this a little harder is that there explicit "Support" properties like SupportsUserLockout that would need to be overridden by a custom manager. This really isn't required because the underlying UserManager determines these flags by what the user store implements.

Update: I've just pushed v7.2.1 of this package which exposes these methods and adds better support for extending, see https://github.com/Shazwazza/UmbracoIdentity/releases/tag/v7.2.0

To extend this functionality and implement these additional interfaces like IUserLockoutStore you will need:

  • A good understanding of how the base classes of the MS implementations work, the source code is here, example: https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/UserManager.cs
  • A custom user store that implements the interface/feature that you need and inherits from UmbracoMembersUserStore<TMember>
  • Possibly a custom user manager that inherits from UmbracoMembersUserManager that overrides methods that might not by default do what you want. That will be a case by case basis
  • Possibly a custom member instances (TMember) that inherits from UmbracoIdentityMember and adds the data properties to support the features you need, however, i would think that the current implementation contains most likely all of the properties you would need
  • owin startup to replace the default user manager with your own and your own store/member type, https://github.com/Shazwazza/UmbracoIdentity/wiki/Startup-Configuration#custom-user-store

Example

Custom user store enabling the lockout feature and mapping the properties from the IMember to and from TMember. This doesn't require a custom member instance because as mentioned above most of the properties you'll need to map already exist on the underlying UmbracoIdentityMember. This also doesn't require a custom user manager because all of the methods it does just interact with the user store and the feature flag of SupportsUserLockedOut will be set based on the interfaces the user store implements.

public class CustomUmbracoMembersUserStore<TMember> : UmbracoMembersUserStore<TMember>,
    IUserLockoutStore<TMember, int>
    where TMember : UmbracoIdentityMember, IUser<int>, new()
{
    public CustomUmbracoMembersUserStore(ILogger logger, IMemberService memberService, IMemberTypeService memberTypeService, IMemberGroupService memberGroupService, IdentityEnabledMembersMembershipProvider membershipProvider, IExternalLoginStore externalLoginStore)
        : base(logger, memberService, memberTypeService, memberGroupService, membershipProvider, externalLoginStore)
    {
    }

    public Task<int> GetAccessFailedCountAsync(TMember user) => Task.FromResult(user.AccessFailedCount);

    public Task<bool> GetLockoutEnabledAsync(TMember user) => Task.FromResult(user.LockoutEnabled);

    public Task<DateTimeOffset> GetLockoutEndDateAsync(TMember user) => Task.FromResult(new DateTimeOffset(user.LockoutEndDateUtc ?? DateTime.MinValue));

    public Task<int> IncrementAccessFailedCountAsync(TMember user)
    {
        user.AccessFailedCount++;
        return Task.FromResult(user.AccessFailedCount);
    }

    public Task ResetAccessFailedCountAsync(TMember user)
    {
        user.AccessFailedCount = 0;
        return Task.CompletedTask;
    }

    public Task SetLockoutEnabledAsync(TMember user, bool enabled)
    {
        user.LockoutEnabled = enabled;
        return Task.CompletedTask;
    }

    public Task SetLockoutEndDateAsync(TMember user, DateTimeOffset lockoutEnd)
    {
        user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
        return Task.CompletedTask;
    }

    /// <summary>
    /// Override to map custom properties for persistence
    /// </summary>
    /// <param name="member"></param>
    /// <param name="user"></param>
    /// <returns></returns>
    protected override bool UpdateMemberProperties(IMember member, TMember user)
    {
        var hasChanged = base.UpdateMemberProperties(member, user);

        // Assumes your lockoutEnabled member type property is a true/false property type, in which case
        // i "think" it stores 0 or 1 as a int (could be wrong though)
        var lockoutEnabled = member.GetValue<int>("lockoutEnabled") == 1;
        if (lockoutEnabled != user.LockoutEnabled)
        {
            member.SetValue("lockoutEnabled", user.LockoutEnabled ? 1 : 0);
            hasChanged = true;
        }

        return hasChanged;
    }

    /// <summary>
    /// Override to map custom properties when retrieved from the DB
    /// </summary>
    /// <param name="member"></param>
    /// <returns></returns>
    protected override TMember MapFromMember(IMember member)
    {
        var mapped = base.MapFromMember(member);
        mapped.LockoutEnabled = member.GetValue<int>("lockoutEnabled") == 1;
        return mapped;
    }
}

Replace the user manager

app.ConfigureUserManagerForUmbracoMembers<UmbracoMembersUserManager<UmbracoApplicationMember>, UmbracoApplicationMember>(
    (options, owinCtx) =>
    {
        // Get the membership provider
        var membershipProvider = Membership.Providers["UmbracoMembershipProvider"] as IdentityEnabledMembersMembershipProvider;

        // Create the user store
        var userStore = new CustomUmbracoMembersUserStore<UmbracoApplicationMember>(
                Current.ProfilingLogger,
                Services.MemberService,
                Services.MemberTypeService,
                Services.MemberGroupService,
                membershipProvider,
                new ExternalLoginStore(Current.ScopeProvider));

        // Create the manager and initialize with default settings
        var manager = UmbracoMembersUserManager<UmbracoApplicationMember>.Create(options, userStore, membershipProvider);

        return manager;
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants