Spotify Auth working, strongly typed web config

This commit is contained in:
andy 2021-10-27 17:31:29 +01:00
parent d1563c3365
commit 222c738854
6 changed files with 191 additions and 7 deletions

View File

@ -30,7 +30,6 @@ namespace Selector.CLI
// CONFIG // CONFIG
services.Configure<RootOptions>(options => services.Configure<RootOptions>(options =>
{ {
OptionsHelper.ConfigureOptions(options, context.Configuration); OptionsHelper.ConfigureOptions(options, context.Configuration);
}); });
var config = OptionsHelper.ConfigureOptions(context.Configuration); var config = OptionsHelper.ConfigureOptions(context.Configuration);

View File

@ -38,12 +38,14 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
public string Username { get; set; } public string Username { get; set; }
} }
private async Task LoadAsync(ApplicationUser user) private Task LoadAsync(ApplicationUser user)
{ {
Input = new InputModel Input = new InputModel
{ {
Username = user.LastFmUsername, Username = user.LastFmUsername,
}; };
return Task.CompletedTask;
} }
public async Task<IActionResult> OnGetAsync() public async Task<IActionResult> OnGetAsync()
@ -74,8 +76,8 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
if (Input.Username != user.LastFmUsername) if (Input.Username != user.LastFmUsername)
{ {
user.LastFmUsername = Input.Username; user.LastFmUsername = Input.Username.Trim();
_userManager.UpdateAsync(user); await _userManager.UpdateAsync(user);
StatusMessage = "Username changed"; StatusMessage = "Username changed";
return RedirectToPage(); return RedirectToPage();

View File

@ -12,17 +12,27 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Selector.Model; using Selector.Model;
using SpotifyAPI.Web;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
namespace Selector.Web.Areas.Identity.Pages.Account.Manage namespace Selector.Web.Areas.Identity.Pages.Account.Manage
{ {
public partial class SpotifyModel : PageModel public partial class SpotifyModel : PageModel
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<SpotifyModel> Logger;
private readonly RootOptions Config;
public SpotifyModel( public SpotifyModel(
UserManager<ApplicationUser> userManager) UserManager<ApplicationUser> userManager,
ILogger<SpotifyModel> logger,
IOptions<RootOptions> config
)
{ {
_userManager = userManager; _userManager = userManager;
Logger = logger;
Config = config.Value;
} }
[BindProperty] [BindProperty]
@ -46,12 +56,58 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
public async Task<IActionResult> OnPostLinkAsync() public async Task<IActionResult> OnPostLinkAsync()
{ {
StatusMessage = "Spotify Linked"; var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if(Config.ClientId is null)
{
Logger.LogError($"Cannot link user, no Spotify client ID");
StatusMessage = "Could not link Spotify, no app credentials";
return RedirectToPage(); return RedirectToPage();
} }
if (Config.ClientSecret is null)
{
Logger.LogError($"Cannot link user, no Spotify client secret");
StatusMessage = "Could not link Spotify, no app credentials";
return RedirectToPage();
}
var loginRequest = new LoginRequest(
new Uri(Config.SpotifyCallback),
Config.ClientId,
LoginRequest.ResponseType.Code
) {
Scope = new[] {
Scopes.UserReadPlaybackState
}
};
return Redirect(loginRequest.ToUri().ToString());
}
public async Task<IActionResult> OnPostUnlinkAsync() public async Task<IActionResult> OnPostUnlinkAsync()
{ {
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
// TODO: stop users Spotify-linked resources (watchers)
user.SpotifyIsLinked = false;
user.SpotifyAccessToken = null;
user.SpotifyRefreshToken = null;
user.SpotifyTokenExpiry = 0;
user.SpotifyLastRefresh = default;
await _userManager.UpdateAsync(user);
StatusMessage = "Spotify Unlinked"; StatusMessage = "Spotify Unlinked";
return RedirectToPage(); return RedirectToPage();
} }

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using Selector.Model;
using SpotifyAPI.Web;
namespace Selector.Web.Controller
{
[ApiController]
[Route("api/[controller]/callback")]
public class SpotifyController : BaseAuthController
{
private readonly RootOptions Config;
private const string ManageSpotifyPath = "/Identity/Account/Manage/Spotify";
public SpotifyController(
ApplicationDbContext context,
IAuthorizationService auth,
UserManager<ApplicationUser> userManager,
ILogger<UsersController> logger,
IOptions<RootOptions> config
) : base(context, auth, userManager, logger)
{
Config = config.Value;
}
[HttpGet]
public async Task<RedirectResult> Callback(string code)
{
if (Config.ClientId is null)
{
Logger.LogError($"Cannot link user, no Spotify client ID");
TempData["StatusMessage"] = "Could not link Spotify, no app credentials";
return Redirect(ManageSpotifyPath);
}
if (Config.ClientSecret is null)
{
Logger.LogError($"Cannot link user, no Spotify client secret");
TempData["StatusMessage"] = "Could not link Spotify, no app credentials";
return Redirect(ManageSpotifyPath);
}
var user = await UserManager.GetUserAsync(User);
if (user == null)
{
throw new ArgumentNullException("No user returned");
}
// TODO: Authorise
var response = await new OAuthClient()
.RequestToken(
new AuthorizationCodeTokenRequest(
Config.ClientId,
Config.ClientSecret,
code,
new Uri(Config.SpotifyCallback)
)
);
user.SpotifyIsLinked = true;
user.SpotifyAccessToken = response.AccessToken;
user.SpotifyRefreshToken = response.RefreshToken;
user.SpotifyLastRefresh = response.CreatedAt;
user.SpotifyTokenExpiry = response.ExpiresIn;
await UserManager.UpdateAsync(user);
TempData["StatusMessage"] = "Spotify Linked";
return Redirect(ManageSpotifyPath);
}
}
}

39
Selector.Web/Options.cs Normal file
View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
namespace Selector.Web
{
public static class OptionsHelper {
public static void ConfigureOptions(RootOptions options, IConfiguration config)
{
config.GetSection(RootOptions.Key).Bind(options);
}
public static RootOptions ConfigureOptions(IConfiguration config)
{
var options = config.GetSection(RootOptions.Key).Get<RootOptions>();
ConfigureOptions(options, config);
return options;
}
public static string FormatKeys(string[] args) => string.Join(":", args);
}
public class RootOptions
{
public const string Key = "Selector";
/// <summary>
/// Spotify client ID
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Spotify app secret
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Spotify callback for authentication
/// </summary>
public string SpotifyCallback { get; set; }
}
}

View File

@ -30,6 +30,11 @@ namespace Selector.Web
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.Configure<RootOptions>(options =>
{
OptionsHelper.ConfigureOptions(options, Configuration);
});
services.AddRazorPages().AddRazorRuntimeCompilation(); services.AddRazorPages().AddRazorRuntimeCompilation();
services.AddControllers(); services.AddControllers();