Added streamer mode and fixed security settings ui
This commit is contained in:
@@ -33,6 +33,7 @@ public class User
|
|||||||
public bool SupportPending { get; set; } = false;
|
public bool SupportPending { get; set; } = false;
|
||||||
public bool HasRated { get; set; } = false;
|
public bool HasRated { get; set; } = false;
|
||||||
public int Rating { get; set; } = 0;
|
public int Rating { get; set; } = 0;
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
public bool TotpEnabled { get; set; } = false;
|
public bool TotpEnabled { get; set; } = false;
|
||||||
|
|||||||
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
1080
Moonlight/App/Database/Migrations/20230625190428_AddedStreamerMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedStreamerMode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StreamerMode",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -791,6 +791,9 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<int>("Status")
|
b.Property<int>("Status")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("StreamerMode")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<int>("SubscriptionDuration")
|
b.Property<int>("SubscriptionDuration")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
6
Moonlight/App/Models/Forms/UserPreferencesDataModel.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class UserPreferencesDataModel
|
||||||
|
{
|
||||||
|
public bool StreamerMode { get; set; } = false;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
|
|
||||||
@@ -38,21 +39,24 @@ public class TotpService
|
|||||||
return user!.TotpSecret;
|
return user!.TotpSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Enable()
|
public async Task GenerateSecret()
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpSecret = GenerateSecret();
|
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
//TODO: AuditLog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnforceTotpLogin()
|
public async Task Enable(string code)
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
|
if (!await Verify(user.TotpSecret, code))
|
||||||
|
{
|
||||||
|
throw new DisplayException("The 2fa code you entered is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
user.TotpEnabled = true;
|
user.TotpEnabled = true;
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
@@ -62,14 +66,10 @@ public class TotpService
|
|||||||
var user = (await IdentityService.Get())!;
|
var user = (await IdentityService.Get())!;
|
||||||
|
|
||||||
user.TotpEnabled = false;
|
user.TotpEnabled = false;
|
||||||
|
user.TotpSecret = "";
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
//TODO: AuditLog
|
//TODO: AuditLog
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateSecret()
|
|
||||||
{
|
|
||||||
return Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
{
|
{
|
||||||
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
<SmartForm Model="TotpData" OnValidSubmit="DoLogin">
|
||||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||||
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent"></InputText>
|
<InputText @bind-Value="TotpData.Code" type="number" class="form-control bg-transparent" placeholder="@(SmartTranslateService.Translate("2fa code"))"></InputText>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid mb-10">
|
<div class="d-grid mb-10">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<a class="text-gray-900 fs-2 fw-bold me-1">@(User.FirstName) @(User.LastName)</a>
|
<a class="text-gray-900 fs-2 fw-bold me-1 @(User.StreamerMode ? "blur" : "")">@(User.FirstName) @(User.LastName)</a>
|
||||||
|
|
||||||
@if (User.Status == UserStatus.Verified)
|
@if (User.Status == UserStatus.Verified)
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
||||||
<span class="d-flex align-items-center text-gray-400 mb-2">
|
<span class="d-flex align-items-center text-gray-400 mb-2 @(User.StreamerMode ? "blur" : "")">
|
||||||
@(User.Email)
|
@(User.Email)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,14 +69,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="fw-bold d-flex align-items-center fs-5">
|
<div class="fw-bold d-flex align-items-center fs-5">
|
||||||
|
<div class="@(User.StreamerMode ? "blur" : "")">
|
||||||
@(User.FirstName) @(User.LastName)
|
@(User.FirstName) @(User.LastName)
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (User.Admin)
|
@if (User.Admin)
|
||||||
{
|
{
|
||||||
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
<span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Admin</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<a class="fw-semibold text-muted text-hover-primary fs-7">@(User.Email)</a>
|
<a class="fw-semibold text-muted text-hover-primary fs-7 @(User.StreamerMode ? "blur" : "")">@(User.Email)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<SmartForm OnValidSubmit="Save" Model="Model">
|
<SmartForm OnValidSubmit="Save" Model="Model">
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="card mb-5 mb-xl-10">
|
||||||
<div class="card-body p-9">
|
<div class="card-body p-9 @(CurrentUser.StreamerMode ? "blur" : "")">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
<div class="col-lg-6 fv-row fv-plugins-icon-container">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private UserDataModel Model = new UserDataModel();
|
private UserDataModel Model = new();
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public User CurrentUser { get; set; }
|
public User CurrentUser { get; set; }
|
||||||
|
|||||||
@@ -2,130 +2,152 @@
|
|||||||
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
@using Moonlight.Shared.Components.Navigations
|
||||||
@using QRCoder
|
@using QRCoder
|
||||||
@using Moonlight.App.Services.Sessions
|
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using System.Text.RegularExpressions
|
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Models.Misc
|
@using Mappy.Net
|
||||||
|
@using Moonlight.App.Models.Forms
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
|
||||||
@inject TotpService TotpService
|
@inject TotpService TotpService
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IdentityService IdentityService
|
|
||||||
@inject UserService UserService
|
@inject UserService UserService
|
||||||
@inject AlertService AlertService
|
@inject NavigationManager NavigationManager
|
||||||
@inject ToastService ToastService
|
@inject ModalService ModalService
|
||||||
|
@inject Repository<User> UserRepository
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<ProfileNavigation Index="2"/>
|
<ProfileNavigation Index="2"/>
|
||||||
|
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="row">
|
||||||
<LazyLoader Load="Load">
|
<div class="col-12 col-md-6 p-3">
|
||||||
@if (TotpEnabled)
|
<div class="card">
|
||||||
{
|
<div class="card-header">
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
<div class="card-title">
|
||||||
<table class="w-100">
|
<TL>Two factor authentication</TL>
|
||||||
<tr>
|
|
||||||
<td rowspan="2">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-100">
|
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
|
||||||
<TL>Your account is secured with 2fa</TL>
|
|
||||||
</h4>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<a @onclick="Disable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
|
||||||
<TL>Disable</TL>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>anyone write a fancy text here?</TL>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="card-body fs-6">
|
||||||
</table>
|
<p>
|
||||||
|
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
||||||
|
</p>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
@if (User.TotpEnabled)
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Disable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-danger"
|
||||||
|
OnClick="DisableTwoFactor">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="StartTwoFactorWizard">
|
||||||
|
</WButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 p-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
|
<TL>Password</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body fs-6">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input @bind="Password" class="form-control" type="password"/>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="ChangePassword">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 p-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title">
|
||||||
|
<TL>Preferences</TL>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body fs-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input @bind="UserModel.StreamerMode" class="form-check-input" type="checkbox" role="switch" id="streamerModeSwitch">
|
||||||
|
<label class="form-check-label" for="streamerModeSwitch">
|
||||||
|
<TL>Streamer mode</TL>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="text-end">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||||
|
WorkingText=""
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="SavePreferences">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Modals *@
|
||||||
|
<div class="modal fade" id="2fa" tabindex="-1" style="display: none" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">
|
||||||
|
<TL>Activate 2fa</TL>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body fs-6">
|
||||||
|
@if (!User.TotpEnabled)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(User.TotpSecret))
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<TL>Make sure you have installed one of the following apps on your smartphone and press continue</TL>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://authy.com/download/" target="_blank">Authy</a>
|
||||||
|
<br/>
|
||||||
|
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Continue"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Preparing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="GenerateTwoFactorToken">
|
||||||
|
</WButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="alert alert-primary d-flex rounded ms-6 me-6 mt-6 mb-8">
|
<p>
|
||||||
<table class="w-100">
|
<TL>Scan the qr code and enter the code generated by the app you have scanned it in</TL>
|
||||||
<tr>
|
</p>
|
||||||
<td rowspan="2">
|
<div class="mt-3 text-center">
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-primary">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-100">
|
|
||||||
<h4 class="text-gray-900 fw-bold ms-4">
|
|
||||||
<TL>Secure your account</TL>
|
|
||||||
</h4>
|
|
||||||
</td>
|
|
||||||
<td rowspan="2">
|
|
||||||
<a @onclick="Enable" class="btn btn-primary px-6 align-self-center text-nowrap" data-bs-toggle="modal" data-bs-target="#twofactorauth">
|
|
||||||
<TL>Enable</TL>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fs-6 text-gray-700 pe-7 ms-4">
|
|
||||||
<TL>2fa adds another layer of security to your account. You have to enter a 6 digit code in order to login.</TL>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="modal fade" id="twofactorauth" tabindex="-1" style="display: none;" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header flex-stack py-6">
|
|
||||||
<h2 class="ms-3">
|
|
||||||
<TL>Activate 2fa</TL>
|
|
||||||
</h2>
|
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
|
||||||
<span class="svg-icon svg-icon-1">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-dark fw-bold mb-3 mt-2">
|
|
||||||
<TL>2fa apps</TL>
|
|
||||||
</h3>
|
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
|
||||||
<TL>Use an app like </TL>
|
|
||||||
<a href="https://support.google.com/accounts/answer/1066447?hl=en" target="_blank">Google Authenticator</a>,
|
|
||||||
<a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a>,
|
|
||||||
<a href="https://authy.com/download/" target="_blank">Authy</a>, <TL>or</TL>
|
|
||||||
<a href="https://support.1password.com/one-time-passwords/" target="_blank">1Password</a> <TL>and scan the following QR Code</TL>
|
|
||||||
@if (EnablingTotp)
|
|
||||||
{
|
|
||||||
<div class="pt-5 text-center">
|
|
||||||
@{
|
@{
|
||||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||||
|
|
||||||
var qrCodeData = qrGenerator.CreateQrCode
|
var qrCodeData = qrGenerator.CreateQrCode
|
||||||
(
|
(
|
||||||
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={TotpSecret}&issuer={Uri.EscapeDataString(Issuer)}",
|
$"otpauth://totp/{Uri.EscapeDataString(User.Email)}?secret={User.TotpSecret}&issuer={Uri.EscapeDataString("Moonlight")}",
|
||||||
QRCodeGenerator.ECCLevel.Q
|
QRCodeGenerator.ECCLevel.Q
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -135,184 +157,77 @@
|
|||||||
}
|
}
|
||||||
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
<img src="data:image/png;base64,@(base64)" alt="" class="mw-200px mt-2">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-3 d-flex justify-content-center">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text"
|
||||||
|
@bind="TwoFactorCode"
|
||||||
|
placeholder="@(SmartTranslateService.Translate("Enter your 2fa code here"))"
|
||||||
|
class="form-control"/>
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Enable"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Processing"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="EnableTwoFactor">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed mb-8 p-6">
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-warning me-4">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.3" x="2" y="2" width="20" height="20" rx="10" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="14" width="7" height="2" rx="1" transform="rotate(-90 11 14)" fill="currentColor"></rect>
|
|
||||||
<rect x="11" y="17" width="2" height="2" rx="1" transform="rotate(-90 11 17)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div class="d-flex flex-stack flex-grow-1">
|
|
||||||
<div class="fw-semibold">
|
|
||||||
<div class="fs-6 text-gray-700">
|
|
||||||
<TL>If you have trouble using the QR Code, select manual input in the app and enter your email and the following code:</TL>
|
|
||||||
<div class="fw-bold text-dark pt-2">@(TotpSecret)</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-primary px-6 align-self-center text-nowrap float-end" data-bs-toggle="modal" data-bs-target="#test">
|
|
||||||
<TL>Next</TL>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="test" tabindex="-1" style="display: none;" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered mw-650px">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header flex-stack py-6">
|
|
||||||
<h2 class="ms-3">
|
|
||||||
<TL>Finish activation</TL>
|
|
||||||
</h2>
|
|
||||||
<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
|
|
||||||
<span class="svg-icon svg-icon-1">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect opacity="0.5" x="6" y="17.3137" width="16" height="2" rx="1" transform="rotate(-45 6 17.3137)" fill="currentColor"></rect>
|
|
||||||
<rect x="7.41422" y="6" width="16" height="2" rx="1" transform="rotate(45 7.41422 6)" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body scroll-y ps-10 pe-10 pb-10">
|
|
||||||
<div class="text-gray-500 fw-semibold fs-6 mb-10">
|
|
||||||
<div class="alert alert-primary d-flex align-items-center p-5 mb-6">
|
|
||||||
<i class="bx bx-info-circle fs-2hx text-primary me-4">
|
|
||||||
<span class="path1"></span><span class="path2"></span>
|
|
||||||
</i>
|
|
||||||
<div class="d-flex flex-column">
|
|
||||||
<h4 class="mb-1 text-primary">
|
|
||||||
<TL>2fa Code requiered</TL>
|
|
||||||
</h4>
|
|
||||||
<span>In order to finish the activation of 2fa, you need to enter the code your 2fa app shows you.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control form-control-lg form-control-solid mb-0" placeholder="@SmartTranslateService.Translate("2fa Code")" @bind="currentTotp"/>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<WButton CssClasses="btn btn-primary mb-2 align-self-center text-nowrap float-end" WorkingText="@SmartTranslateService.Translate("Saving")" Text="@SmartTranslateService.Translate("Finish")" OnClick="CheckAndSaveTotp">
|
|
||||||
</WButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="separator mt-2"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-danger d-flex rounded ms-6 me-6 mt-6 mb-8 bg-body">
|
|
||||||
<div class="w-100">
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="svg-icon svg-icon-2tx svg-icon-body">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path opacity="0.3" d="M20.5543 4.37824L12.1798 2.02473C12.0626 1.99176 11.9376 1.99176 11.8203 2.02473L3.44572 4.37824C3.18118 4.45258 3 4.6807 3 4.93945V13.569C3 14.6914 3.48509 15.8404 4.4417 16.984C5.17231 17.8575 6.18314 18.7345 7.446 19.5909C9.56752 21.0295 11.6566 21.912 11.7445 21.9488C11.8258 21.9829 11.9129 22 12.0001 22C12.0872 22 12.1744 21.983 12.2557 21.9488C12.3435 21.912 14.4326 21.0295 16.5541 19.5909C17.8169 18.7345 18.8277 17.8575 19.5584 16.984C20.515 15.8404 21 14.6914 21 13.569V4.93945C21 4.6807 20.8189 4.45258 20.5543 4.37824Z" fill="currentColor"></path>
|
|
||||||
<path d="M10.5606 11.3042L9.57283 10.3018C9.28174 10.0065 8.80522 10.0065 8.51412 10.3018C8.22897 10.5912 8.22897 11.0559 8.51412 11.3452L10.4182 13.2773C10.8099 13.6747 11.451 13.6747 11.8427 13.2773L15.4859 9.58051C15.771 9.29117 15.771 8.82648 15.4859 8.53714C15.1948 8.24176 14.7183 8.24176 14.4272 8.53714L11.7002 11.3042C11.3869 11.6221 10.874 11.6221 10.5606 11.3042Z" fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-25">
|
|
||||||
<span class="text-gray-700 fw-semibold fs-6 ms-4 me-2">
|
|
||||||
<TL>New password</TL>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="w-75">
|
|
||||||
<input @bind="Password" type="password" class="form-control">
|
|
||||||
</td>
|
|
||||||
<td class="">
|
|
||||||
<WButton OnClick="ChangePassword"
|
|
||||||
CssClasses="btn-danger ms-4"
|
|
||||||
Text="@SmartTranslateService.Translate("Change")"
|
|
||||||
WorkingText="@SmartTranslateService.Translate("Changing")">
|
|
||||||
</WButton>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</LazyLoader>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private bool TotpEnabled = false;
|
[CascadingParameter]
|
||||||
private bool EnablingTotp = false;
|
public User User { get; set; }
|
||||||
private string TotpSecret = "";
|
|
||||||
private User User;
|
|
||||||
private string Issuer = "Moonlight";
|
|
||||||
private string currentTotp = "";
|
|
||||||
|
|
||||||
|
private string TwoFactorCode = "";
|
||||||
private string Password = "";
|
private string Password = "";
|
||||||
|
private UserPreferencesDataModel UserModel;
|
||||||
|
|
||||||
private async void Enable()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
//TODO: AuditLog
|
UserModel = Mapper.Map<UserPreferencesDataModel>(User);
|
||||||
await TotpService.Enable();
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
EnablingTotp = true;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckAndSaveTotp()
|
private async Task StartTwoFactorWizard()
|
||||||
{
|
{
|
||||||
if (await TotpService.Verify(TotpSecret, currentTotp))
|
await ModalService.Show("2fa");
|
||||||
{
|
|
||||||
await TotpService.EnforceTotpLogin();
|
|
||||||
TotpEnabled = true;
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
await ToastService.Success("Successfully enabled 2fa!");
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await AlertService.Error("2fa code incorrect", "The given 2fa code is incorrect. Maybe check if the code in your 2fa app has changed.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Disable()
|
private async Task GenerateTwoFactorToken()
|
||||||
{
|
{
|
||||||
//TODO: AuditLog
|
await TotpService.GenerateSecret();
|
||||||
await TotpService.Disable();
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnableTwoFactor()
|
||||||
|
{
|
||||||
|
await ModalService.Hide("2fa");
|
||||||
|
await TotpService.Enable(TwoFactorCode);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(LazyLoader lazyLoader)
|
private async Task DisableTwoFactor()
|
||||||
{
|
{
|
||||||
await lazyLoader.SetText("Requesting secrets");
|
await TotpService.Disable();
|
||||||
|
|
||||||
TotpEnabled = await TotpService.GetEnabled();
|
|
||||||
TotpSecret = await TotpService.GetSecret();
|
|
||||||
|
|
||||||
await lazyLoader.SetText("Requesting identity");
|
|
||||||
User = await IdentityService.Get();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangePassword()
|
private async Task ChangePassword()
|
||||||
{
|
|
||||||
if (Regex.IsMatch(Password, @"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z@$!%*#.,?&\d]{8,}$"))
|
|
||||||
{
|
{
|
||||||
await UserService.ChangePassword(User, Password);
|
await UserService.ChangePassword(User, Password);
|
||||||
|
|
||||||
//TODO: AuditLog
|
|
||||||
|
|
||||||
// Reload to make the user login again
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
private async Task SavePreferences()
|
||||||
{
|
{
|
||||||
await AlertService.Error("Error", "Your password must be at least 8 characters and must contain a number");
|
User = Mapper.Map(User, UserModel);
|
||||||
}
|
UserRepository.Update(User);
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
<span class="fw-bold"><TL>Shared IP</TL>:</span>
|
||||||
<span class="ms-1 text-muted">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
<span class="ms-1 text-muted @(User.StreamerMode ? "blur" : "")">@($"{CurrentServer.Node.Fqdn}:{CurrentServer.MainAllocation.Port}")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||||
|
|||||||
@@ -43,66 +43,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@foreach (var group in ServerGroups)
|
@foreach (var group in ServerGroups)
|
||||||
{
|
{
|
||||||
@*
|
|
||||||
<div class="card my-2">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-title">
|
|
||||||
@if (EditMode)
|
|
||||||
{
|
|
||||||
<input @bind="group.Name" class="form-control"/>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@(group.Name)</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (EditMode)
|
|
||||||
{
|
|
||||||
<div class="card-toolbar">
|
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Remove group"))"
|
|
||||||
WorkingText=""
|
|
||||||
CssClasses="btn-danger"
|
|
||||||
OnClick="async () => await RemoveGroup(group)">
|
|
||||||
</WButton>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row min-h-200px draggable-zone" ml-server-group="@(group.Name)">
|
|
||||||
@foreach (var id in group.Servers)
|
|
||||||
{
|
|
||||||
var server = AllServers.First(x => x.Id.ToString() == id);
|
|
||||||
|
|
||||||
<div class="col-12 col-md-3 p-3 draggable" ml-server-id="@(server.Id)">
|
|
||||||
<div class="card bg-secondary">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-title">
|
|
||||||
<h3 class="card-label">@(server.Name)</h3>
|
|
||||||
</div>
|
|
||||||
@if (EditMode)
|
|
||||||
{
|
|
||||||
<div class="card-toolbar">
|
|
||||||
<a href="#" class="btn btn-icon btn-sm btn-hover-light-primary draggable-handle">
|
|
||||||
<i class="bx bx-md bx-move"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
@if (EditMode)
|
|
||||||
{
|
|
||||||
<TL>Hidden in edit mode</TL>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>*@
|
|
||||||
<div class="accordion my-3" id="serverListGroup@(group.GetHashCode())">
|
<div class="accordion my-3" id="serverListGroup@(group.GetHashCode())">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="serverListGroup-header@(group.GetHashCode())">
|
<h2 class="accordion-header" id="serverListGroup-header@(group.GetHashCode())">
|
||||||
@@ -177,7 +117,7 @@
|
|||||||
<span class="card-text fs-6">
|
<span class="card-text fs-6">
|
||||||
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
@(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) <span class="text-gray-700">- @(server.Image.Name)</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-text my-1 fs-6 fw-bold">
|
<div class="card-text my-1 fs-6 fw-bold @(User.StreamerMode ? "blur" : "")">
|
||||||
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
@(server.Node.Fqdn):@(server.MainAllocation.Port)
|
||||||
</div>
|
</div>
|
||||||
<div class="card-text fs-6">
|
<div class="card-text fs-6">
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blur {
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
div.wave {
|
div.wave {
|
||||||
}
|
}
|
||||||
div.wave .dot {
|
div.wave .dot {
|
||||||
|
|||||||
Reference in New Issue
Block a user