Merge pull request #124 from Moonlight-Panel/RewriteOAuth2System
Rewritten the oauth2 system to be more modular
This commit is contained in:
commit
4e1d673efa
12 changed files with 355 additions and 379 deletions
|
@ -30,4 +30,17 @@ public static class StringHelper
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string CapitalizeFirstCharacter(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
char firstChar = char.ToUpper(input[0]);
|
||||
string restOfString = input.Substring(1);
|
||||
|
||||
return firstChar + restOfString;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,6 @@
|
|||
using Logging.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.OAuth2;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
|
||||
|
@ -14,130 +8,37 @@ namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
|||
[Route("api/moonlight/oauth2")]
|
||||
public class OAuth2Controller : Controller
|
||||
{
|
||||
private readonly GoogleOAuth2Service GoogleOAuth2Service;
|
||||
private readonly DiscordOAuth2Service DiscordOAuth2Service;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly UserService UserService;
|
||||
private readonly OAuth2Service OAuth2Service;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
public OAuth2Controller(
|
||||
GoogleOAuth2Service googleOAuth2Service,
|
||||
UserRepository userRepository,
|
||||
UserService userService,
|
||||
DiscordOAuth2Service discordOAuth2Service, DateTimeService dateTimeService)
|
||||
public OAuth2Controller(UserService userService, OAuth2Service oAuth2Service, DateTimeService dateTimeService)
|
||||
{
|
||||
GoogleOAuth2Service = googleOAuth2Service;
|
||||
UserRepository = userRepository;
|
||||
UserService = userService;
|
||||
DiscordOAuth2Service = discordOAuth2Service;
|
||||
OAuth2Service = oAuth2Service;
|
||||
DateTimeService = dateTimeService;
|
||||
}
|
||||
|
||||
[HttpGet("google")]
|
||||
public async Task<ActionResult> Google([FromQuery] string code)
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult> Hande([FromRoute] string id, [FromQuery] string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userData = await GoogleOAuth2Service.HandleCode(code);
|
||||
var user = await OAuth2Service.HandleCode(id, code);
|
||||
|
||||
if (userData == null)
|
||||
return Redirect("/login");
|
||||
|
||||
try
|
||||
Response.Cookies.Append("token", await UserService.GenerateToken(user), new()
|
||||
{
|
||||
var user = UserRepository.Get().FirstOrDefault(x => x.Email == userData.Email);
|
||||
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||
});
|
||||
|
||||
string token;
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
token = await UserService.Register(
|
||||
userData.Email,
|
||||
StringHelper.GenerateString(32),
|
||||
userData.FirstName,
|
||||
userData.LastName
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
token = await UserService.GenerateToken(user, true);
|
||||
}
|
||||
|
||||
Response.Cookies.Append("token", token, new ()
|
||||
{
|
||||
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||
});
|
||||
|
||||
return Redirect("/");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn(e.Message);
|
||||
return Redirect("/login");
|
||||
}
|
||||
return Redirect("/");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("An unexpected error occured while handling oauth2");
|
||||
Logger.Warn(e.Message);
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("discord")]
|
||||
public async Task<ActionResult> Discord([FromQuery] string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userData = await DiscordOAuth2Service.HandleCode(code);
|
||||
|
||||
if (userData == null)
|
||||
return Redirect("/login");
|
||||
|
||||
try
|
||||
{
|
||||
var user = UserRepository.Get().FirstOrDefault(x => x.Email == userData.Email);
|
||||
|
||||
string token;
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
token = await UserService.Register(
|
||||
userData.Email,
|
||||
StringHelper.GenerateString(32),
|
||||
userData.FirstName,
|
||||
userData.LastName
|
||||
);
|
||||
|
||||
var newUser = UserRepository
|
||||
.Get()
|
||||
.First(x => x.Email == userData.Email);
|
||||
|
||||
newUser.Status = UserStatus.DataPending;
|
||||
|
||||
UserRepository.Update(newUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
token = await UserService.GenerateToken(user, true);
|
||||
}
|
||||
|
||||
Response.Cookies.Append("token", token, new ()
|
||||
{
|
||||
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||
});
|
||||
|
||||
return Redirect("/");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn(e.Message);
|
||||
return Redirect("/login");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn(e.Message);
|
||||
return BadRequest();
|
||||
return Redirect("/login");
|
||||
}
|
||||
}
|
||||
}
|
8
Moonlight/App/Models/Misc/OAuth2ProviderConfig.cs
Normal file
8
Moonlight/App/Models/Misc/OAuth2ProviderConfig.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Models.Misc;
|
||||
|
||||
public class OAuth2ProviderConfig
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string ClientId { get; set; } = "";
|
||||
public string ClientSecret { get; set; } = "";
|
||||
}
|
15
Moonlight/App/OAuth2/OAuth2Provider.cs
Normal file
15
Moonlight/App/OAuth2/OAuth2Provider.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.OAuth2;
|
||||
|
||||
public abstract class OAuth2Provider
|
||||
{
|
||||
public OAuth2ProviderConfig Config { get; set; }
|
||||
public string Url { get; set; }
|
||||
public IServiceScopeFactory ServiceScopeFactory { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public abstract Task<string> GetUrl();
|
||||
public abstract Task<User> HandleCode(string code);
|
||||
}
|
122
Moonlight/App/OAuth2/Providers/DiscordOAuth2Provider.cs
Normal file
122
Moonlight/App/OAuth2/Providers/DiscordOAuth2Provider.cs
Normal file
|
@ -0,0 +1,122 @@
|
|||
using System.Text;
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.OAuth2.Providers;
|
||||
|
||||
public class DiscordOAuth2Provider : OAuth2Provider
|
||||
{
|
||||
public override Task<string> GetUrl()
|
||||
{
|
||||
string url = $"https://discord.com/api/oauth2/authorize?client_id={Config.ClientId}" +
|
||||
$"&redirect_uri={Url}/api/moonlight/oauth2/discord" +
|
||||
"&response_type=code&scope=identify%20email";
|
||||
|
||||
return Task.FromResult(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
public override async Task<User> HandleCode(string code)
|
||||
{
|
||||
// Endpoints
|
||||
|
||||
var endpoint = Url + "/api/moonlight/oauth2/discord";
|
||||
var discordUserDataEndpoint = "https://discordapp.com/api/users/@me";
|
||||
var discordEndpoint = "https://discordapp.com/api/oauth2/token";
|
||||
|
||||
// Generate access token
|
||||
|
||||
using var client = new RestClient();
|
||||
var request = new RestRequest(discordEndpoint);
|
||||
|
||||
request.AddParameter("client_id", Config.ClientId);
|
||||
request.AddParameter("client_secret", Config.ClientSecret);
|
||||
request.AddParameter("grant_type", "authorization_code");
|
||||
request.AddParameter("code", code);
|
||||
request.AddParameter("redirect_uri", endpoint);
|
||||
|
||||
var response = await client.ExecutePostAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
Logger.Warn("Error verifying oauth2 code");
|
||||
Logger.Warn(response.ErrorMessage);
|
||||
throw new DisplayException("An error occured while verifying oauth2 code");
|
||||
}
|
||||
|
||||
// parse response
|
||||
|
||||
var data = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(response.Content!))
|
||||
).Build();
|
||||
|
||||
var accessToken = data.GetValue<string>("access_token");
|
||||
|
||||
// Now, we will call the discord api with our access token to get the data we need
|
||||
|
||||
var getRequest = new RestRequest(discordUserDataEndpoint);
|
||||
getRequest.AddHeader("Authorization", $"Bearer {accessToken}");
|
||||
|
||||
var getResponse = await client.ExecuteGetAsync(getRequest);
|
||||
|
||||
if (!getResponse.IsSuccessful)
|
||||
{
|
||||
Logger.Warn("An unexpected error occured while fetching user data from remote api");
|
||||
Logger.Warn(getResponse.ErrorMessage);
|
||||
|
||||
throw new DisplayException("An unexpected error occured while fetching user data from remote api");
|
||||
}
|
||||
|
||||
// Parse response
|
||||
|
||||
var getData = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(getResponse.Content!))
|
||||
).Build();
|
||||
|
||||
var email = getData.GetValue<string>("email");
|
||||
var id = getData.GetValue<ulong>("id");
|
||||
|
||||
// Handle data
|
||||
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||
var userService = scope.ServiceProvider.GetRequiredService<UserService>();
|
||||
|
||||
if (userRepo.Get().Any(x => x.Email == email))
|
||||
{
|
||||
var user = userRepo.Get().First(x => x.Email == email);
|
||||
|
||||
user.DiscordId = id;
|
||||
|
||||
userRepo.Update(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
else
|
||||
{
|
||||
await userService.Register(
|
||||
email,
|
||||
StringHelper.GenerateString(32),
|
||||
"User",
|
||||
"User"
|
||||
);
|
||||
|
||||
var user = userRepo.Get().First(x => x.Email == email);
|
||||
user.Status = UserStatus.DataPending;
|
||||
|
||||
user.DiscordId = id;
|
||||
|
||||
userRepo.Update(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,70 +3,42 @@ using Logging.Net;
|
|||
using Moonlight.App.ApiClients.Google.Requests;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Services.OAuth2;
|
||||
namespace Moonlight.App.OAuth2.Providers;
|
||||
|
||||
public class GoogleOAuth2Service
|
||||
public class GoogleOAuth2Provider : OAuth2Provider
|
||||
{
|
||||
private readonly bool EnableGoogle;
|
||||
private readonly string GoogleClientId;
|
||||
private readonly string GoogleClientSecret;
|
||||
|
||||
private readonly bool EnableOverrideUrl;
|
||||
private readonly string OverrideUrl;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public GoogleOAuth2Service(ConfigService configService)
|
||||
public override Task<string> GetUrl()
|
||||
{
|
||||
var config = configService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("OAuth2");
|
||||
|
||||
EnableGoogle = config
|
||||
.GetSection("Google")
|
||||
.GetValue<bool>("Enable");
|
||||
|
||||
if (EnableGoogle)
|
||||
{
|
||||
GoogleClientId = config.GetSection("Google").GetValue<string>("ClientId");
|
||||
GoogleClientSecret = config.GetSection("Google").GetValue<string>("ClientSecret");
|
||||
}
|
||||
|
||||
EnableOverrideUrl = config.GetValue<bool>("EnableOverrideUrl");
|
||||
|
||||
if (EnableOverrideUrl)
|
||||
OverrideUrl = config.GetValue<string>("OverrideUrl");
|
||||
|
||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
public Task<string> GetUrl()
|
||||
{
|
||||
if (!EnableGoogle)
|
||||
throw new DisplayException("Google OAuth2 not enabled");
|
||||
|
||||
var endpoint = GetBaseUrl() + "/api/moonlight/oauth2/google";
|
||||
var endpoint = Url + "/api/moonlight/oauth2/google";
|
||||
var scope = "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email";
|
||||
|
||||
return Task.FromResult(
|
||||
$"https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={GoogleClientId}&redirect_uri={endpoint}&scope={scope}"
|
||||
$"https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={Config.ClientId}&redirect_uri={endpoint}&scope={scope}"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<User?> HandleCode(string code)
|
||||
public override async Task<User> HandleCode(string code)
|
||||
{
|
||||
// Generate access token
|
||||
var endpoint = GetBaseUrl() + "/api/moonlight/oauth2/google";
|
||||
// Endpoints
|
||||
var endpoint = Url + "/api/moonlight/oauth2/google";
|
||||
var googleEndpoint = "https://oauth2.googleapis.com/token";
|
||||
var googlePeopleEndpoint = "https://people.googleapis.com/v1/people/me";
|
||||
|
||||
// Generate access token
|
||||
|
||||
// Setup payload
|
||||
var payload = new GoogleOAuth2CodePayload()
|
||||
{
|
||||
Code = code,
|
||||
RedirectUri = endpoint,
|
||||
ClientId = GoogleClientId,
|
||||
ClientSecret = GoogleClientSecret
|
||||
ClientId = Config.ClientId,
|
||||
ClientSecret = Config.ClientSecret
|
||||
};
|
||||
|
||||
using var client = new RestClient();
|
||||
|
@ -77,9 +49,9 @@ public class GoogleOAuth2Service
|
|||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
//TODO: Maybe add better error handling
|
||||
Logger.Debug("oAuth2 validate error: " + response.Content!);
|
||||
return null;
|
||||
Logger.Warn("Error verifying oauth2 code");
|
||||
Logger.Warn(response.ErrorMessage);
|
||||
throw new DisplayException("An error occured while verifying oauth2 code");
|
||||
}
|
||||
|
||||
// parse response
|
||||
|
@ -91,8 +63,6 @@ public class GoogleOAuth2Service
|
|||
var accessToken = data.GetValue<string>("access_token");
|
||||
|
||||
// Now, we will call the google api with our access token to get the data we need
|
||||
|
||||
var googlePeopleEndpoint = "https://people.googleapis.com/v1/people/me";
|
||||
|
||||
var getRequest = new RestRequest(googlePeopleEndpoint);
|
||||
getRequest.AddHeader("Authorization", $"Bearer {accessToken}");
|
||||
|
@ -102,9 +72,10 @@ public class GoogleOAuth2Service
|
|||
|
||||
if (!getResponse.IsSuccessful)
|
||||
{
|
||||
//TODO: Maybe add better error handling
|
||||
Logger.Debug("OAuth2 api access error: " + getResponse.Content!);
|
||||
return null;
|
||||
Logger.Warn("An unexpected error occured while fetching user data from remote api");
|
||||
Logger.Warn(getResponse.ErrorMessage);
|
||||
|
||||
throw new DisplayException("An unexpected error occured while fetching user data from remote api");
|
||||
}
|
||||
|
||||
// Parse response
|
||||
|
@ -131,19 +102,29 @@ public class GoogleOAuth2Service
|
|||
.First()
|
||||
.GetValue<string>("value");
|
||||
|
||||
return new()
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||
var userService = scope.ServiceProvider.GetRequiredService<UserService>();
|
||||
|
||||
if (userRepo.Get().Any(x => x.Email == email))
|
||||
{
|
||||
Email = email,
|
||||
FirstName = firstName,
|
||||
LastName = lastName
|
||||
};
|
||||
}
|
||||
var user = userRepo.Get().First(x => x.Email == email);
|
||||
|
||||
return user;
|
||||
}
|
||||
else
|
||||
{
|
||||
await userService.Register(
|
||||
email,
|
||||
StringHelper.GenerateString(32),
|
||||
firstName,
|
||||
lastName
|
||||
);
|
||||
|
||||
private string GetBaseUrl()
|
||||
{
|
||||
if (EnableOverrideUrl)
|
||||
return OverrideUrl;
|
||||
var user = userRepo.Get().First(x => x.Email == email);
|
||||
|
||||
return AppUrl;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
using System.Text;
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Services.OAuth2;
|
||||
|
||||
public class DiscordOAuth2Service
|
||||
{
|
||||
private readonly bool Enable;
|
||||
private readonly string ClientId;
|
||||
private readonly string ClientSecret;
|
||||
|
||||
private readonly bool EnableOverrideUrl;
|
||||
private readonly string OverrideUrl;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public DiscordOAuth2Service(ConfigService configService)
|
||||
{
|
||||
var config = configService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("OAuth2");
|
||||
|
||||
Enable = config
|
||||
.GetSection("Discord")
|
||||
.GetValue<bool>("Enable");
|
||||
|
||||
if (Enable)
|
||||
{
|
||||
ClientId = config.GetSection("Discord").GetValue<string>("ClientId");
|
||||
ClientSecret = config.GetSection("Discord").GetValue<string>("ClientSecret");
|
||||
}
|
||||
|
||||
EnableOverrideUrl = config.GetValue<bool>("EnableOverrideUrl");
|
||||
|
||||
if (EnableOverrideUrl)
|
||||
OverrideUrl = config.GetValue<string>("OverrideUrl");
|
||||
|
||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
}
|
||||
|
||||
public Task<string> GetUrl()
|
||||
{
|
||||
if (!Enable)
|
||||
throw new DisplayException("Discord OAuth2 not enabled");
|
||||
|
||||
string url = $"https://discord.com/api/oauth2/authorize?client_id={ClientId}" +
|
||||
$"&redirect_uri={GetBaseUrl()}/api/moonlight/oauth2/discord" +
|
||||
"&response_type=code&scope=identify%20email";
|
||||
|
||||
return Task.FromResult(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<User?> HandleCode(string code)
|
||||
{
|
||||
// Generate access token
|
||||
var endpoint = GetBaseUrl() + "/api/moonlight/oauth2/discord";
|
||||
var discordEndpoint = "https://discordapp.com/api/oauth2/token";
|
||||
|
||||
using var client = new RestClient();
|
||||
var request = new RestRequest(discordEndpoint);
|
||||
|
||||
request.AddParameter("client_id", ClientId);
|
||||
request.AddParameter("client_secret", ClientSecret);
|
||||
request.AddParameter("grant_type", "authorization_code");
|
||||
request.AddParameter("code", code);
|
||||
request.AddParameter("redirect_uri", endpoint);
|
||||
|
||||
var response = await client.ExecutePostAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
//TODO: Maybe add better error handling
|
||||
Logger.Debug("oAuth2 validate error: " + response.Content!);
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse response
|
||||
|
||||
var data = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(response.Content!))
|
||||
).Build();
|
||||
|
||||
var accessToken = data.GetValue<string>("access_token");
|
||||
|
||||
// Now, we will call the google api with our access token to get the data we need
|
||||
|
||||
var googlePeopleEndpoint = "https://discordapp.com/api/users/@me";
|
||||
|
||||
var getRequest = new RestRequest(googlePeopleEndpoint);
|
||||
getRequest.AddHeader("Authorization", $"Bearer {accessToken}");
|
||||
|
||||
var getResponse = await client.ExecuteGetAsync(getRequest);
|
||||
|
||||
if (!getResponse.IsSuccessful)
|
||||
{
|
||||
//TODO: Maybe add better error handling
|
||||
Logger.Debug("OAuth2 api access error: " + getResponse.Content!);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse response
|
||||
|
||||
var getData = new ConfigurationBuilder().AddJsonStream(
|
||||
new MemoryStream(Encoding.ASCII.GetBytes(getResponse.Content!))
|
||||
).Build();
|
||||
|
||||
return new User()
|
||||
{
|
||||
Email = getData.GetValue<string>("email"),
|
||||
FirstName = "User",
|
||||
LastName = "User",
|
||||
DiscordId = getData.GetValue<ulong>("id"),
|
||||
Status = UserStatus.DataPending
|
||||
};
|
||||
}
|
||||
|
||||
private string GetBaseUrl()
|
||||
{
|
||||
if (EnableOverrideUrl)
|
||||
return OverrideUrl;
|
||||
|
||||
return AppUrl;
|
||||
}
|
||||
}
|
85
Moonlight/App/Services/OAuth2Service.cs
Normal file
85
Moonlight/App/Services/OAuth2Service.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.OAuth2;
|
||||
using Moonlight.App.OAuth2.Providers;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class OAuth2Service
|
||||
{
|
||||
public readonly Dictionary<string, OAuth2Provider> Providers = new();
|
||||
private readonly OAuth2ProviderConfig[] Configs;
|
||||
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||
|
||||
private readonly string OverrideUrl;
|
||||
private readonly bool EnableOverrideUrl;
|
||||
private readonly string AppUrl;
|
||||
|
||||
public OAuth2Service(ConfigService configService, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
ConfigService = configService;
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
|
||||
var config = ConfigService.GetSection("Moonlight").GetSection("OAuth2");
|
||||
|
||||
Configs = config.GetSection("Providers").Get<OAuth2ProviderConfig[]>();
|
||||
OverrideUrl = config.GetValue<string>("OverrideUrl");
|
||||
EnableOverrideUrl = config.GetValue<bool>("EnableOverrideUrl");
|
||||
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||
|
||||
// Register additional providers here
|
||||
RegisterOAuth2<DiscordOAuth2Provider>("discord");
|
||||
RegisterOAuth2<GoogleOAuth2Provider>("google");
|
||||
}
|
||||
|
||||
private void RegisterOAuth2<T>(string id, string displayName = "")
|
||||
{
|
||||
var name =
|
||||
string.IsNullOrEmpty(displayName) ?
|
||||
StringHelper.CapitalizeFirstCharacter(id) : displayName;
|
||||
|
||||
var provider = Activator.CreateInstance<T>()! as OAuth2Provider;
|
||||
|
||||
if (provider == null)
|
||||
throw new Exception($"Unable to cast oauth2 provider '{typeof(T).Name}'");
|
||||
|
||||
provider.Config = Configs.First(x => x.Id == id);
|
||||
provider.Url = GetAppUrl();
|
||||
provider.ServiceScopeFactory = ServiceScopeFactory;
|
||||
provider.DisplayName = name;
|
||||
|
||||
Providers.Add(id, provider);
|
||||
}
|
||||
|
||||
public async Task<string> GetUrl(string id)
|
||||
{
|
||||
if (Providers.All(x => x.Key != id))
|
||||
throw new DisplayException("Invalid oauth2 id");
|
||||
|
||||
var provider = Providers[id];
|
||||
|
||||
return await provider.GetUrl();
|
||||
}
|
||||
|
||||
public async Task<User> HandleCode(string id, string code)
|
||||
{
|
||||
if (Providers.All(x => x.Key != id))
|
||||
throw new DisplayException("Invalid oauth2 id");
|
||||
|
||||
var provider = Providers[id];
|
||||
|
||||
return await provider.HandleCode(code);
|
||||
}
|
||||
|
||||
private string GetAppUrl()
|
||||
{
|
||||
if (EnableOverrideUrl)
|
||||
return OverrideUrl;
|
||||
|
||||
return AppUrl;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ using Moonlight.App.Services.LogServices;
|
|||
using Moonlight.App.Services.Mail;
|
||||
using Moonlight.App.Services.Minecraft;
|
||||
using Moonlight.App.Services.Notifications;
|
||||
using Moonlight.App.Services.OAuth2;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Moonlight.App.Services.Statistics;
|
||||
using Moonlight.App.Services.SupportChat;
|
||||
|
@ -119,9 +118,7 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<RatingService>();
|
||||
builder.Services.AddScoped<ReCaptchaService>();
|
||||
builder.Services.AddScoped<IpBanService>();
|
||||
|
||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||
builder.Services.AddSingleton<OAuth2Service>();
|
||||
|
||||
builder.Services.AddScoped<SubscriptionService>();
|
||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
@using Logging.Net
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Models.Misc
|
||||
@using Moonlight.App.Services.OAuth2
|
||||
@using Moonlight.App.Services.Sessions
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using Moonlight.App.Models.Forms
|
||||
|
@ -21,8 +20,7 @@
|
|||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject CookieService CookieService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject GoogleOAuth2Service GoogleOAuth2Service
|
||||
@inject DiscordOAuth2Service DiscordOAuth2Service
|
||||
@inject OAuth2Service OAuth2Service
|
||||
|
||||
<div class="d-flex flex-center">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
|
@ -39,25 +37,23 @@
|
|||
<TL>Sign in to start with moonlight</TL>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-9">
|
||||
<div class="col-md-6">
|
||||
<a href="#" @onclick:preventDefault @onclick="DoDiscord" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bxl-discord-alt"></i>
|
||||
|
||||
@foreach (var providers in OAuth2Service.Providers.Chunk(2))
|
||||
{
|
||||
<div class="row g-3 mb-9">
|
||||
@foreach (var provider in providers)
|
||||
{
|
||||
<div class="col">
|
||||
<a href="#" @onclick:preventDefault @onclick="() => StartOAuth2(provider.Key)" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bx-fingerprint"></i>
|
||||
</div>
|
||||
<TL>Sign in with</TL> @(provider.Value.DisplayName)
|
||||
</a>
|
||||
</div>
|
||||
<TL>Sign in with Discord</TL>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a href="#" @onclick:preventDefault @onclick="DoGoogle" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bxl-google"></i>
|
||||
</div>
|
||||
<TL>Sign in with Google</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="separator separator-content my-14">
|
||||
<span class="w-125px text-gray-500 fw-semibold fs-7">
|
||||
|
@ -171,6 +167,9 @@
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Error while login");
|
||||
Logger.Error(e);
|
||||
|
||||
// Reset state
|
||||
LoginData = new();
|
||||
TotpData = new();
|
||||
|
@ -180,21 +179,12 @@
|
|||
SmartTranslateService.Translate("Error"),
|
||||
SmartTranslateService.Translate("An error occured while logging you in")
|
||||
);
|
||||
|
||||
Logger.Error("Error while login");
|
||||
Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoGoogle()
|
||||
private async Task StartOAuth2(string id)
|
||||
{
|
||||
var url = await GoogleOAuth2Service.GetUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
|
||||
private async Task DoDiscord()
|
||||
{
|
||||
var url = await DiscordOAuth2Service.GetUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
var url = await OAuth2Service.GetUrl(id);
|
||||
NavigationManager.NavigateTo(url ,true);
|
||||
}
|
||||
}
|
|
@ -6,18 +6,16 @@
|
|||
*@
|
||||
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.OAuth2
|
||||
@using Moonlight.App.Models.Forms
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Moonlight.App.Services.Sessions
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject GoogleOAuth2Service GoogleOAuth2Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject DiscordOAuth2Service DiscordOAuth2Service
|
||||
@inject AlertService AlertService
|
||||
@inject UserService UserService
|
||||
@inject CookieService CookieService
|
||||
@inject OAuth2Service OAuth2Service
|
||||
|
||||
<div class="d-flex flex-center">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
|
@ -33,24 +31,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-9">
|
||||
<div class="col-md-6">
|
||||
<a href="#" @onclick:preventDefault @onclick="DoDiscord" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bxl-discord-alt"></i>
|
||||
@foreach (var providers in OAuth2Service.Providers.Chunk(2))
|
||||
{
|
||||
<div class="row g-3 mb-9">
|
||||
@foreach (var provider in providers)
|
||||
{
|
||||
<div class="col-md-6">
|
||||
<a href="#" @onclick:preventDefault @onclick="() => StartOAuth2(provider.Key)" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bx-fingerprint"></i>
|
||||
</div>
|
||||
<TL>Sign in with</TL> @(provider.Value.DisplayName)
|
||||
</a>
|
||||
</div>
|
||||
<TL>Sign up with Discord</TL>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a href="#" @onclick:preventDefault @onclick="DoGoogle" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
||||
<div class="h-15px me-3">
|
||||
<i class="mb-1 bx bx-md bxl-google"></i>
|
||||
</div>
|
||||
<TL>Sign up with Google</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="separator separator-content my-10">
|
||||
<span class="w-125px text-gray-500 fw-semibold fs-7">
|
||||
|
@ -112,16 +108,10 @@
|
|||
{
|
||||
private UserRegisterModel UserRegisterModel = new();
|
||||
|
||||
private async Task DoGoogle()
|
||||
private async Task StartOAuth2(string id)
|
||||
{
|
||||
var url = await GoogleOAuth2Service.GetUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
|
||||
private async Task DoDiscord()
|
||||
{
|
||||
var url = await DiscordOAuth2Service.GetUrl();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
var url = await OAuth2Service.GetUrl(id);
|
||||
NavigationManager.NavigateTo(url ,true);
|
||||
}
|
||||
|
||||
private async Task CreateUser()
|
||||
|
|
|
@ -34,17 +34,20 @@
|
|||
"Website": "https://mycoolproject.de/"
|
||||
},
|
||||
"OAuth2": {
|
||||
"Discord": {
|
||||
"ClientId": "10324",
|
||||
"ClientSecret": "s3cr3t",
|
||||
"Enable": "True"
|
||||
},
|
||||
"Google": {
|
||||
"ClientId": "xyz.apps.googleusercontent.com",
|
||||
"ClientSecret": "s3cr3t",
|
||||
"Enable": "True"
|
||||
},
|
||||
"EnableOverrideUrl": "True",
|
||||
"_exampleProviders": [
|
||||
{
|
||||
"Id": "discord",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
{
|
||||
"Id": "google",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
}
|
||||
],
|
||||
"Providers": [],
|
||||
"EnableOverrideUrl": false,
|
||||
"OverrideUrl": "http://your-moonlight-url.test"
|
||||
},
|
||||
"Security": {
|
||||
|
|
Loading…
Reference in a new issue