Added ping route. Allow register from authentication window.

pull/19/head
Pat Hartl 2023-03-14 02:31:42 -05:00
parent 43f0e3823d
commit cad74115e1
7 changed files with 202 additions and 33 deletions

View File

@ -11,6 +11,7 @@ using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media.Converters;
namespace LANCommander.PlaynitePlugin namespace LANCommander.PlaynitePlugin
{ {
@ -85,6 +86,36 @@ namespace LANCommander.PlaynitePlugin
} }
} }
public async Task<AuthResponse> RegisterAsync(string username, string password)
{
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
{
UserName = username,
Password = password
}));
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return response.Data;
case HttpStatusCode.BadRequest:
case HttpStatusCode.Forbidden:
case HttpStatusCode.Unauthorized:
throw new WebException(response.Data.Message);
default:
throw new WebException("Could not communicate with the server");
}
}
public async Task<bool> PingAsync()
{
var response = await Client.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
return response.StatusCode == HttpStatusCode.OK;
}
public AuthResponse RefreshToken(AuthToken token) public AuthResponse RefreshToken(AuthToken token)
{ {
var request = new RestRequest("/api/Auth/Refresh") var request = new RestRequest("/api/Auth/Refresh")

View File

@ -312,7 +312,8 @@ namespace LANCommander.PlaynitePlugin
}; };
window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow(); window.Owner = PlayniteApi.Dialogs.GetCurrentAppWindow();
window.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner; window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.ResizeMode = ResizeMode.NoResize;
window.ShowDialog(); window.ShowDialog();
}); });

View File

@ -202,6 +202,7 @@
<RowDefinition Height="auto" /> <RowDefinition Height="auto" />
<RowDefinition Height="auto" /> <RowDefinition Height="auto" />
<RowDefinition Height="auto" /> <RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" /> <ColumnDefinition Width="auto" />
@ -214,6 +215,20 @@
<TextBox Grid.Row="1" Grid.Column="1" Name="Username" Text="{Binding UserName}" KeyDown="TextBox_KeyDown" /> <TextBox Grid.Row="1" Grid.Column="1" Name="Username" Text="{Binding UserName}" KeyDown="TextBox_KeyDown" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Password" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Password" />
<PasswordBox Grid.Row="2" Grid.Column="1" Name="Password" PasswordChanged="Password_PasswordChanged" KeyDown="TextBox_KeyDown" /> <PasswordBox Grid.Row="2" Grid.Column="1" Name="Password" PasswordChanged="Password_PasswordChanged" KeyDown="TextBox_KeyDown" />
<Button Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding LoginCommand}" x:Name="LoginButton">Login</Button>
<GridSplitter Grid.Row="4" />
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Click="LoginButton_Click" x:Name="LoginButton">Login</Button>
<Button Grid.Row="0" Grid.Column="1" Click="RegisterButton_Click" x:Name="RegisterButton">Register</Button>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -60,6 +60,11 @@ namespace LANCommander.PlaynitePlugin.Views
Authenticate(); Authenticate();
} }
private void RegisterButton_Click(object sender, RoutedEventArgs e)
{
Register();
}
private void Password_PasswordChanged(object sender, RoutedEventArgs e) private void Password_PasswordChanged(object sender, RoutedEventArgs e)
{ {
if (DataContext != null) if (DataContext != null)
@ -121,5 +126,46 @@ namespace LANCommander.PlaynitePlugin.Views
})); }));
} }
} }
private async Task Register()
{
try
{
LoginButton.IsEnabled = false;
RegisterButton.IsEnabled = false;
RegisterButton.Content = "Working...";
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
else
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
var response = await Plugin.LANCommander.RegisterAsync(Context.UserName, Context.Password);
Plugin.Settings.ServerAddress = Context.ServerAddress;
Plugin.Settings.AccessToken = response.AccessToken;
Plugin.Settings.RefreshToken = response.RefreshToken;
Plugin.LANCommander.Token = new AuthToken()
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken,
};
Context.Password = String.Empty;
Plugin.SavePluginSettings(Plugin.Settings);
Window.GetWindow(this).Close();
}
catch (Exception ex)
{
Plugin.PlayniteApi.Dialogs.ShowErrorMessage(ex.Message);
LoginButton.IsEnabled = true;
RegisterButton.IsEnabled = true;
RegisterButton.Content = "Register";
}
}
} }
} }

