Implemented new discord linking system
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Logging.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
||||
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||
|
||||
@@ -11,12 +12,41 @@ public class OAuth2Controller : Controller
|
||||
private readonly UserService UserService;
|
||||
private readonly OAuth2Service OAuth2Service;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
private readonly IdentityService IdentityService;
|
||||
|
||||
public OAuth2Controller(UserService userService, OAuth2Service oAuth2Service, DateTimeService dateTimeService)
|
||||
public OAuth2Controller(
|
||||
UserService userService,
|
||||
OAuth2Service oAuth2Service,
|
||||
DateTimeService dateTimeService,
|
||||
IdentityService identityService)
|
||||
{
|
||||
UserService = userService;
|
||||
OAuth2Service = oAuth2Service;
|
||||
DateTimeService = dateTimeService;
|
||||
IdentityService = identityService;
|
||||
}
|
||||
|
||||
[HttpGet("{id}/start")]
|
||||
public async Task<ActionResult> Start([FromRoute] string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (OAuth2Service.Providers.ContainsKey(id))
|
||||
{
|
||||
return Redirect(await OAuth2Service.GetUrl(id));
|
||||
}
|
||||
|
||||
Logger.Warn($"Someone tried to start an oauth2 flow using the id '{id}' which is not registered");
|
||||
|
||||
return Redirect("/");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn($"Error starting oauth2 flow for id: {id}");
|
||||
Logger.Warn(e);
|
||||
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@@ -24,6 +54,18 @@ public class OAuth2Controller : Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUser = await IdentityService.Get();
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
if (await OAuth2Service.CanBeLinked(id))
|
||||
{
|
||||
await OAuth2Service.LinkToUser(id, currentUser, code);
|
||||
|
||||
return Redirect("/profile");
|
||||
}
|
||||
}
|
||||
|
||||
var user = await OAuth2Service.HandleCode(id, code);
|
||||
|
||||
Response.Cookies.Append("token", await UserService.GenerateToken(user), new()
|
||||
|
||||
@@ -9,7 +9,9 @@ public abstract class OAuth2Provider
|
||||
public string Url { get; set; }
|
||||
public IServiceScopeFactory ServiceScopeFactory { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public bool CanBeLinked { get; set; } = false;
|
||||
|
||||
public abstract Task<string> GetUrl();
|
||||
public abstract Task<User> HandleCode(string code);
|
||||
public abstract Task LinkToUser(User user, string code);
|
||||
}
|
||||
@@ -12,6 +12,11 @@ namespace Moonlight.App.OAuth2.Providers;
|
||||
|
||||
public class DiscordOAuth2Provider : OAuth2Provider
|
||||
{
|
||||
public DiscordOAuth2Provider()
|
||||
{
|
||||
CanBeLinked = true;
|
||||
}
|
||||
|
||||
public override Task<string> GetUrl()
|
||||
{
|
||||
string url = $"https://discord.com/api/oauth2/authorize?client_id={Config.ClientId}" +
|
||||
@@ -119,4 +124,74 @@ public class DiscordOAuth2Provider : OAuth2Provider
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task LinkToUser(User user, 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 id = getData.GetValue<ulong>("id");
|
||||
|
||||
// Handle data
|
||||
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
||||
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||
|
||||
user.DiscordId = id;
|
||||
|
||||
userRepo.Update(user);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -13,6 +12,11 @@ namespace Moonlight.App.OAuth2.Providers;
|
||||
|
||||
public class GoogleOAuth2Provider : OAuth2Provider
|
||||
{
|
||||
public GoogleOAuth2Provider()
|
||||
{
|
||||
CanBeLinked = false;
|
||||
}
|
||||
|
||||
public override Task<string> GetUrl()
|
||||
{
|
||||
var endpoint = Url + "/api/moonlight/oauth2/google";
|
||||
@@ -127,4 +131,9 @@ public class GoogleOAuth2Provider : OAuth2Provider
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task LinkToUser(User user, string code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,26 @@ public class OAuth2Service
|
||||
return await provider.HandleCode(code);
|
||||
}
|
||||
|
||||
public Task<bool> CanBeLinked(string id)
|
||||
{
|
||||
if (Providers.All(x => x.Key != id))
|
||||
throw new DisplayException("Invalid oauth2 id");
|
||||
|
||||
var provider = Providers[id];
|
||||
|
||||
return Task.FromResult(provider.CanBeLinked);
|
||||
}
|
||||
|
||||
public async Task LinkToUser(string id, User user, string code)
|
||||
{
|
||||
if (Providers.All(x => x.Key != id))
|
||||
throw new DisplayException("Invalid oauth2 id");
|
||||
|
||||
var provider = Providers[id];
|
||||
|
||||
await provider.LinkToUser(user, code);
|
||||
}
|
||||
|
||||
private string GetAppUrl()
|
||||
{
|
||||
if (EnableOverrideUrl)
|
||||
|
||||
@@ -31,12 +31,17 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/profile/security">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/profile/discord">
|
||||
<TL>Discord</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/profile/security">
|
||||
<TL>Security</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/profile/subscriptions">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/profile/subscriptions">
|
||||
<TL>Subscriptions</TL>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
77
Moonlight/Shared/Views/Profile/Discord.razor
Normal file
77
Moonlight/Shared/Views/Profile/Discord.razor
Normal file
@@ -0,0 +1,77 @@
|
||||
@page "/profile/discord"
|
||||
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject Repository<User> UserRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<ProfileNavigation Index="1"/>
|
||||
|
||||
@if (User.DiscordId == 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert bg-warning d-flex flex-column flex-sm-row p-5 mb-10">
|
||||
<i class="fs-2hx text-light me-4 mb-5 mb-sm-0 bx bx-error bx-lg"></i>
|
||||
|
||||
<div class="d-flex flex-column text-light pe-0 pe-sm-10">
|
||||
<h4 class="mb-2 light">
|
||||
<TL>Your account is currently not linked to discord</TL>
|
||||
</h4>
|
||||
<TL>To use features like the discord bot, link your moonlight account with your discord account</TL><br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<a class="btn btn-primary" href="/api/moonlight/oauth2/discord/start">
|
||||
<TL>Link account</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert bg-success d-flex flex-column flex-sm-row p-5 mb-10">
|
||||
<i class="fs-2hx text-light me-4 mb-5 mb-sm-0 bx bx-check bx-lg"></i>
|
||||
|
||||
<div class="d-flex flex-column text-light pe-0 pe-sm-10">
|
||||
<h4 class="mb-2 light">
|
||||
<TL>Your account is linked to a discord account</TL>
|
||||
</h4>
|
||||
<TL>You are able to use features like the discord bot of moonlight</TL>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<WButton CssClasses="btn-danger"
|
||||
Text="@(SmartTranslateService.Translate("Remove link"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
OnClick="RemoveLink">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public User User { get; set; }
|
||||
|
||||
private async Task RemoveLink()
|
||||
{
|
||||
User.DiscordId = 0;
|
||||
UserRepository.Update(User);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<ProfileNavigation Index="1"/>
|
||||
<ProfileNavigation Index="2"/>
|
||||
|
||||
<div class="card mb-5 mb-xl-10">
|
||||
<LazyLoader Load="Load">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@inject SubscriptionService SubscriptionService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<ProfileNavigation Index="2"/>
|
||||
<ProfileNavigation Index="3"/>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="row g-0">
|
||||
|
||||
Reference in New Issue
Block a user