Skip to content

Commit

Permalink
Add Passwordless.AspNetCore registration overloads that support `IC…
Browse files Browse the repository at this point in the history
…onfiguration` properly (#146)
  • Loading branch information
Tyrrrz authored Aug 6, 2024
1 parent 540def1 commit 04adada
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 112 deletions.
202 changes: 127 additions & 75 deletions src/Passwordless.AspNetCore/IdentityBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,53 @@ namespace Microsoft.Extensions.DependencyInjection;
/// </summary>
public static class IdentityBuilderExtensions
{
/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" />.</param>
/// <param name="configure">Configures the <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IServiceCollection AddPasswordless<TUser>(this IServiceCollection services, Action<PasswordlessAspNetCoreOptions> configure)
where TUser : class, new()
private static IServiceCollection AddPasswordlessIdentity(
this IServiceCollection services,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
Type userType,
Action<OptionsBuilder<PasswordlessAspNetCoreOptions>> configureOptions,
string? defaultScheme)
{
return services.AddPasswordlessCore(typeof(TUser), configure, defaultScheme: null);
// Options
var optionsBuilder = services.AddOptions<PasswordlessAspNetCoreOptions>();
configureOptions(optionsBuilder);

// Default scheme
if (!string.IsNullOrEmpty(defaultScheme))
{
optionsBuilder.Configure(o => o.SignInScheme = defaultScheme);
}

// Services
services.TryAddScoped(
typeof(IPasswordlessService<PasswordlessRegisterRequest>),
typeof(PasswordlessService<>).MakeGenericType(userType)
);

services.TryAddScoped<ICustomizeRegisterOptions, NoopCustomizeRegisterOptions>();

return services;
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="builder">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="services">The <see cref="IServiceCollection" />.</param>
/// <param name="configure">Configures the <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
/// <returns>The <see cref="IServiceCollection" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IdentityBuilder AddPasswordless(this IdentityBuilder builder, Action<PasswordlessAspNetCoreOptions> configure)
{
builder.Services.AddPasswordlessCore(builder.UserType, configure, IdentityConstants.ApplicationScheme);
return builder;
}

[RequiresDynamicCode("Calls Microsoft.Extensions.DependencyInjection.IdentityBuilderExtensions.AddShared(Type, OptionsBuilder<PasswordlessAspNetCoreOptions>, String)")]
[RequiresUnreferencedCode("Calls Microsoft.Extensions.DependencyInjection.IdentityBuilderExtensions.AddShared(Type, OptionsBuilder<PasswordlessAspNetCoreOptions>, String)")]
private static IServiceCollection AddPasswordlessCore(this IServiceCollection services,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type userType,
Action<PasswordlessAspNetCoreOptions> configure,
string? defaultScheme)
public static IServiceCollection AddPasswordless<TUser>(
this IServiceCollection services,
Action<PasswordlessAspNetCoreOptions> configure)
where TUser : class, new()
{
var optionsBuilder = services.AddOptions<PasswordlessAspNetCoreOptions>().Configure(configure);

// Add the SDK services but don't configure it there since ASP.NET Core options are a superset of their options.
// Don't set up options here because we can't use the provided delegate as it's for a different type
services.AddPasswordlessSdk(_ => { });

// Override SDK options to come from ASP.NET Core options
// Derive core options from ASP.NET Core options
services.AddOptions<PasswordlessOptions>()
.Configure<IOptions<PasswordlessAspNetCoreOptions>>((options, aspNetCoreOptionsAccessor) =>
{
Expand All @@ -68,106 +74,152 @@ private static IServiceCollection AddPasswordlessCore(this IServiceCollection se
options.ApiKey = aspNetCoreOptions.ApiKey;
});

return services.AddShared(userType, optionsBuilder, defaultScheme);
return services.AddPasswordlessIdentity(typeof(TUser), o => o.Configure(configure), null);
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="builder">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration" /> to use to bind to <see cref="PasswordlessAspNetCoreOptions" />. Generally it's own section.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IServiceCollection AddPasswordless(this IdentityBuilder builder, IConfiguration configuration)
public static IServiceCollection AddPasswordless<TUser>(
this IServiceCollection services,
IConfiguration configuration)
where TUser : class, new()
{
return builder.AddPasswordless(configuration, builder.UserType, IdentityConstants.ApplicationScheme);
services.AddPasswordlessSdk(configuration);
return services.AddPasswordlessIdentity(typeof(TUser), o => o.Bind(configuration), null);
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="builder">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration" /> to use to bind to <see cref="PasswordlessAspNetCoreOptions" />. Generally it's own section.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IServiceCollection AddPasswordless<TUserType>(this IdentityBuilder builder, IConfiguration configuration)
public static IServiceCollection AddPasswordless<TUser>(
this IServiceCollection services,
string configurationSection)
where TUser : class, new()
{
return builder.AddPasswordless(configuration, typeof(TUserType), null);
services.AddPasswordlessSdk(configurationSection);
return services.AddPasswordlessIdentity(typeof(TUser), o => o.BindConfiguration(configurationSection), null);
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="identity">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configure">Configures the <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
private static IServiceCollection AddPasswordless(this IdentityBuilder builder, IConfiguration configuration, Type? userType, string? defaultScheme)
public static IdentityBuilder AddPasswordless(
this IdentityBuilder identity,
Action<PasswordlessAspNetCoreOptions> configure)
{
var optionsBuilder = builder.Services
.AddOptions<PasswordlessAspNetCoreOptions>()
.Bind(configuration);
// Don't set up options here because we can't use the provided delegate as it's for a different type
identity.Services.AddPasswordlessSdk(_ => { });

// Derive core options from ASP.NET Core options
identity.Services.AddOptions<PasswordlessOptions>()
.Configure<IOptions<PasswordlessAspNetCoreOptions>>((options, aspNetCoreOptionsAccessor) =>
{
var aspNetCoreOptions = aspNetCoreOptionsAccessor.Value;
options.ApiUrl = aspNetCoreOptions.ApiUrl;
options.ApiSecret = aspNetCoreOptions.ApiSecret;
options.ApiKey = aspNetCoreOptions.ApiKey;
});

builder.Services.AddPasswordlessSdk(configuration);
identity.Services.AddPasswordlessIdentity(
identity.UserType,
o => o.Configure(configure),
IdentityConstants.ApplicationScheme
);

return builder.Services.AddShared(userType ?? builder.UserType, optionsBuilder, IdentityConstants.ApplicationScheme);
return identity;
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="builder">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="path">The configuration path to use to bind to <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" />.</returns>
/// <param name="identity">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration" /> to use to bind to <see cref="PasswordlessAspNetCoreOptions" />. Generally it's own section.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IServiceCollection AddPasswordless(this IdentityBuilder builder, string path)
public static IdentityBuilder AddPasswordless(this IdentityBuilder identity, IConfiguration configuration)
{
return builder.AddPasswordless(path, builder.UserType, IdentityConstants.ApplicationScheme);
identity.Services.AddPasswordlessSdk(configuration);

identity.Services.AddPasswordlessIdentity(
identity.UserType,
o => o.Bind(configuration),
IdentityConstants.ApplicationScheme
);

return identity;
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="builder">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="path">The configuration path to use to bind to <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" />.</returns>
/// <param name="identity">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configuration">The <see cref="IConfiguration" /> to use to bind to <see cref="PasswordlessAspNetCoreOptions" />. Generally it's own section.</param>
/// <returns>The <see cref="IdentityBuilder" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
public static IServiceCollection AddPasswordless<TUserType>(this IdentityBuilder builder, string path)
public static IdentityBuilder AddPasswordless<TUserType>(this IdentityBuilder identity, IConfiguration configuration)
{
return builder.AddPasswordless(path, typeof(TUserType), null);
identity.Services.AddPasswordlessSdk(configuration);

identity.Services.AddPasswordlessIdentity(
typeof(TUserType),
o => o.Bind(configuration),
IdentityConstants.ApplicationScheme
);

return identity;
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="identity">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configurationSection">The configuration path to use to bind to <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
private static IServiceCollection AddPasswordless(this IdentityBuilder builder, string path, Type userType, string? defaultScheme)
public static IdentityBuilder AddPasswordless(this IdentityBuilder identity, string configurationSection)
{
var optionsBuilder = builder.Services
.AddOptions<PasswordlessAspNetCoreOptions>()
.BindConfiguration(path);
identity.Services.AddPasswordlessSdk(configurationSection);

builder.Services.AddPasswordlessSdk(path);
identity.Services.AddPasswordlessIdentity(
identity.UserType,
o => o.BindConfiguration(configurationSection),
IdentityConstants.ApplicationScheme
);

return builder.Services.AddShared(userType ?? builder.UserType, optionsBuilder, IdentityConstants.ApplicationScheme);
return identity;
}

/// <summary>
/// Adds the services to support <see cref="PasswordlessApiEndpointRouteBuilderExtensions.MapPasswordless(IEndpointRouteBuilder)" />
/// </summary>
/// <param name="identity">The current <see cref="IdentityBuilder" /> instance.</param>
/// <param name="configurationSection">The configuration path to use to bind to <see cref="PasswordlessAspNetCoreOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" />.</returns>
[RequiresUnreferencedCode("This method is incompatible with assembly trimming.")]
[RequiresDynamicCode("This method is incompatible with native AOT compilation.")]
private static IServiceCollection AddShared(this IServiceCollection services,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
Type userType,
OptionsBuilder<PasswordlessAspNetCoreOptions> optionsBuilder,
string? defaultScheme)
public static IdentityBuilder AddPasswordless<TUserType>(this IdentityBuilder identity, string configurationSection)
{
if (!string.IsNullOrEmpty(defaultScheme))
{
optionsBuilder.Configure(o => o.SignInScheme = defaultScheme);
}
identity.Services.AddPasswordlessSdk(configurationSection);

services.TryAddScoped(
typeof(IPasswordlessService<PasswordlessRegisterRequest>),
typeof(PasswordlessService<>).MakeGenericType(userType));
identity.Services.AddPasswordlessIdentity(
typeof(TUserType),
o => o.BindConfiguration(configurationSection),
IdentityConstants.ApplicationScheme
);

services.TryAddScoped<ICustomizeRegisterOptions, NoopCustomizeRegisterOptions>();

return services;
return identity;
}
}
Loading

0 comments on commit 04adada

Please sign in to comment.