View File

@ -9,5 +9,6 @@ namespace LANCommander.SDK.Models
public string AccessToken { get; set; } public string AccessToken { get; set; }
public string RefreshToken { get; set; } public string RefreshToken { get; set; }
public DateTime Expiration { get; set; } public DateTime Expiration { get; set; }
public string Message { get; set; }
} }
} }

View File

@ -16,6 +16,7 @@ namespace LANCommander.Controllers.Api
{ {
public string AccessToken { get; set; } public string AccessToken { get; set; }
public string RefreshToken { get; set; } public string RefreshToken { get; set; }
public DateTime Expiration { get; set; }
} }
public class LoginModel public class LoginModel
@ -24,17 +25,25 @@ namespace LANCommander.Controllers.Api
public string Password { get; set; } public string Password { get; set; }
} }
public class RegisterModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
public class AuthController : ControllerBase public class AuthController : ControllerBase
{ {
private readonly UserManager<User> UserManager; private readonly UserManager<User> UserManager;
private readonly IUserStore<User> UserStore;
private readonly RoleManager<Role> RoleManager; private readonly RoleManager<Role> RoleManager;
private readonly LANCommanderSettings Settings; private readonly LANCommanderSettings Settings;
public AuthController(UserManager<User> userManager, RoleManager<Role> roleManager) public AuthController(UserManager<User> userManager, IUserStore<User> userStore, RoleManager<Role> roleManager)
{ {
UserManager = userManager; UserManager = userManager;
UserStore = userStore;
RoleManager = roleManager; RoleManager = roleManager;
Settings = SettingService.GetSettings(); Settings = SettingService.GetSettings();
} }
@ -44,38 +53,16 @@ namespace LANCommander.Controllers.Api
{ {
var user = await UserManager.FindByNameAsync(model.UserName); var user = await UserManager.FindByNameAsync(model.UserName);
if (user != null && await UserManager.CheckPasswordAsync(user, model.Password)) try
{ {
var userRoles = await UserManager.GetRolesAsync(user); var token = await Login(user, model.Password);
var authClaims = new List<Claim> return Ok(token);
{ }
new Claim(ClaimTypes.Name, user.UserName), catch
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) {
}; return Unauthorized();
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
var token = GetToken(authClaims);
var refreshToken = GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiration = DateTime.Now.AddDays(Settings.TokenLifetime);
await UserManager.UpdateAsync(user);
return Ok(new
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(token),
RefreshToken = refreshToken,
Expiration = token.ValidTo
});
} }
return Unauthorized();
} }
[HttpPost("Validate")] [HttpPost("Validate")]
@ -125,6 +112,82 @@ namespace LANCommander.Controllers.Api
}); });
} }
[HttpPost("Register")]
public async Task<IActionResult> Register([FromBody] RegisterModel model)
{
var user = await UserManager.FindByNameAsync(model.UserName);
if (user != null)
return Unauthorized(new
{
Message = "Username is unavailable"
});
user = new User();
await UserStore.SetUserNameAsync(user, model.UserName, CancellationToken.None);
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
try
{
var token = await Login(user, model.Password);
return Ok(token);
}
catch
{
return BadRequest(new
{
Message = "An unknown error occurred"
});
}
}
return Unauthorized(new
{
Message = "Error:\n" + String.Join('\n', result.Errors.Select(e => e.Description))
});
}
private async Task<TokenModel> Login(User user, string password)
{
if (user != null && await UserManager.CheckPasswordAsync(user, password))
{
var userRoles = await UserManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
}
var token = GetToken(authClaims);
var refreshToken = GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiration = DateTime.Now.AddDays(Settings.TokenLifetime);
await UserManager.UpdateAsync(user);
return new TokenModel()
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(token),
RefreshToken = refreshToken,
Expiration = token.ValidTo
};
}
throw new Exception("Invalid username or password");
}
private JwtSecurityToken GetToken(List<Claim> authClaims) private JwtSecurityToken GetToken(List<Claim> authClaims)
{ {
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Settings.TokenSecret)); var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Settings.TokenSecret));

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
namespace LANCommander.Controllers.Api
{
public class PingController : Controller
{
public IActionResult Index()
{
return Ok("Pong!");
}
}
}