Added google and discord oauth2. Fixed menu bugs
This commit is contained in:
parent
8f3f9fa1fb
commit
60693d25da
12 changed files with 555 additions and 47 deletions
132
Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs
Normal file
132
Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
using Logging.Net;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
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;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/moonlight/oauth2")]
|
||||||
|
public class OAuth2Controller : Controller
|
||||||
|
{
|
||||||
|
private readonly GoogleOAuth2Service GoogleOAuth2Service;
|
||||||
|
private readonly DiscordOAuth2Service DiscordOAuth2Service;
|
||||||
|
private readonly UserRepository UserRepository;
|
||||||
|
private readonly UserService UserService;
|
||||||
|
|
||||||
|
public OAuth2Controller(
|
||||||
|
GoogleOAuth2Service googleOAuth2Service,
|
||||||
|
UserRepository userRepository,
|
||||||
|
UserService userService,
|
||||||
|
DiscordOAuth2Service discordOAuth2Service)
|
||||||
|
{
|
||||||
|
GoogleOAuth2Service = googleOAuth2Service;
|
||||||
|
UserRepository = userRepository;
|
||||||
|
UserService = userService;
|
||||||
|
DiscordOAuth2Service = discordOAuth2Service;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("google")]
|
||||||
|
public async Task<ActionResult> Google([FromQuery] string code)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userData = await GoogleOAuth2Service.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token = await UserService.GenerateToken(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response.Cookies.Append("token", token, new ()
|
||||||
|
{
|
||||||
|
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
||||||
|
});
|
||||||
|
|
||||||
|
return Redirect("/");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn(e.Message);
|
||||||
|
return Redirect("/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token = await UserService.GenerateToken(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response.Cookies.Append("token", token, new ()
|
||||||
|
{
|
||||||
|
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
||||||
|
});
|
||||||
|
|
||||||
|
return Redirect("/");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn(e.Message);
|
||||||
|
return Redirect("/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn(e.Message);
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Google.Requests;
|
||||||
|
|
||||||
|
public class GoogleOAuth2CodePayload
|
||||||
|
{
|
||||||
|
[JsonProperty("grant_type")]
|
||||||
|
public string GrantType { get; set; } = "authorization_code";
|
||||||
|
|
||||||
|
[JsonProperty("code")]
|
||||||
|
public string Code { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("client_id")]
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("client_secret")]
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("redirect_uri")]
|
||||||
|
public string RedirectUri { get; set; }
|
||||||
|
}
|
127
Moonlight/App/Services/OAuth2/DiscordOAuth2Service.cs
Normal file
127
Moonlight/App/Services/OAuth2/DiscordOAuth2Service.cs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
using System.Text;
|
||||||
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Models.Google.Requests;
|
||||||
|
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 = getData.GetValue<string>("username"),
|
||||||
|
LastName = getData.GetValue<string>("discriminator")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBaseUrl()
|
||||||
|
{
|
||||||
|
if (EnableOverrideUrl)
|
||||||
|
return OverrideUrl;
|
||||||
|
|
||||||
|
return AppUrl;
|
||||||
|
}
|
||||||
|
}
|
149
Moonlight/App/Services/OAuth2/GoogleOAuth2Service.cs
Normal file
149
Moonlight/App/Services/OAuth2/GoogleOAuth2Service.cs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
using System.Text;
|
||||||
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Models.Google.Requests;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.OAuth2;
|
||||||
|
|
||||||
|
public class GoogleOAuth2Service
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 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}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> HandleCode(string code)
|
||||||
|
{
|
||||||
|
// Generate access token
|
||||||
|
var endpoint = GetBaseUrl() + "/api/moonlight/oauth2/google";
|
||||||
|
var googleEndpoint = "https://oauth2.googleapis.com/token";
|
||||||
|
|
||||||
|
// Setup payload
|
||||||
|
var payload = new GoogleOAuth2CodePayload()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
RedirectUri = endpoint,
|
||||||
|
ClientId = GoogleClientId,
|
||||||
|
ClientSecret = GoogleClientSecret
|
||||||
|
};
|
||||||
|
|
||||||
|
using var client = new RestClient();
|
||||||
|
var request = new RestRequest(googleEndpoint);
|
||||||
|
|
||||||
|
request.AddBody(payload);
|
||||||
|
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://people.googleapis.com/v1/people/me";
|
||||||
|
|
||||||
|
var getRequest = new RestRequest(googlePeopleEndpoint);
|
||||||
|
getRequest.AddHeader("Authorization", $"Bearer {accessToken}");
|
||||||
|
getRequest.AddParameter("personFields", "names,emailAddresses");
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
var firstName = getData
|
||||||
|
.GetSection("names")
|
||||||
|
.GetChildren()
|
||||||
|
.First()
|
||||||
|
.GetValue<string>("givenName");
|
||||||
|
|
||||||
|
var lastName = getData
|
||||||
|
.GetSection("names")
|
||||||
|
.GetChildren()
|
||||||
|
.First()
|
||||||
|
.GetValue<string>("familyName");
|
||||||
|
|
||||||
|
var email = getData
|
||||||
|
.GetSection("emailAddresses")
|
||||||
|
.GetChildren()
|
||||||
|
.First()
|
||||||
|
.GetValue<string>("value");
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
FirstName = firstName,
|
||||||
|
LastName = lastName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBaseUrl()
|
||||||
|
{
|
||||||
|
if (EnableOverrideUrl)
|
||||||
|
return OverrideUrl;
|
||||||
|
|
||||||
|
return AppUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,13 +68,7 @@ public class UserService
|
||||||
//var mail = new WelcomeMail(user);
|
//var mail = new WelcomeMail(user);
|
||||||
//await MailService.Send(mail, user);
|
//await MailService.Send(mail, user);
|
||||||
|
|
||||||
return JwtBuilder.Create()
|
return await GenerateToken(user);
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
|
||||||
.WithSecret(JwtSecret)
|
|
||||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
|
||||||
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
|
||||||
.AddClaim("userid", user.Id)
|
|
||||||
.Encode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> CheckTotp(string email, string password)
|
public Task<bool> CheckTotp(string email, string password)
|
||||||
|
@ -123,13 +117,7 @@ public class UserService
|
||||||
{
|
{
|
||||||
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
|
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
|
||||||
|
|
||||||
return JwtBuilder.Create()
|
return await GenerateToken(user);
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
|
||||||
.WithSecret(JwtSecret)
|
|
||||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
|
||||||
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
|
||||||
.AddClaim("userid", user.Id)
|
|
||||||
.Encode();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -141,17 +129,11 @@ public class UserService
|
||||||
{
|
{
|
||||||
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
|
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
|
||||||
|
|
||||||
return JwtBuilder.Create()
|
return await GenerateToken(user!);
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
|
||||||
.WithSecret(JwtSecret)
|
|
||||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
|
||||||
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
|
||||||
.AddClaim("userid", user.Id)
|
|
||||||
.Encode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ChangePassword(User user, string password)
|
public Task ChangePassword(User user, string password)
|
||||||
{
|
{
|
||||||
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
||||||
user.TokenValidTime = DateTime.Now;
|
user.TokenValidTime = DateTime.Now;
|
||||||
|
@ -161,6 +143,8 @@ public class UserService
|
||||||
//await MailService.Send(mail, user);
|
//await MailService.Send(mail, user);
|
||||||
|
|
||||||
//AuditLogService.Log("password:change", "The password has been set to a new one");
|
//AuditLogService.Log("password:change", "The password has been set to a new one");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<User> SftpLogin(int id, string password)
|
public Task<User> SftpLogin(int id, string password)
|
||||||
|
@ -179,4 +163,17 @@ public class UserService
|
||||||
//TODO: Log
|
//TODO: Log
|
||||||
throw new Exception("Invalid userid or password");
|
throw new Exception("Invalid userid or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<string> GenerateToken(User user)
|
||||||
|
{
|
||||||
|
var token = JwtBuilder.Create()
|
||||||
|
.WithAlgorithm(new HMACSHA256Algorithm())
|
||||||
|
.WithSecret(JwtSecret)
|
||||||
|
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
||||||
|
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
||||||
|
.AddClaim("userid", user.Id)
|
||||||
|
.Encode();
|
||||||
|
|
||||||
|
return Task.FromResult(token);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -59,6 +59,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="App\Http\Middleware" />
|
<Folder Include="App\Http\Middleware" />
|
||||||
<Folder Include="App\Models\AuditLogData" />
|
<Folder Include="App\Models\AuditLogData" />
|
||||||
|
<Folder Include="App\Models\Google\Resources" />
|
||||||
<Folder Include="resources\lang" />
|
<Folder Include="resources\lang" />
|
||||||
<Folder Include="wwwroot\assets\media" />
|
<Folder Include="wwwroot\assets\media" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
using Moonlight.App.Services.Interop;
|
using Moonlight.App.Services.Interop;
|
||||||
|
using Moonlight.App.Services.OAuth2;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using Moonlight.App.Services.Support;
|
using Moonlight.App.Services.Support;
|
||||||
|
|
||||||
|
@ -56,6 +57,8 @@ namespace Moonlight
|
||||||
builder.Services.AddSingleton<PaperService>();
|
builder.Services.AddSingleton<PaperService>();
|
||||||
builder.Services.AddScoped<ClipboardService>();
|
builder.Services.AddScoped<ClipboardService>();
|
||||||
builder.Services.AddSingleton<ResourceService>();
|
builder.Services.AddSingleton<ResourceService>();
|
||||||
|
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||||
|
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||||
|
|
||||||
builder.Services.AddScoped<AuditLogService>();
|
builder.Services.AddScoped<AuditLogService>();
|
||||||
builder.Services.AddScoped<SystemAuditLogService>();
|
builder.Services.AddScoped<SystemAuditLogService>();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
@using Logging.Net
|
@using Logging.Net
|
||||||
|
@using Moonlight.App.Services.OAuth2
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
|
@ -16,6 +17,8 @@
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject CookieService CookieService
|
@inject CookieService CookieService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject GoogleOAuth2Service GoogleOAuth2Service
|
||||||
|
@inject DiscordOAuth2Service DiscordOAuth2Service
|
||||||
|
|
||||||
<div class="d-flex flex-center">
|
<div class="d-flex flex-center">
|
||||||
<div class="card rounded-3 w-md-550px">
|
<div class="card rounded-3 w-md-550px">
|
||||||
|
@ -35,17 +38,17 @@
|
||||||
|
|
||||||
<div class="row g-3 mb-9">
|
<div class="row g-3 mb-9">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="#" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
<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">
|
<div class="h-15px me-3">
|
||||||
<i class="bx bx-md bxl-discord-alt"></i>
|
<i class="mb-1 bx bx-md bxl-discord-alt"></i>
|
||||||
</div>
|
</div>
|
||||||
<TL>Sign in with Discord</TL>
|
<TL>Sign in with Discord</TL>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="#" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
<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">
|
<div class="h-15px me-3">
|
||||||
<i class="bx bx-md bxl-google"></i>
|
<i class="mb-1 bx bx-md bxl-google"></i>
|
||||||
</div>
|
</div>
|
||||||
<TL>Sign in with Google</TL>
|
<TL>Sign in with Google</TL>
|
||||||
</a>
|
</a>
|
||||||
|
@ -158,4 +161,16 @@
|
||||||
Logger.Error(e);
|
Logger.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DoGoogle()
|
||||||
|
{
|
||||||
|
var url = await GoogleOAuth2Service.GetUrl();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DoDiscord()
|
||||||
|
{
|
||||||
|
var url = await DiscordOAuth2Service.GetUrl();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,12 @@
|
||||||
*@
|
*@
|
||||||
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.OAuth2
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject GoogleOAuth2Service GoogleOAuth2Service
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject DiscordOAuth2Service DiscordOAuth2Service
|
||||||
|
|
||||||
<div class="d-flex flex-center">
|
<div class="d-flex flex-center">
|
||||||
<div class="card rounded-3 w-md-550px">
|
<div class="card rounded-3 w-md-550px">
|
||||||
|
@ -25,17 +29,17 @@
|
||||||
|
|
||||||
<div class="row g-3 mb-9">
|
<div class="row g-3 mb-9">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="#" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
<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">
|
<div class="h-15px me-3">
|
||||||
<i class="bx bx-md bxl-discord-alt"></i>
|
<i class="mb-1 bx bx-md bxl-discord-alt"></i>
|
||||||
</div>
|
</div>
|
||||||
<TL>Sign up with Discord</TL>
|
<TL>Sign up with Discord</TL>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="#" class="btn btn-flex btn-outline btn-text-gray-700 btn-active-color-primary bg-state-light flex-center text-nowrap w-100">
|
<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">
|
<div class="h-15px me-3">
|
||||||
<i class="bx bx-md bxl-google"></i>
|
<i class="mb-1 bx bx-md bxl-google"></i>
|
||||||
</div>
|
</div>
|
||||||
<TL>Sign up with Google</TL>
|
<TL>Sign up with Google</TL>
|
||||||
</a>
|
</a>
|
||||||
|
@ -85,4 +89,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private async Task DoGoogle()
|
||||||
|
{
|
||||||
|
var url = await GoogleOAuth2Service.GetUrl();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DoDiscord()
|
||||||
|
{
|
||||||
|
var url = await DiscordOAuth2Service.GetUrl();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
@using Logging.Net
|
@using Logging.Net
|
||||||
|
@using Moonlight.App.Exceptions
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
@inherits ErrorBoundary
|
@inherits ErrorBoundary
|
||||||
|
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
|
@inject AlertService AlertService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
@if (CurrentException is null)
|
@if (CurrentException is null)
|
||||||
{
|
{
|
||||||
|
@ -60,6 +64,21 @@ else
|
||||||
Logger.Error(exception);
|
Logger.Error(exception);
|
||||||
|
|
||||||
await base.OnErrorAsync(exception);
|
await base.OnErrorAsync(exception);
|
||||||
|
|
||||||
|
if (exception is DisplayException displayException)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await AlertService.Error(
|
||||||
|
SmartTranslateService.Translate("Error"),
|
||||||
|
SmartTranslateService.Translate(displayException.Message)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Recover();
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public new void Recover()
|
public new void Recover()
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
@using Logging.Net
|
||||||
|
|
||||||
@layout ThemeInit
|
@layout ThemeInit
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
@ -31,7 +32,7 @@
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(pathPart))
|
if (!string.IsNullOrEmpty(pathPart))
|
||||||
{
|
{
|
||||||
if(pathPart == pathParts.Last())
|
if (pathPart == pathParts.Last())
|
||||||
title += $"{pathPart.FirstCharToUpper()} ";
|
title += $"{pathPart.FirstCharToUpper()} ";
|
||||||
else
|
else
|
||||||
title += $"{pathPart.FirstCharToUpper()} - ";
|
title += $"{pathPart.FirstCharToUpper()} - ";
|
||||||
|
@ -128,11 +129,6 @@
|
||||||
AddBodyAttribute("data-kt-app-toolbar-enabled", "true");
|
AddBodyAttribute("data-kt-app-toolbar-enabled", "true");
|
||||||
|
|
||||||
AddBodyClass("app-default");
|
AddBodyClass("app-default");
|
||||||
|
|
||||||
JsRuntime.InvokeVoidAsync("KTModalUpgradePlan.init");
|
|
||||||
JsRuntime.InvokeVoidAsync("KTCreateApp.init");
|
|
||||||
JsRuntime.InvokeVoidAsync("KTModalUserSearch.init");
|
|
||||||
JsRuntime.InvokeVoidAsync("KTModalNewTarget.init");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
@ -142,9 +138,7 @@
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = await IdentityService.Get();
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await Task.Delay(300);
|
|
||||||
|
|
||||||
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
|
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-reset-transition");
|
||||||
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
|
await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading");
|
||||||
|
@ -157,19 +151,24 @@
|
||||||
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
|
NavigationManager.LocationChanged += (sender, args) => { SessionService.Refresh(); };
|
||||||
|
|
||||||
MessageService.Subscribe<MainLayout, SupportMessage>(
|
MessageService.Subscribe<MainLayout, SupportMessage>(
|
||||||
$"support.{User.Id}.message",
|
$"support.{User.Id}.message",
|
||||||
this,
|
this,
|
||||||
async message =>
|
async message =>
|
||||||
{
|
|
||||||
if (!NavigationManager.Uri.EndsWith("/support") && (message.IsSupport || message.IsSystem))
|
|
||||||
{
|
{
|
||||||
await ToastService.Info($"Support: {message.Message}");
|
if (!NavigationManager.Uri.EndsWith("/support") && (message.IsSupport || message.IsSystem))
|
||||||
}
|
{
|
||||||
});
|
await ToastService.Info($"Support: {message.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RunDelayedMenu(0);
|
||||||
|
RunDelayedMenu(1);
|
||||||
|
RunDelayedMenu(3);
|
||||||
|
RunDelayedMenu(5);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,4 +192,21 @@
|
||||||
{
|
{
|
||||||
JsRuntime.InvokeVoidAsync("document.body.classList.add", className);
|
JsRuntime.InvokeVoidAsync("document.body.classList.add", className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RunDelayedMenu(int seconds)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(seconds));
|
||||||
|
await JsRuntime.InvokeVoidAsync("KTMenu.initHandlers");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn("Delayed menu error");
|
||||||
|
Logger.Warn(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2462,8 +2462,17 @@ KTMenu.updateDropdowns = function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bug fix for menu load initializing
|
||||||
|
KTMenu.hasInit = false;
|
||||||
|
|
||||||
// Global handlers
|
// Global handlers
|
||||||
KTMenu.initHandlers = function() {
|
KTMenu.initHandlers = function() {
|
||||||
|
|
||||||
|
if(KTMenu.hasInit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
KTMenu.hasInit = true;
|
||||||
|
|
||||||
// Dropdown handler
|
// Dropdown handler
|
||||||
document.addEventListener("click", function(e) {
|
document.addEventListener("click", function(e) {
|
||||||
var items = document.querySelectorAll('.show.menu-dropdown[data-kt-menu-trigger]');
|
var items = document.querySelectorAll('.show.menu-dropdown[data-kt-menu-trigger]');
|
||||||
|
|
Loading…
Reference in a new issue