Testing new oauth2 setup
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
namespace Moonlight.ApiServer.Database.Entities;
|
||||
using MoonCore.Extended.OAuth2.Consumer;
|
||||
|
||||
public class User
|
||||
namespace Moonlight.ApiServer.Database.Entities;
|
||||
|
||||
public class User : IUserModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Extended.OAuth2.Models;
|
||||
using MoonCore.Extended.OAuth2.Provider;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Http.Controllers.OAuth2.Pages;
|
||||
using Moonlight.ApiServer.Services;
|
||||
using Moonlight.Shared.Http.Responses.OAuth2;
|
||||
|
||||
namespace Moonlight.ApiServer.Http.Controllers.OAuth2;
|
||||
|
||||
[ApiController]
|
||||
[Microsoft.AspNetCore.Mvc.Route("oauth2")]
|
||||
public class OAuth2Controller : Controller
|
||||
{
|
||||
private readonly OAuth2ProviderService OAuth2Service;
|
||||
private readonly AuthService AuthService;
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
|
||||
public OAuth2Controller(OAuth2ProviderService oAuth2Service,
|
||||
AuthService authService, DatabaseRepository<User> userRepository)
|
||||
{
|
||||
OAuth2Service = oAuth2Service;
|
||||
AuthService = authService;
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
[HttpGet("authorize")]
|
||||
public async Task Authorize(
|
||||
[FromQuery(Name = "response_type")] string responseType,
|
||||
[FromQuery(Name = "client_id")] string clientId,
|
||||
[FromQuery(Name = "redirect_uri")] string redirectUri,
|
||||
[FromQuery(Name = "action")] string action = "login"
|
||||
)
|
||||
{
|
||||
if (responseType != "code")
|
||||
throw new HttpApiException("Invalid response type", 400);
|
||||
|
||||
if (!await OAuth2Service.IsValidAuthorization(clientId, redirectUri))
|
||||
throw new HttpApiException("Invalid authorization request", 400);
|
||||
|
||||
Response.StatusCode = 200;
|
||||
|
||||
if (action == "register")
|
||||
{
|
||||
await Response.WriteAsync(
|
||||
await RenderPage<Register>(parameters =>
|
||||
{
|
||||
parameters.Add("ClientId", clientId);
|
||||
parameters.Add("ResponseType", responseType);
|
||||
parameters.Add("RedirectUri", redirectUri);
|
||||
})
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Response.WriteAsync(
|
||||
await RenderPage<Login>(parameters =>
|
||||
{
|
||||
parameters.Add("ClientId", clientId);
|
||||
parameters.Add("ResponseType", responseType);
|
||||
parameters.Add("RedirectUri", redirectUri);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("authorize")]
|
||||
public async Task AuthorizePost(
|
||||
[FromQuery(Name = "response_type")] string responseType,
|
||||
[FromQuery(Name = "client_id")] string clientId,
|
||||
[FromQuery(Name = "redirect_uri")] string redirectUri,
|
||||
[FromForm(Name = "email")] string email,
|
||||
[FromForm(Name = "password")] string password,
|
||||
[FromForm(Name = "username")] string username = "",
|
||||
[FromQuery(Name = "action")] string action = "login"
|
||||
)
|
||||
{
|
||||
if (responseType != "code")
|
||||
throw new HttpApiException("Invalid response type", 400);
|
||||
|
||||
if (!await OAuth2Service.IsValidAuthorization(clientId, redirectUri))
|
||||
throw new HttpApiException("Invalid authorization request", 400);
|
||||
|
||||
User user;
|
||||
|
||||
if (action == "register")
|
||||
user = await AuthService.Register(username, email, password);
|
||||
else
|
||||
user = await AuthService.Login(email, password);
|
||||
|
||||
var code = await OAuth2Service.GenerateCode(data => { data.Add("userId", user.Id); });
|
||||
|
||||
var redirectUrl = redirectUri +
|
||||
$"?code={code}";
|
||||
|
||||
Response.Redirect(redirectUrl);
|
||||
}
|
||||
|
||||
[HttpPost("access")]
|
||||
public async Task<AccessData> Access(
|
||||
[FromForm(Name = "client_id")] string clientId,
|
||||
[FromForm(Name = "client_secret")] string clientSecret,
|
||||
[FromForm(Name = "redirect_uri")] string redirectUri,
|
||||
[FromForm(Name = "grant_type")] string grantType,
|
||||
[FromForm(Name = "code")] string code
|
||||
)
|
||||
{
|
||||
if (grantType != "authorization_code")
|
||||
throw new HttpApiException("Invalid grant type", 400);
|
||||
|
||||
User? user = null;
|
||||
|
||||
var access = await OAuth2Service.ValidateAccess(clientId, clientSecret, redirectUri, code, data =>
|
||||
{
|
||||
if (!data.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return false;
|
||||
|
||||
user = UserRepository.Get().FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
return user != null;
|
||||
}, data => { data.Add("userId", user!.Id); });
|
||||
|
||||
if (access == null)
|
||||
throw new HttpApiException("Unable to validate access", 400);
|
||||
|
||||
return access;
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
public async Task<RefreshData> Refresh(
|
||||
[FromForm(Name = "grant_type")] string grantType,
|
||||
[FromForm(Name = "refresh_token")] string refreshToken
|
||||
)
|
||||
{
|
||||
if (grantType != "refresh_token")
|
||||
throw new HttpApiException("Invalid grant type", 400);
|
||||
|
||||
var refreshData = await OAuth2Service.RefreshAccess(refreshToken, (refreshTokenData, newTokenData) =>
|
||||
{
|
||||
// Check if the userId is present in the refresh token
|
||||
if (!refreshTokenData.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return false;
|
||||
|
||||
// Load user from database if existent
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
newTokenData.Add("userId", user.Id);
|
||||
return true;
|
||||
});
|
||||
|
||||
if(refreshData == null)
|
||||
throw new HttpApiException("Unable to validate refresh", 400);
|
||||
|
||||
return refreshData;
|
||||
}
|
||||
|
||||
[HttpGet("info")]
|
||||
public async Task<InfoResponse> Info()
|
||||
{
|
||||
if (!Request.Headers.ContainsKey("Authorization"))
|
||||
throw new HttpApiException("Authorization header is missing", 400);
|
||||
|
||||
var authHeader = Request.Headers["Authorization"].First() ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(authHeader))
|
||||
throw new HttpApiException("Authorization header is missing", 400);
|
||||
|
||||
User? currentUser = null;
|
||||
|
||||
var isValid = await OAuth2Service.IsValidAccessToken(
|
||||
authHeader,
|
||||
data =>
|
||||
{
|
||||
// Check if the userId is present in the access token
|
||||
if (!data.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return false;
|
||||
|
||||
currentUser = UserRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (currentUser == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
if (!isValid)
|
||||
throw new HttpApiException("Invalid access token", 401);
|
||||
|
||||
if (currentUser == null)
|
||||
throw new HttpApiException("Invalid access token", 401);
|
||||
|
||||
return new InfoResponse()
|
||||
{
|
||||
Username = currentUser.Username,
|
||||
Email = currentUser.Email
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string> RenderPage<T>(Action<Dictionary<string, object>>? onConfigureParameters = null) where T : ComponentBase
|
||||
{
|
||||
var parameters = new Dictionary<string, object>();
|
||||
onConfigureParameters?.Invoke(parameters);
|
||||
|
||||
await using var htmlRenderer = new HtmlRenderer(HttpContext.RequestServices, HttpContext.RequestServices.GetRequiredService<ILoggerFactory>());
|
||||
|
||||
var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
var parameterView = ParameterView.FromDictionary(parameters!);
|
||||
var output = await htmlRenderer.RenderComponentAsync<T>(parameterView);
|
||||
|
||||
return output.ToHtmlString();
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,10 @@ public class ApiAuthenticationMiddleware
|
||||
return Task.CompletedTask;
|
||||
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(apiKey.PermissionsJson) ?? [];
|
||||
context.User = new PermClaimsPrinciple(permissions);
|
||||
context.User = new PermClaimsPrinciple()
|
||||
{
|
||||
Permissions = permissions
|
||||
};
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Extensions;
|
||||
using MoonCore.Services;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using MoonCore.Extended.Helpers;
|
||||
using MoonCore.Extended.OAuth2.LocalProvider;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Interfaces.OAuth2;
|
||||
using Moonlight.Shared.Http.Responses.OAuth2;
|
||||
|
||||
namespace Moonlight.ApiServer.Implementations.OAuth2;
|
||||
|
||||
public class LocalOAuth2Provider : IOAuth2Provider
|
||||
public class LocalOAuth2Provider : ILocalProviderImplementation<User>
|
||||
{
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
|
||||
public LocalOAuth2Provider(DatabaseRepository<User> userRepository)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
/*
|
||||
public async Task<User?> Sync(IServiceProvider provider, string accessToken)
|
||||
{
|
||||
var logger = provider.GetRequiredService<ILogger<LocalOAuth2Provider>>();
|
||||
@@ -55,5 +61,51 @@ public class LocalOAuth2Provider : IOAuth2Provider
|
||||
logger.LogCritical("Unable to sync user: {e}", e);
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
|
||||
public Task SaveChanges(User model)
|
||||
{
|
||||
UserRepository.Update(model);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<User?> LoadById(int id)
|
||||
{
|
||||
var res = UserRepository.Get().FirstOrDefault(x => x.Id == id);
|
||||
|
||||
return Task.FromResult(res);
|
||||
}
|
||||
|
||||
public Task<User> Login(string email, string password)
|
||||
{
|
||||
var user = UserRepository.Get().FirstOrDefault(x => x.Email == email);
|
||||
|
||||
if (user == null)
|
||||
throw new HttpApiException("Invalid email or password", 400);
|
||||
|
||||
if(!HashHelper.Verify(password, user.Password))
|
||||
throw new HttpApiException("Invalid email or password", 400);
|
||||
|
||||
return Task.FromResult(user);
|
||||
}
|
||||
|
||||
public Task<User> Register(string username, string email, string password)
|
||||
{
|
||||
if (UserRepository.Get().Any(x => x.Username == username))
|
||||
throw new HttpApiException("A user with that username already exists", 400);
|
||||
|
||||
if (UserRepository.Get().Any(x => x.Email == email))
|
||||
throw new HttpApiException("A user with that email address already exists", 400);
|
||||
|
||||
var user = new User()
|
||||
{
|
||||
Username = username,
|
||||
Email = email,
|
||||
Password = HashHelper.Hash(password)
|
||||
};
|
||||
|
||||
var finalUser = UserRepository.Add(user);
|
||||
|
||||
return Task.FromResult(finalUser);
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MoonCore" Version="1.7.3" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.1.5" />
|
||||
<PackageReference Include="MoonCore" Version="1.7.4" />
|
||||
<PackageReference Include="MoonCore.Extended" Version="1.1.7" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||
|
||||
@@ -7,6 +7,10 @@ using MoonCore.Extended.Abstractions;
|
||||
using MoonCore.Extended.Extensions;
|
||||
using MoonCore.Extended.Helpers;
|
||||
using MoonCore.Extended.OAuth2.Consumer;
|
||||
using MoonCore.Extended.OAuth2.Consumer.Extensions;
|
||||
using MoonCore.Extended.OAuth2.LocalProvider;
|
||||
using MoonCore.Extended.OAuth2.LocalProvider.Extensions;
|
||||
using MoonCore.Extended.OAuth2.LocalProvider.Implementations;
|
||||
using MoonCore.Extensions;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.PluginFramework.Extensions;
|
||||
@@ -14,6 +18,7 @@ using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using Moonlight.ApiServer.Helpers;
|
||||
using Moonlight.ApiServer.Http.Middleware;
|
||||
using Moonlight.ApiServer.Implementations.OAuth2;
|
||||
using Moonlight.ApiServer.Interfaces.Auth;
|
||||
using Moonlight.ApiServer.Interfaces.OAuth2;
|
||||
using Moonlight.ApiServer.Interfaces.Startup;
|
||||
@@ -167,7 +172,6 @@ public static class Startup
|
||||
builder.Services.AutoAddServices(typeof(Startup).Assembly);
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
await ConfigureTokenAuthentication(builder, config);
|
||||
await ConfigureOAuth2(builder, startupLogger, config);
|
||||
|
||||
// Implementation service
|
||||
@@ -201,7 +205,6 @@ public static class Startup
|
||||
|
||||
app.UseApiErrorHandling();
|
||||
|
||||
await UseTokenAuthentication(app);
|
||||
await UseOAuth2(app);
|
||||
|
||||
// Call interfaces
|
||||
@@ -296,163 +299,6 @@ public static class Startup
|
||||
|
||||
#endregion
|
||||
|
||||
#region Token Authentication
|
||||
|
||||
public static Task ConfigureTokenAuthentication(WebApplicationBuilder builder, AppConfiguration config)
|
||||
{
|
||||
/*
|
||||
builder.Services.AddTokenAuthentication(configuration =>
|
||||
{
|
||||
configuration.AccessSecret = config.Authentication.AccessSecret;
|
||||
configuration.DataLoader = async (data, provider, context) =>
|
||||
{
|
||||
if (!data.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return false;
|
||||
|
||||
var userRepo = provider.GetRequiredService<DatabaseRepository<User>>();
|
||||
var user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
// Load permissions, handle empty values
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(
|
||||
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
|
||||
) ?? [];
|
||||
|
||||
// Save permission state
|
||||
context.User = new PermClaimsPrinciple(permissions)
|
||||
{
|
||||
IdentityModel = user
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
});*/
|
||||
|
||||
builder.AddTokenAuthentication(authenticationConfig =>
|
||||
{
|
||||
authenticationConfig.AccessSecret = config.Authentication.AccessSecret;
|
||||
authenticationConfig.RefreshSecret = config.Authentication.RefreshSecret;
|
||||
|
||||
authenticationConfig.AccessDuration = config.Authentication.AccessDuration;
|
||||
authenticationConfig.RefreshDuration = config.Authentication.RefreshDuration;
|
||||
|
||||
authenticationConfig.ProcessAccess = (accessData, provider, httpContext) =>
|
||||
{
|
||||
if (!accessData.TryGetValue("userId", out var userIdStr) || !userIdStr.TryGetInt32(out var userId))
|
||||
return Task.FromResult(false);
|
||||
|
||||
var userRepo = provider.GetRequiredService<DatabaseRepository<User>>();
|
||||
var user = userRepo.Get().FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return Task.FromResult(false);
|
||||
|
||||
// Load permissions, handle empty values
|
||||
var permissions = JsonSerializer.Deserialize<string[]>(
|
||||
string.IsNullOrEmpty(user.PermissionsJson) ? "[]" : user.PermissionsJson
|
||||
) ?? [];
|
||||
|
||||
// Save permission state
|
||||
httpContext.User = new PermClaimsPrinciple(permissions)
|
||||
{
|
||||
IdentityModel = user
|
||||
};
|
||||
|
||||
return Task.FromResult(true);
|
||||
};
|
||||
|
||||
authenticationConfig.ProcessRefresh = async (oldData, newData, serviceProvider) =>
|
||||
{
|
||||
var oauth2Providers = serviceProvider.GetRequiredService<IOAuth2Provider[]>();
|
||||
|
||||
// Find oauth2 provider
|
||||
var provider = oauth2Providers.FirstOrDefault();
|
||||
|
||||
if (provider == null)
|
||||
throw new HttpApiException("No oauth2 provider has been registered", 500);
|
||||
|
||||
// Check if the userId is present in the refresh token
|
||||
if (!oldData.TryGetValue("userId", out var userIdStr) ||
|
||||
!userIdStr.TryGetInt32(out var userId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load user from database if existent
|
||||
var userRepo = serviceProvider.GetRequiredService<DatabaseRepository<User>>();
|
||||
|
||||
var user = userRepo
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
// Allow plugins to intercept the refresh call
|
||||
//if (AuthInterceptors.Any(interceptor => !interceptor.AllowRefresh(user, serviceProvider)))
|
||||
// return false;
|
||||
|
||||
// Check if it's time to resync with the oauth2 provider
|
||||
if (DateTime.UtcNow >= user.RefreshTimestamp)
|
||||
{
|
||||
var oAuth2Service = serviceProvider.GetRequiredService<OAuth2ConsumerService>();
|
||||
|
||||
try
|
||||
{
|
||||
// It's time to refresh the access to the external oauth2 provider
|
||||
var refreshData = oAuth2Service.RefreshAccess(user.RefreshToken).Result;
|
||||
|
||||
// Sync user with oauth2 provider
|
||||
var syncedUser = await provider.Sync(serviceProvider, refreshData.AccessToken);
|
||||
|
||||
if (syncedUser == null) // User sync has failed. No refresh allowed
|
||||
return false;
|
||||
|
||||
// Save oauth2 refresh and access tokens for later use (re-authentication etc.).
|
||||
// Fetch user model in current db context, just in case the oauth2 provider
|
||||
// uses a different db context or smth
|
||||
|
||||
var userModel = userRepo
|
||||
.Get()
|
||||
.First(x => x.Id == syncedUser.Id);
|
||||
|
||||
userModel.AccessToken = refreshData.AccessToken;
|
||||
userModel.RefreshToken = refreshData.RefreshToken;
|
||||
userModel.RefreshTimestamp = DateTime.UtcNow.AddSeconds(refreshData.ExpiresIn);
|
||||
|
||||
userRepo.Update(userModel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("OAuth2 Refresh");
|
||||
|
||||
// We are handling this error more softly, because it will occur when a user hasn't logged in a long period of time
|
||||
logger.LogTrace("An error occured while refreshing external oauth2 access: {e}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// All checks have passed, allow refresh
|
||||
newData.Add("userId", user.Id);
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task UseTokenAuthentication(WebApplication builder)
|
||||
{
|
||||
builder.UseTokenAuthentication();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Database
|
||||
|
||||
public static async Task ConfigureDatabase(IHostApplicationBuilder builder, ILoggerFactory loggerFactory,
|
||||
@@ -493,98 +339,34 @@ public static class Startup
|
||||
|
||||
public static Task ConfigureOAuth2(WebApplicationBuilder builder, ILogger logger, AppConfiguration config)
|
||||
{
|
||||
builder.AddOAuth2Consumer(configuration =>
|
||||
builder.AddOAuth2Authentication<User>(configuration =>
|
||||
{
|
||||
configuration.AccessSecret = config.Authentication.AccessSecret;
|
||||
configuration.RefreshSecret = config.Authentication.RefreshSecret;
|
||||
configuration.RefreshDuration = TimeSpan.FromSeconds(config.Authentication.RefreshDuration);
|
||||
configuration.RefreshInterval = TimeSpan.FromSeconds(config.Authentication.AccessDuration);
|
||||
configuration.ClientId = config.Authentication.OAuth2.ClientId;
|
||||
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
|
||||
configuration.AuthorizationRedirect =
|
||||
config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/";
|
||||
|
||||
configuration.AccessEndpoint =
|
||||
config.Authentication.OAuth2.AccessEndpoint ?? $"{config.PublicUrl}/oauth2/access";
|
||||
configuration.RefreshEndpoint =
|
||||
config.Authentication.OAuth2.RefreshEndpoint ?? $"{config.PublicUrl}/oauth2/refresh";
|
||||
|
||||
if (config.Authentication.UseLocalOAuth2)
|
||||
{
|
||||
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationRedirect ??
|
||||
$"{config.PublicUrl}/oauth2/authorize";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (config.Authentication.OAuth2.AuthorizationUri == null)
|
||||
logger.LogWarning(
|
||||
"The 'AuthorizationUri' for the oauth2 client is not set. If you want to use an external oauth2 provider, you need to specify this url. If you want to use the local oauth2 service, set 'UseLocalOAuth2Service' to true");
|
||||
|
||||
configuration.AuthorizationEndpoint = config.Authentication.OAuth2.AuthorizationUri!;
|
||||
}
|
||||
|
||||
// TODO: Make modular
|
||||
configuration.ProcessComplete = async (serviceProvider, accessData) =>
|
||||
{
|
||||
var oauth2Providers = serviceProvider.GetRequiredService<IOAuth2Provider[]>();
|
||||
|
||||
// Find oauth2 provider
|
||||
var provider = oauth2Providers.FirstOrDefault();
|
||||
|
||||
if (provider == null)
|
||||
throw new HttpApiException("No oauth2 provider has been registered", 500);
|
||||
|
||||
try
|
||||
{
|
||||
var user = await provider.Sync(serviceProvider, accessData.AccessToken);
|
||||
|
||||
if (user == null)
|
||||
throw new HttpApiException("OAuth2 provider returned empty user", 500);
|
||||
|
||||
// Save new token
|
||||
user.AccessToken = accessData.AccessToken;
|
||||
user.RefreshToken = accessData.RefreshToken;
|
||||
user.RefreshTimestamp = DateTime.UtcNow.AddSeconds(accessData.ExpiresIn);
|
||||
|
||||
var userRepo = serviceProvider.GetRequiredService<DatabaseRepository<User>>();
|
||||
userRepo.Update(user);
|
||||
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "userId", user.Id }
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(provider.GetType());
|
||||
|
||||
logger.LogTrace("An error occured while syncing user with oauth2 provider: {e}", e);
|
||||
throw new HttpApiException("Unable to synchronize with oauth2 provider", 400);
|
||||
}
|
||||
};
|
||||
configuration.AuthorizeEndpoint = config.PublicUrl + "/api/_auth/oauth2/authorize";
|
||||
configuration.RedirectUri = config.PublicUrl;
|
||||
});
|
||||
|
||||
if (!config.Authentication.UseLocalOAuth2) return Task.CompletedTask;
|
||||
builder.Services.AddScoped<IDataProvider<User>, LocalOAuth2Provider>();
|
||||
|
||||
logger.LogInformation("Using local oauth2 provider");
|
||||
|
||||
builder.AddOAuth2Provider(configuration =>
|
||||
if (config.Authentication.UseLocalOAuth2)
|
||||
{
|
||||
configuration.AccessSecret = config.Authentication.LocalOAuth2.AccessSecret;
|
||||
configuration.RefreshSecret = config.Authentication.LocalOAuth2.RefreshSecret;
|
||||
|
||||
configuration.ClientId = config.Authentication.OAuth2.ClientId;
|
||||
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret;
|
||||
configuration.CodeSecret = config.Authentication.LocalOAuth2.CodeSecret;
|
||||
configuration.AuthorizationRedirect =
|
||||
config.Authentication.OAuth2.AuthorizationRedirect ?? $"{config.PublicUrl}/";
|
||||
configuration.AccessTokenDuration = 60;
|
||||
configuration.RefreshTokenDuration = 3600;
|
||||
});
|
||||
|
||||
builder.AddLocalOAuth2Provider<User>(config.PublicUrl);
|
||||
builder.Services.AddScoped<ILocalProviderImplementation<User>, LocalOAuth2Provider>();
|
||||
builder.Services.AddScoped<IOAuth2Provider<User>, LocalOAuth2Provider<User>>();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task UseOAuth2(WebApplication application)
|
||||
{
|
||||
application.UseOAuth2Consumer();
|
||||
application.UseOAuth2Authentication<User>();
|
||||
application.UseLocalOAuth2Provider<User>();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<PackageReference Include="Blazor-ApexCharts" Version="3.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all"/>
|
||||
<PackageReference Include="MoonCore" Version="1.7.3" />
|
||||
<PackageReference Include="MoonCore.Blazor" Version="1.2.6" />
|
||||
<PackageReference Include="MoonCore" Version="1.7.4" />
|
||||
<PackageReference Include="MoonCore.Blazor" Version="1.2.7" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.4" />
|
||||
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user