Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## 3.2.3
- Feature[#935]: Web 新增修改密码功能
## 3.2.2
- Feature: 新增登录功能
- Feature[#935]: Web 新增登录功能
## 3.2.1
- Feature: 镜像新增linux/386和linux/arm/v7架构
- Feature: 镜像新增 linux/386 和 linux/arm/v7 架构
## 3.2.0
- Fix: 修复大会员大积分签到任务
- Fix: 修复大会员大积分的签到和浏览追番频道任务
Expand Down
2 changes: 1 addition & 1 deletion common.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Authors>Ray</Authors>
<Version>3.2.2</Version>
<Version>3.2.3</Version>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,11 @@
<span class="bi bi-calendar-nav-menu" aria-hidden="true"></span> Schedules
</NavLink>
</div>

<div class="nav-item px-3">
<NavLink class="nav-link" href="Admin">
<span class="bi bi-person-circle" aria-hidden="true"></span> Admin
</NavLink>
</div>
</nav>
</div>
4 changes: 4 additions & 0 deletions src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-calendar-event' viewBox='0 0 16 16'%3E%3Cpath d='M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z'/%3E%3Cpath d='M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z'/%3E%3C/svg%3E");
}

.bi-person-circle {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-circle' viewBox='0 0 16 16'%3E%3Cpath d='M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0'/%3E%3Cpath fill-rule='evenodd' d='M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1'/%3E%3C/svg%3E");
}

.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
Expand Down
39 changes: 39 additions & 0 deletions src/Ray.BiliBiliTool.Web/Components/Pages/Admin.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@page "/Admin"
@using Microsoft.AspNetCore.Authorization
@rendermode InteractiveServer
@attribute [Authorize]

<PageTitle>Change Password</PageTitle>

<MudText Typo="Typo.h5">Change Password</MudText>

<MudCard Class="mx-auto my-4" Style="max-width: 500px;">
<MudCardContent>
@if (!string.IsNullOrEmpty(_errorMessage))
{
<MudAlert Severity="Severity.Error" Class="mb-3">@_errorMessage</MudAlert>
}
@if (!string.IsNullOrEmpty(_successMessage))
{
<MudAlert Severity="Severity.Success" Class="mb-3">@_successMessage</MudAlert>
}
<MudTextField @bind-Value="_username" Label="New User Name" Required="true" RequiredError="Username cannot be empty" />
<MudTextField @bind-Value="_currentPassword" Label="Current Password" Required="true" RequiredError="Password cannot be empty"
InputType="@_currentPasswordInput" Adornment="Adornment.End"
AdornmentIcon="@_currentPasswordInputIcon" OnAdornmentClick="ToggleCurrentPasswordVisibility" />
<MudTextField @bind-Value="_newPassword" Label="New Password" Required="true" RequiredError="Password cannot be empty"
InputType="@_passwordInput" Adornment="Adornment.End"
AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="TogglePasswordVisibility" />
<MudTextField @bind-Value="_confirmPassword" Label="Confirm Password" Required="true" RequiredError="Password cannot be empty"
InputType="@_passwordInput" Adornment="Adornment.End"
AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="TogglePasswordVisibility" />
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ChangePasswordAsync"
Class="ml-auto">Submit</MudButton>
</MudCardActions>
</MudCard>

<MudThemeProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
99 changes: 99 additions & 0 deletions src/Ray.BiliBiliTool.Web/Components/Pages/Admin.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
using Ray.BiliBiliTool.Web.Services;

namespace Ray.BiliBiliTool.Web.Components.Pages;

public partial class Admin : ComponentBase
{
[Inject]
private NavigationManager NavigationManager { get; set; } = null!;

[Inject]
private IAuthService AuthService { get; set; } = null!;

private string _username = "";
private string _currentPassword = "";
private string _newPassword = "";
private string _confirmPassword = "";
private string _errorMessage = "";
private string _successMessage = "";

private bool _passwordVisibility;
private InputType _passwordInput = InputType.Password;
private string _passwordInputIcon = Icons.Material.Filled.VisibilityOff;

private bool _currentPasswordVisibility;
private InputType _currentPasswordInput = InputType.Password;
private string _currentPasswordInputIcon = Icons.Material.Filled.VisibilityOff;

private void TogglePasswordVisibility()
{
if (_passwordVisibility)
{
_passwordVisibility = false;
_passwordInputIcon = Icons.Material.Filled.VisibilityOff;
_passwordInput = InputType.Password;
}
else
{
_passwordVisibility = true;
_passwordInputIcon = Icons.Material.Filled.Visibility;
_passwordInput = InputType.Text;
}
}

private void ToggleCurrentPasswordVisibility()
{
if (_currentPasswordVisibility)
{
_currentPasswordVisibility = false;
_currentPasswordInputIcon = Icons.Material.Filled.VisibilityOff;
_currentPasswordInput = InputType.Password;
}
else
{
_currentPasswordVisibility = true;
_currentPasswordInputIcon = Icons.Material.Filled.Visibility;
_currentPasswordInput = InputType.Text;
}
}

protected override async Task OnInitializedAsync()
{
_username = await AuthService.GetAdminUserNameAsync();
}

private async Task ChangePasswordAsync()
{
_errorMessage = "";
_successMessage = "";

if (_newPassword != _confirmPassword)
{
_errorMessage = "The new password and the confirm password do not match";
return;
}

if (string.IsNullOrWhiteSpace(_newPassword))
{
_errorMessage = "Password cannot be empty";
return;
}

try
{
await AuthService.ChangePasswordAsync(_username, _currentPassword, _newPassword);
_successMessage = "Update Successful, you will be logged out in 2 seconds";
await Task.Delay(2000);
_currentPassword = "";
_newPassword = "";
_confirmPassword = "";
NavigationManager.NavigateTo("/auth/logout", true);
}
catch (Exception e)
{
_errorMessage = e.Message;
}
}
}
Empty file.
5 changes: 5 additions & 0 deletions src/Ray.BiliBiliTool.Web/Components/Pages/Admin.razor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class Admin {

}

window.Admin = Admin;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PageTitle>Schedules</PageTitle>

<div>
<MudText Typo="Typo.h4">Schedules</MudText>
<MudText Typo="Typo.h5">Schedules</MudText>

<MudDataGrid @ref="_scheduleDataGrid" Items="@ScheduledJobs"
MultiSelection="true" Filterable="false"
Expand Down
32 changes: 32 additions & 0 deletions src/Ray.BiliBiliTool.Web/Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Ray.BiliBiliTool.Web.Services;
public interface IAuthService
{
Task<ClaimsIdentity> LoginAsync(string username, string password);
Task ChangePasswordAsync(string username, string currentPassword, string newPassword);
Task<string> GetAdminUserNameAsync();
}

public class AuthService(IDbContextFactory<BiliDbContext> dbFactory) : IAuthService
Expand All @@ -33,4 +35,34 @@ public async Task<ClaimsIdentity> LoginAsync(string username, string password)

return new ClaimsIdentity();
}

