Merge branch 'feature/moduleConfig' into 'dev'
Resolve "Module configuration" See merge request leon.hoppe/hopframe!10
This commit was merged in pull request #48.
This commit is contained in:
@@ -80,6 +80,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</wpf:ResourceDictionary>
|
</wpf:ResourceDictionary>
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
using HopFrame.Api.Models;
|
using HopFrame.Api.Models;
|
||||||
using HopFrame.Security.Authentication.OpenID;
|
using HopFrame.Security.Authentication.OpenID;
|
||||||
using HopFrame.Security.Authentication.OpenID.Options;
|
|
||||||
using HopFrame.Security.Claims;
|
using HopFrame.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace HopFrame.Api.Controller;
|
namespace HopFrame.Api.Controller;
|
||||||
|
|
||||||
[ApiController, Route("api/v1/openid")]
|
[ApiController, Route("api/v1/openid")]
|
||||||
public class OpenIdController(IOpenIdAccessor accessor, IOptions<OpenIdOptions> options) : ControllerBase {
|
public class OpenIdController(IOpenIdAccessor accessor) : ControllerBase {
|
||||||
public const string DefaultCallback = "api/v1/openid/callback";
|
public const string DefaultCallback = "api/v1/openid/callback";
|
||||||
|
|
||||||
[HttpGet("redirect")]
|
[HttpGet("redirect")]
|
||||||
public async Task<IActionResult> RedirectToProvider([FromQuery] string redirectAfter, [FromQuery] int performRedirect = 1) {
|
public async Task<IActionResult> RedirectToProvider([FromQuery] string redirectAfter, [FromQuery] int performRedirect = 1) {
|
||||||
var uri = await accessor.ConstructAuthUri(DefaultCallback, redirectAfter);
|
var uri = await accessor.ConstructAuthUri(redirectAfter);
|
||||||
|
|
||||||
if (performRedirect == 1) {
|
if (performRedirect == 1) {
|
||||||
return Redirect(uri);
|
return Redirect(uri);
|
||||||
@@ -29,22 +26,13 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions<OpenIdOptions>
|
|||||||
return BadRequest("Authorization code is missing");
|
return BadRequest("Authorization code is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
var token = await accessor.RequestToken(code, DefaultCallback);
|
var token = await accessor.RequestToken(code);
|
||||||
|
|
||||||
if (token is null) {
|
if (token is null) {
|
||||||
return Forbid("Authorization code is not valid");
|
return Forbid("Authorization code is not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions {
|
accessor.SetAuthenticationCookies(token);
|
||||||
MaxAge = TimeSpan.FromSeconds(token.ExpiresIn),
|
|
||||||
HttpOnly = false,
|
|
||||||
Secure = true
|
|
||||||
});
|
|
||||||
Response.Cookies.Append(ITokenContext.RefreshTokenType, token.RefreshToken, new CookieOptions {
|
|
||||||
MaxAge = options.Value.RefreshToken.ConstructTimeSpan,
|
|
||||||
HttpOnly = false,
|
|
||||||
Secure = true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(state)) {
|
if (string.IsNullOrEmpty(state)) {
|
||||||
return Ok(new SingleValueResult<string>(token.AccessToken));
|
return Ok(new SingleValueResult<string>(token.AccessToken));
|
||||||
@@ -65,19 +53,14 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions<OpenIdOptions>
|
|||||||
if (token is null)
|
if (token is null)
|
||||||
return NotFound("Refresh token not valid");
|
return NotFound("Refresh token not valid");
|
||||||
|
|
||||||
Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions {
|
accessor.SetAuthenticationCookies(token);
|
||||||
MaxAge = TimeSpan.FromSeconds(token.ExpiresIn),
|
|
||||||
HttpOnly = false,
|
|
||||||
Secure = true
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(new SingleValueResult<string>(token.AccessToken));
|
return Ok(new SingleValueResult<string>(token.AccessToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("logout")]
|
[HttpDelete("logout")]
|
||||||
public IActionResult Logout() {
|
public IActionResult Logout() {
|
||||||
Response.Cookies.Delete(ITokenContext.RefreshTokenType);
|
accessor.Logout();
|
||||||
Response.Cookies.Delete(ITokenContext.AccessTokenType);
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using HopFrame.Api.Logic;
|
|||||||
using HopFrame.Api.Logic.Implementation;
|
using HopFrame.Api.Logic.Implementation;
|
||||||
using HopFrame.Database;
|
using HopFrame.Database;
|
||||||
using HopFrame.Security.Authentication;
|
using HopFrame.Security.Authentication;
|
||||||
|
using HopFrame.Security.Authentication.OpenID;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -25,8 +26,10 @@ public static class ServiceCollectionExtensions {
|
|||||||
if (!defaultAuthenticationSection.Exists() || configuration.GetValue<bool>("HopFrame:Authentication:DefaultAuthentication"))
|
if (!defaultAuthenticationSection.Exists() || configuration.GetValue<bool>("HopFrame:Authentication:DefaultAuthentication"))
|
||||||
controllers.Add(typeof(AuthController));
|
controllers.Add(typeof(AuthController));
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("HopFrame:Authentication:OpenID:Enabled"))
|
if (configuration.GetValue<bool>("HopFrame:Authentication:OpenID:Enabled")) {
|
||||||
|
IOpenIdAccessor.DefaultCallback = OpenIdController.DefaultCallback;
|
||||||
controllers.Add(typeof(OpenIdController));
|
controllers.Add(typeof(OpenIdController));
|
||||||
|
}
|
||||||
|
|
||||||
AddHopFrameNoEndpoints<TDbContext>(services, configuration);
|
AddHopFrameNoEndpoints<TDbContext>(services, configuration);
|
||||||
services.AddMvcCore().UseSpecificControllers(controllers.ToArray());
|
services.AddMvcCore().UseSpecificControllers(controllers.ToArray());
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ using HopFrame.Security.Authentication.OpenID.Models;
|
|||||||
namespace HopFrame.Security.Authentication.OpenID;
|
namespace HopFrame.Security.Authentication.OpenID;
|
||||||
|
|
||||||
public interface IOpenIdAccessor {
|
public interface IOpenIdAccessor {
|
||||||
|
public static string DefaultCallback;
|
||||||
|
|
||||||
Task<OpenIdConfiguration> LoadConfiguration();
|
Task<OpenIdConfiguration> LoadConfiguration();
|
||||||
Task<OpenIdToken> RequestToken(string code, string defaultCallback);
|
Task<OpenIdToken> RequestToken(string code);
|
||||||
Task<string> ConstructAuthUri(string defaultCallback, string state = null);
|
Task<string> ConstructAuthUri(string state = null);
|
||||||
Task<OpenIdIntrospection> InspectToken(string token);
|
Task<OpenIdIntrospection> InspectToken(string token);
|
||||||
Task<OpenIdToken> RefreshAccessToken(string refreshToken);
|
Task<OpenIdToken> RefreshAccessToken(string refreshToken);
|
||||||
|
void SetAuthenticationCookies(OpenIdToken token);
|
||||||
|
void Logout();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using HopFrame.Security.Authentication.OpenID.Models;
|
using HopFrame.Security.Authentication.OpenID.Models;
|
||||||
using HopFrame.Security.Authentication.OpenID.Options;
|
using HopFrame.Security.Authentication.OpenID.Options;
|
||||||
|
using HopFrame.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -18,7 +19,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdO
|
|||||||
}
|
}
|
||||||
|
|
||||||
var client = clientFactory.CreateClient();
|
var client = clientFactory.CreateClient();
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, Path.Combine(options.Value.Issuer, ".well-known/openid-configuration"));
|
var request = new HttpRequestMessage(HttpMethod.Get, Path.Combine(options.Value.Issuer, ".well-known/openid-configuration").Replace("\\", "/"));
|
||||||
var response = await client.SendAsync(request);
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
@@ -32,13 +33,13 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdO
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OpenIdToken> RequestToken(string code, string defaultCallback) {
|
public async Task<OpenIdToken> RequestToken(string code) {
|
||||||
if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled && cache.TryGetValue(AuthCodeCacheKey + code, out object cachedToken)) {
|
if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled && cache.TryGetValue(AuthCodeCacheKey + code, out object cachedToken)) {
|
||||||
return cachedToken as OpenIdToken;
|
return cachedToken as OpenIdToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http";
|
var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http";
|
||||||
var callback = options.Value.Callback ?? $"{protocol}://{accessor.HttpContext!.Request.Host.Value}/{defaultCallback}";
|
var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback).Replace("\\", "/");
|
||||||
|
|
||||||
var configuration = await LoadConfiguration();
|
var configuration = await LoadConfiguration();
|
||||||
|
|
||||||
@@ -65,9 +66,9 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdO
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ConstructAuthUri(string defaultCallback, string state = null) {
|
public async Task<string> ConstructAuthUri(string state = null) {
|
||||||
var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http";
|
var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http";
|
||||||
var callback = options.Value.Callback ?? $"{protocol}://{accessor.HttpContext!.Request.Host.Value}/{defaultCallback}";
|
var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback).Replace("\\", "/");
|
||||||
|
|
||||||
var configuration = await LoadConfiguration();
|
var configuration = await LoadConfiguration();
|
||||||
return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}";
|
return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}";
|
||||||
@@ -120,4 +121,25 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdO
|
|||||||
|
|
||||||
return await JsonSerializer.DeserializeAsync<OpenIdToken>(await response.Content.ReadAsStreamAsync());
|
return await JsonSerializer.DeserializeAsync<OpenIdToken>(await response.Content.ReadAsStreamAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetAuthenticationCookies(OpenIdToken token) {
|
||||||
|
if (token.AccessToken is not null)
|
||||||
|
accessor.HttpContext!.Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions {
|
||||||
|
MaxAge = TimeSpan.FromSeconds(token.ExpiresIn),
|
||||||
|
HttpOnly = false,
|
||||||
|
Secure = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token.RefreshToken is not null)
|
||||||
|
accessor.HttpContext!.Response.Cookies.Append(ITokenContext.RefreshTokenType, token.RefreshToken, new CookieOptions {
|
||||||
|
MaxAge = options.Value.RefreshToken.ConstructTimeSpan,
|
||||||
|
HttpOnly = false,
|
||||||
|
Secure = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Logout() {
|
||||||
|
accessor.HttpContext!.Response.Cookies.Delete(ITokenContext.RefreshTokenType);
|
||||||
|
accessor.HttpContext!.Response.Cookies.Delete(ITokenContext.AccessTokenType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ public sealed class OpenIdOptions : OptionsFromConfiguration {
|
|||||||
Configuration = new() {
|
Configuration = new() {
|
||||||
Enabled = true,
|
Enabled = true,
|
||||||
TTL = new() {
|
TTL = new() {
|
||||||
Minutes = 10
|
Hours = 24
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Auth = new() {
|
Auth = new() {
|
||||||
|
|||||||
5
src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs
Normal file
5
src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace HopFrame.Web.Models;
|
||||||
|
|
||||||
|
public class HopFrameWebModuleConfig {
|
||||||
|
public string AdminLoginPageUri { get; set; } = "/administration/login";
|
||||||
|
}
|
||||||
@@ -8,11 +8,12 @@
|
|||||||
@using HopFrame.Security.Authorization
|
@using HopFrame.Security.Authorization
|
||||||
@using HopFrame.Web.Admin.Providers
|
@using HopFrame.Web.Admin.Providers
|
||||||
@using HopFrame.Web.Components
|
@using HopFrame.Web.Components
|
||||||
|
@using HopFrame.Web.Models
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using Microsoft.Extensions.Options
|
@using Microsoft.Extensions.Options
|
||||||
@layout AdminLayout
|
@layout AdminLayout
|
||||||
|
|
||||||
<AuthorizedView Permission="@Options.Value.Dashboard" RedirectIfUnauthorized="/administration/login" />
|
<AuthorizedView Permission="@Options.Value.Dashboard" RedirectIfUnauthorized="@ConstructRedirectUri()" />
|
||||||
|
|
||||||
<PageTitle>Admin Dashboard</PageTitle>
|
<PageTitle>Admin Dashboard</PageTitle>
|
||||||
|
|
||||||
@@ -38,11 +39,16 @@
|
|||||||
@inject NavigationManager Navigator
|
@inject NavigationManager Navigator
|
||||||
@inject IAdminPagesProvider Pages
|
@inject IAdminPagesProvider Pages
|
||||||
@inject IOptions<AdminPermissionOptions> Options
|
@inject IOptions<AdminPermissionOptions> Options
|
||||||
|
@inject HopFrameWebModuleConfig Config
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
public void NavigateTo(string url) {
|
public void NavigateTo(string url) {
|
||||||
Navigator.NavigateTo("administration/" + url, true);
|
Navigator.NavigateTo("/administration/" + url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConstructRedirectUri() {
|
||||||
|
return Config.AdminLoginPageUri + "?redirect=/administration";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? DefaultRedirect : "/administration/" + RedirectAfter, true);
|
Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? DefaultRedirect : RedirectAfter, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
@using HopFrame.Security.Claims
|
@using HopFrame.Security.Claims
|
||||||
@using HopFrame.Web.Admin
|
@using HopFrame.Web.Admin
|
||||||
@using HopFrame.Web.Components
|
@using HopFrame.Web.Components
|
||||||
|
@using HopFrame.Web.Models
|
||||||
|
|
||||||
<PageTitle>@_pageData.Title</PageTitle>
|
<PageTitle>@_pageData.Title</PageTitle>
|
||||||
<AuthorizedView Permission="@_pageData.Permissions.Read" RedirectIfUnauthorized="@GenerateRedirectString()" />
|
<AuthorizedView Permission="@_pageData.Permissions.Read" RedirectIfUnauthorized="@GenerateRedirectString()" />
|
||||||
@@ -107,6 +108,7 @@
|
|||||||
@inject IPermissionRepository Permissions
|
@inject IPermissionRepository Permissions
|
||||||
@inject SweetAlertService Alerts
|
@inject SweetAlertService Alerts
|
||||||
@inject NavigationManager Navigator
|
@inject NavigationManager Navigator
|
||||||
|
@inject HopFrameWebModuleConfig Config
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -251,6 +253,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateRedirectString() {
|
private string GenerateRedirectString() {
|
||||||
return "/administration/login?redirect=" + _pageData?.Url;
|
return Config.AdminLoginPageUri + "?redirect=/administration/" + _pageData?.Url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using CurrieTechnologies.Razor.SweetAlert2;
|
|||||||
using HopFrame.Database;
|
using HopFrame.Database;
|
||||||
using HopFrame.Security.Authentication;
|
using HopFrame.Security.Authentication;
|
||||||
using HopFrame.Web.Admin;
|
using HopFrame.Web.Admin;
|
||||||
|
using HopFrame.Web.Models;
|
||||||
using HopFrame.Web.Services;
|
using HopFrame.Web.Services;
|
||||||
using HopFrame.Web.Services.Implementation;
|
using HopFrame.Web.Services.Implementation;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@@ -12,12 +13,13 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
namespace HopFrame.Web;
|
namespace HopFrame.Web;
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions {
|
public static class ServiceCollectionExtensions {
|
||||||
public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
|
public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration, HopFrameWebModuleConfig config = null) where TDbContext : HopDbContextBase {
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
services.AddHopFrameRepositories<TDbContext>();
|
services.AddHopFrameRepositories<TDbContext>();
|
||||||
services.AddScoped<IAuthService, AuthService>();
|
services.AddScoped<IAuthService, AuthService>();
|
||||||
services.AddTransient<AuthMiddleware>();
|
services.AddTransient<AuthMiddleware>();
|
||||||
services.AddAdminContext<HopAdminContext>();
|
services.AddAdminContext<HopAdminContext>();
|
||||||
|
services.AddSingleton(config ?? new HopFrameWebModuleConfig());
|
||||||
|
|
||||||
// Component library's
|
// Component library's
|
||||||
services.AddSweetAlert2();
|
services.AddSweetAlert2();
|
||||||
|
|||||||
@@ -108,11 +108,7 @@ internal class AuthService(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, openIdToken.AccessToken, new CookieOptions {
|
accessor.SetAuthenticationCookies(openIdToken);
|
||||||
MaxAge = TimeSpan.FromSeconds(openIdToken.ExpiresIn),
|
|
||||||
HttpOnly = false,
|
|
||||||
Secure = true
|
|
||||||
});
|
|
||||||
return new() {
|
return new() {
|
||||||
Owner = user,
|
Owner = user,
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using HopFrame.Api.Logic;
|
|||||||
using HopFrame.Api.Models;
|
using HopFrame.Api.Models;
|
||||||
using HopFrame.Database.Models;
|
using HopFrame.Database.Models;
|
||||||
using HopFrame.Database.Repositories;
|
using HopFrame.Database.Repositories;
|
||||||
|
using HopFrame.Security.Authentication.OpenID;
|
||||||
using HopFrame.Security.Authorization;
|
using HopFrame.Security.Authorization;
|
||||||
using HopFrame.Security.Claims;
|
using HopFrame.Security.Claims;
|
||||||
using HopFrame.Testing.Api.Models;
|
using HopFrame.Testing.Api.Models;
|
||||||
@@ -68,9 +69,8 @@ public class TestController(ITokenContext userContext, DatabaseContext context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("url")]
|
[HttpGet("url")]
|
||||||
public async Task<ActionResult<SingleValueResult<string>>> GetUrl() {
|
public ActionResult<string> GetUrl() {
|
||||||
var protocol = Request.IsHttps ? "https" : "http";
|
return Ok(IOpenIdAccessor.DefaultCallback ?? "Not set");
|
||||||
return Ok($"{protocol}://{Request.Host.Value}/auth/callback");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ public class AdminLoginTests : TestContext {
|
|||||||
var password = component.Find("""input[type="password"]""");
|
var password = component.Find("""input[type="password"]""");
|
||||||
var submit = component.Find("button");
|
var submit = component.Find("button");
|
||||||
|
|
||||||
component.Instance.RedirectAfter = "testRedirect";
|
component.Instance.RedirectAfter = "/administration/testRedirect";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
email.Change("test@example.com");
|
email.Change("test@example.com");
|
||||||
|
|||||||
Reference in New Issue
Block a user