public async Task ChangePasswordAsync(
string username,
string currentPassword,
string newPassword
)
{
await using var context = await dbFactory.CreateDbContextAsync();
var user = await context.Users.FirstAsync(u => u.Id == 1);

if (!PasswordHelper.VerifyPassword(currentPassword, user.Salt, user.PasswordHash))
Comment on lines +46 to +48
Copy link

Copilot AI Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a hardcoded user ID may lead to issues in multi-user environments. Consider querying the user based on the provided username or another flexible identifier.

Suggested change
var user = await context.Users.FirstAsync(u => u.Id == 1);
if (!PasswordHelper.VerifyPassword(currentPassword, user.Salt, user.PasswordHash))
var user = await context.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null || !PasswordHelper.VerifyPassword(currentPassword, user.Salt, user.PasswordHash))

Copilot uses AI. Check for mistakes.
{
throw new Exception("Current password is incorrect.");
}

var (hash, salt) = PasswordHelper.HashPassword(newPassword);

user.Salt = salt;
user.PasswordHash = hash;
user.Username = username;

await context.SaveChangesAsync();
}

public async Task<string> GetAdminUserNameAsync()
{
await using var context = await dbFactory.CreateDbContextAsync();
var user = await context.Users.FirstAsync(u => u.Id == 1);
return user.Username;
}
}
Loading