mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-25 00:42:57 -04:00
Add admin db project password seeding logic, extended init.sh (#113)
This commit is contained in:
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Admin", "src\Ali
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Admin2", "src\AliasVault.Admin2\AliasVault.Admin2.csproj", "{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InitializationCLI", "src\Utilities\InitializationCLI\InitializationCLI.csproj", "{857BCD0E-753F-437A-AF75-B995B4D9A5FE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -115,6 +117,10 @@ Global
|
||||
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -131,6 +137,7 @@ Global
|
||||
{A9C9A606-C87E-4298-AB32-09B1884D7487} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
|
||||
{B095A174-E528-4D38-BEC1-D1D38B3B30C0} = {8A477241-B96C-4174-968D-D40CB77F1ECD}
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
|
||||
{857BCD0E-753F-437A-AF75-B995B4D9A5FE} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FEE82475-C009-4762-8113-A6563D9DC49E}
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
services:
|
||||
admin:
|
||||
image: aliasvault-admin
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Admin2/Dockerfile
|
||||
ports:
|
||||
- "8080:8082"
|
||||
volumes:
|
||||
- ./database:/database
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
client:
|
||||
image: aliasvault-client
|
||||
build:
|
||||
|
||||
166
init.sh
166
init.sh
@@ -13,6 +13,25 @@ NC='\033[0m' # No Color
|
||||
ENV_FILE=".env"
|
||||
ENV_EXAMPLE_FILE=".env.example"
|
||||
|
||||
# Define verbose flag
|
||||
VERBOSE=false
|
||||
|
||||
# Function to parse command-line arguments
|
||||
parse_args() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--verbose)
|
||||
VERBOSE=true
|
||||
;;
|
||||
*)
|
||||
printf "${RED}Unknown argument: $1${NC}\n"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
# Function to generate a new 32-character JWT key
|
||||
generate_jwt_key() {
|
||||
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 | head -c 32
|
||||
@@ -87,6 +106,125 @@ set_smtp_tls_enabled() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to generate a random admin password and store its hash in the .env file with progress indication
|
||||
generate_admin_password() {
|
||||
if grep -q "^ADMIN_PASSWORD_HASH=" ".env"; then
|
||||
ADMIN_PASSWORD_GENERATED=$(grep "^ADMIN_PASSWORD_GENERATED=" ".env" | cut -d '=' -f2)
|
||||
|
||||
printf "${CYAN}> ADMIN_PASSWORD_HASH already exists in .env. Last generated at ${ADMIN_PASSWORD_GENERATED}.${NC}\n"
|
||||
|
||||
printf "\n"
|
||||
read -p " Do you want to update the admin password? (yes/no): " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
printf "${CYAN}> Admin password will not be changed.${NC}\n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Remove existing entries
|
||||
sed -i '' '/^ADMIN_PASSWORD_HASH=/d' .env
|
||||
sed -i '' '/^ADMIN_PASSWORD_GENERATED=/d' .env
|
||||
fi
|
||||
|
||||
ADMIN_PASSWORD=$(openssl rand -base64 12)
|
||||
printf "\n"
|
||||
printf "${BLUE} Building Docker image for password generation...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker build -t initcli -f src/Utilities/InitializationCLI/Dockerfile .
|
||||
else
|
||||
{
|
||||
docker build -t initcli -f src/Utilities/InitializationCLI/Dockerfile . | while IFS= read -r line; do
|
||||
printf "."
|
||||
done
|
||||
} > init_build_output.log 2>&1 &
|
||||
BUILD_PID=$!
|
||||
while kill -0 $BUILD_PID 2> /dev/null; do
|
||||
printf "."
|
||||
sleep 1
|
||||
done
|
||||
wait $BUILD_PID
|
||||
BUILD_EXIT_CODE=$?
|
||||
if [ $BUILD_EXIT_CODE -ne 0 ]; then
|
||||
printf "\n${RED} Error occurred while building Docker image:${NC}\n"
|
||||
cat init_build_output.log
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
printf "${BLUE} Running Docker container to generate admin password hash...${NC}\n"
|
||||
{
|
||||
ADMIN_PASSWORD_HASH=$(docker run --rm initcli "$ADMIN_PASSWORD")
|
||||
} &> init_run_output.log
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "${RED} Error occurred while running Docker container:${NC}\n"
|
||||
cat init_run_output.log
|
||||
return 1
|
||||
fi
|
||||
|
||||
CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Append new entries
|
||||
echo "ADMIN_PASSWORD_HASH=$ADMIN_PASSWORD_HASH" >> .env
|
||||
echo "ADMIN_PASSWORD_GENERATED=$CURRENT_TIME" >> .env
|
||||
|
||||
printf "\n"
|
||||
printf "${CYAN}> New admin password generated successfully.${NC}\n"
|
||||
}
|
||||
|
||||
# Function to build and run the Docker Compose stack with muted output unless an error occurs, showing progress indication
|
||||
build_and_run_docker_compose() {
|
||||
printf "\n"
|
||||
printf "${BLUE}Building Docker Compose stack...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker-compose build
|
||||
else
|
||||
{
|
||||
docker-compose build | while IFS= read -r line; do
|
||||
printf "."
|
||||
done
|
||||
} > compose_build_output.log 2>&1 &
|
||||
BUILD_PID=$!
|
||||
while kill -0 $BUILD_PID 2> /dev/null; do
|
||||
printf "."
|
||||
sleep 1
|
||||
done
|
||||
wait $BUILD_PID
|
||||
BUILD_EXIT_CODE=$?
|
||||
if [ $BUILD_EXIT_CODE -ne 0 ]; then
|
||||
printf "\n${RED}Error occurred while building Docker Compose stack:${NC}\n"
|
||||
cat compose_build_output.log
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
printf "\n${BLUE}Starting Docker Compose stack...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker-compose up -d
|
||||
else
|
||||
{
|
||||
docker-compose up -d | while IFS= read -r line; do
|
||||
printf "."
|
||||
done
|
||||
} > compose_up_output.log 2>&1 &
|
||||
UP_PID=$!
|
||||
while kill -0 $UP_PID 2> /dev/null; do
|
||||
printf "."
|
||||
sleep 1
|
||||
done
|
||||
wait $UP_PID
|
||||
UP_EXIT_CODE=$?
|
||||
if [ $UP_EXIT_CODE -ne 0 ]; then
|
||||
printf "\n${RED}Error occurred while starting Docker Compose stack:${NC}\n"
|
||||
cat compose_up_output.log
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "\n${GREEN}Docker Compose stack built and started successfully.${NC}\n"
|
||||
}
|
||||
|
||||
|
||||
# Function to print the CLI logo
|
||||
print_logo() {
|
||||
printf "${MAGENTA}\n"
|
||||
@@ -104,15 +242,35 @@ print_logo() {
|
||||
|
||||
# Run the functions and print status
|
||||
print_logo
|
||||
printf "${BLUE}Initializing AliasVault...${NC}\n"
|
||||
printf "${BLUE}+++ Initializing .env file...${NC}\n"
|
||||
printf "\n"
|
||||
create_env_file
|
||||
populate_jwt_key
|
||||
set_smtp_allowed_domains
|
||||
set_smtp_tls_enabled
|
||||
printf "${BLUE}Initialization complete.${NC}\n"
|
||||
generate_admin_password
|
||||
printf "\n${BLUE}+++ Finish initializing .env file...${NC}\n"
|
||||
build_and_run_docker_compose
|
||||
printf "${BLUE}If no errors are reported, the AliasVault Docker containers should have started successfully.${NC}\n"
|
||||
printf "\n"
|
||||
printf "To build the images and start the containers, run the following command:\n"
|
||||
printf "${MAGENTA}=========================================================${NC}\n"
|
||||
printf "\n"
|
||||
printf "${CYAN}$ docker compose up -d --build --force-recreate${NC}\n"
|
||||
printf "AliasVault is successfully initialized!\n"
|
||||
printf "\n"
|
||||
printf "You can now login to the admin panel:\n"
|
||||
printf "\n"
|
||||
if [ "$ADMIN_PASSWORD" != "" ]; then
|
||||
printf "${CYAN}Admin Panel: http://localhost:8080/${NC}\n"
|
||||
printf "${CYAN}Username: admin${NC}\n"
|
||||
printf "${CYAN}Password: $ADMIN_PASSWORD${NC}\n"
|
||||
printf "\n"
|
||||
printf "(!) Caution: Make sure to backup the above credentials in a safe place, they won't be shown again!\n"
|
||||
printf "\n"
|
||||
else
|
||||
printf "${CYAN}Admin Panel: http://localhost:8080/${NC}\n"
|
||||
printf "${CYAN}Username: admin${NC}\n"
|
||||
printf "${CYAN}Password: (Previously set.)${NC}\n"
|
||||
printf "\n"
|
||||
printf "\n"
|
||||
fi
|
||||
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Admin2.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Admin2.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6"/>
|
||||
@@ -26,6 +34,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\stylecop.json">
|
||||
<Link>stylecop.json</Link>
|
||||
</AdditionalFiles>
|
||||
<AdditionalFiles Include="Auth\Pages\AccessDenied.razor" />
|
||||
<AdditionalFiles Include="Auth\Pages\ConfirmEmail.razor" />
|
||||
<AdditionalFiles Include="Auth\Pages\ConfirmEmailChange.razor" />
|
||||
@@ -52,13 +63,10 @@
|
||||
<AdditionalFiles Include="Main\Components\Alerts\GlobalNotificationDisplay.razor" />
|
||||
<AdditionalFiles Include="Main\Components\Alerts\ServerValidationErrors.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\ChangePassword.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\DeletePersonalData.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\Disable2fa.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\Email.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\EnableAuthenticator.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\GenerateRecoveryCodes.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\Index.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\PersonalData.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\ResetAuthenticator.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\SetPassword.razor" />
|
||||
<AdditionalFiles Include="Main\Pages\Account\Manage\TwoFactorAuthentication.razor" />
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<EditForm Model="Input" FormName="LoginForm" OnValidSubmit="LoginUser" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
||||
<InputTextField id="email" @bind-Value="Input.Email" placeholder="name@company.com" />
|
||||
<ValidationMessage For="() => Input.Email"/>
|
||||
<label asp-for="Input.UserName" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your username</label>
|
||||
<InputTextField id="username" @bind-Value="Input.UserName" placeholder="username" />
|
||||
<ValidationMessage For="() => Input.UserName"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
|
||||
@@ -56,7 +56,7 @@
|
||||
{
|
||||
ServerValidationErrors.Clear();
|
||||
|
||||
var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
|
||||
var result = await SignInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
Logger.LogInformation("User logged in.");
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
private sealed class InputModel
|
||||
{
|
||||
[Required] [EmailAddress] public string Email { get; set; } = "";
|
||||
[Required] public string UserName { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
<input type="hidden" name="Input.Code" value="@Input.Code"/>
|
||||
<div class="form-floating mb-3">
|
||||
<InputText @bind-Value="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com"/>
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<ValidationMessage For="() => Input.Email" class="text-danger"/>
|
||||
<InputText @bind-Value="Input.UserName" class="form-control" autocomplete="username" aria-required="true" placeholder="username"/>
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<ValidationMessage For="() => Input.UserName" class="text-danger"/>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<InputText type="password" @bind-Value="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your password."/>
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
private async Task OnValidSubmitAsync()
|
||||
{
|
||||
var user = await UserManager.FindByEmailAsync(Input.Email);
|
||||
var user = await UserManager.FindByNameAsync(Input.UserName);
|
||||
if (user is null)
|
||||
{
|
||||
// Don't reveal that the user does not exist
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
private sealed class InputModel
|
||||
{
|
||||
[Required] [EmailAddress] public string Email { get; set; } = "";
|
||||
[Required] public string UserName { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
|
||||
26
src/AliasVault.Admin2/Config.cs
Normal file
26
src/AliasVault.Admin2/Config.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Config.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Admin2;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration class for the Admin project with values loaded from environment variables.
|
||||
/// </summary>
|
||||
public class Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the admin password hash which is generated by init.sh and will be set
|
||||
/// as the default password for the admin user.
|
||||
/// </summary>
|
||||
public string AdminPasswordHash { get; set; } = "false";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last time the password was changed. This is used to check if the
|
||||
/// password hash generated by init.sh should replace the current password hash if user already exists.
|
||||
/// </summary>
|
||||
public DateTime LastPasswordChanged { get; set; } = DateTime.MinValue;
|
||||
}
|
||||
@@ -1,23 +1,33 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
# Use the official .NET 8 SDK image to build the app
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/AliasVault.Admin2/AliasVault.Admin2.csproj", "src/AliasVault.Admin2/"]
|
||||
RUN dotnet restore "src/AliasVault.Admin2/AliasVault.Admin2.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/src/AliasVault.Admin2"
|
||||
RUN dotnet build "AliasVault.Admin2.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "AliasVault.Admin2.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
# Copy the solution file to the /src directory in the container
|
||||
COPY aliasvault.sln ./
|
||||
|
||||
# Copy the project file to the /src/AliasVault directory in the container
|
||||
COPY src/AliasVault.Admin2/AliasVault.Admin2.csproj ./AliasVault.Admin2/
|
||||
|
||||
# Restore dependencies for the AliasVault project
|
||||
RUN dotnet restore "./AliasVault.Admin2/AliasVault.Admin2.csproj" --verbosity detailed
|
||||
|
||||
# Copy the rest of the application code to the /src directory in the container
|
||||
COPY src/. ./
|
||||
|
||||
# Publish the application to the /app/publish directory in the container
|
||||
WORKDIR /src/AliasVault.Admin2
|
||||
RUN dotnet publish -c Release -o /app --verbosity detailed
|
||||
|
||||
# Use the official ASP.NET Core runtime image to run the app
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app/AliasVault.Admin2
|
||||
|
||||
# Copy the published output from the build stage to the runtime stage
|
||||
COPY --from=build /app ./
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 8082
|
||||
ENV ASPNETCORE_URLS=http://+:8082
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "AliasVault.Admin2.dll"]
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Change password</h3>
|
||||
<StatusMessage Message="@message" class="mb-6"/>
|
||||
<EditForm Model="Input" FormName="change-password" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
|
||||
@@ -38,7 +37,6 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string? message;
|
||||
private bool hasPassword;
|
||||
|
||||
[CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
|
||||
@@ -57,9 +55,12 @@
|
||||
private async Task OnValidSubmitAsync()
|
||||
{
|
||||
var changePasswordResult = await UserManager.ChangePasswordAsync(UserService.User(), Input.OldPassword, Input.NewPassword);
|
||||
var user = UserService.User();
|
||||
user.LastPasswordChanged = DateTime.UtcNow;
|
||||
await UserService.UpdateUserAsync(user);
|
||||
if (!changePasswordResult.Succeeded)
|
||||
{
|
||||
message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}";
|
||||
GlobalNotificationService.AddErrorMessage($"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using AliasServerDb;
|
||||
using AliasVault.Admin2;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -10,6 +12,18 @@ using Microsoft.Data.Sqlite;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Create global config object, get values from environment variables.
|
||||
Config config = new Config();
|
||||
var adminPasswordHash = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_HASH")
|
||||
?? throw new KeyNotFoundException("ADMIN_PASSWORD_HASH environment variable is not set.");
|
||||
config.AdminPasswordHash = adminPasswordHash;
|
||||
|
||||
var lastPasswordChanged = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_GENERATED")
|
||||
?? throw new KeyNotFoundException("ADMIN_PASSWORD_GENERATED environment variable is not set.");
|
||||
config.LastPasswordChanged = DateTime.ParseExact(lastPasswordChanged, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
|
||||
|
||||
builder.Services.AddSingleton(config);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
@@ -64,6 +78,7 @@ builder.Services.AddIdentityCore<AdminUser>(options =>
|
||||
options.Password.RequiredLength = 8;
|
||||
options.Password.RequiredUniqueChars = 0;
|
||||
options.SignIn.RequireConfirmedAccount = false;
|
||||
options.User.RequireUniqueEmail = false;
|
||||
})
|
||||
.AddRoles<AdminRole>()
|
||||
.AddEntityFrameworkStores<AliasServerDbContext>()
|
||||
@@ -92,4 +107,10 @@ app.UseAntiforgery();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
await StartupTasks.CreateRolesIfNotExist(scope.ServiceProvider);
|
||||
await StartupTasks.SetAdminUser(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5216",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ADMIN_PASSWORD_HASH": "AQAAAAIAAYagAAAAEKWfKfa2gh9Z72vjAlnNP1xlME7FsunRznzyrfqFte40FToufRwa3kX8wwDwnEXZag==",
|
||||
"ADMIN_PASSWORD_GENERATED": "2024-07-22T16:36:02Z"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
|
||||
@@ -187,9 +187,9 @@ public class UserService
|
||||
/// Update user.
|
||||
/// </summary>
|
||||
/// <param name="user">User object.</param>
|
||||
/// <param name="newPassword">New password for the user.</param>
|
||||
/// <param name="newPassword">Optional parameter for new password for the user.</param>
|
||||
/// <returns>List of errors if any.</returns>
|
||||
public async Task<List<string>> UpdateUserAsync(AdminUser user, string newPassword)
|
||||
public async Task<List<string>> UpdateUserAsync(AdminUser user, string newPassword = "")
|
||||
{
|
||||
var errors = await ValidateUser(user, newPassword, isUpdate: true);
|
||||
if (errors.Count > 0)
|
||||
|
||||
70
src/AliasVault.Admin2/StartupTasks.cs
Normal file
70
src/AliasVault.Admin2/StartupTasks.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace AliasVault.Admin2;
|
||||
|
||||
using AliasServerDb;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
/// <summary>
|
||||
/// Startup tasks that should be run when the application starts.
|
||||
/// </summary>
|
||||
public static class StartupTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the roles if they do not exist.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">IServiceProvider instance.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public static async Task CreateRolesIfNotExist(IServiceProvider serviceProvider)
|
||||
{
|
||||
var roleManager = serviceProvider.GetRequiredService<RoleManager<AdminRole>>();
|
||||
|
||||
const string adminRole = "Admin";
|
||||
|
||||
if (!await roleManager.RoleExistsAsync(adminRole))
|
||||
{
|
||||
await roleManager.CreateAsync(new AdminRole(adminRole));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the admin user if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider"></param>
|
||||
/// <returns>Async Task.</returns>
|
||||
public static async Task SetAdminUser(IServiceProvider serviceProvider)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AdminUser>>();
|
||||
var adminUser = await userManager.FindByNameAsync("admin");
|
||||
var config = serviceProvider.GetRequiredService<Config>();
|
||||
|
||||
if (adminUser == null)
|
||||
{
|
||||
var adminPasswordHash = config.AdminPasswordHash;
|
||||
adminUser = new AdminUser();
|
||||
adminUser.UserName = "admin";
|
||||
|
||||
await userManager.CreateAsync(adminUser);
|
||||
adminUser.PasswordHash = adminPasswordHash;
|
||||
await userManager.UpdateAsync(adminUser);
|
||||
|
||||
Console.WriteLine("Admin user created.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the password hash is correct, if not, update it to the .env value.
|
||||
if (adminUser.PasswordHash != config.AdminPasswordHash)
|
||||
{
|
||||
if (config.LastPasswordChanged > adminUser.LastPasswordChanged)
|
||||
{
|
||||
// The password has been changed in the .env file, update the user's password hash.
|
||||
adminUser.PasswordHash = config.AdminPasswordHash;
|
||||
|
||||
adminUser.LastPasswordChanged = DateTime.UtcNow;
|
||||
await userManager.UpdateAsync(adminUser);
|
||||
|
||||
Console.WriteLine("Admin password hash updated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,4 +14,8 @@ using Microsoft.AspNetCore.Identity;
|
||||
/// </summary>
|
||||
public class AdminUser : IdentityUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the last time the password was changed.
|
||||
/// </summary>
|
||||
public DateTime? LastPasswordChanged { get; set; }
|
||||
}
|
||||
|
||||
@@ -19,14 +19,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
[Index(nameof(PushNotificationSent))]
|
||||
public class Email
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Email"/> class.
|
||||
/// </summary>
|
||||
public Email()
|
||||
{
|
||||
Attachments = new HashSet<EmailAttachment>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the email.
|
||||
/// </summary>
|
||||
@@ -110,5 +102,5 @@ public class Email
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of email attachments.
|
||||
/// </summary>
|
||||
public virtual ICollection<EmailAttachment> Attachments { get; set; }
|
||||
public virtual ICollection<EmailAttachment> Attachments { get; set; } = [];
|
||||
}
|
||||
|
||||
487
src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs
generated
Normal file
487
src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs
generated
Normal file
@@ -0,0 +1,487 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using AliasServerDb;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
[DbContext(typeof(AliasServerDbContext))]
|
||||
[Migration("20240722144205_AddAdminUserPasswordSetDate")]
|
||||
partial class AddAdminUserPasswordSetDate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.6")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true);
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AdminRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AdminRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AdminUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastPasswordChanged")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AdminUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AliasVaultRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Verifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AliasVaultUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExpireDate")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AliasVaultUserRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Email", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("From")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FromDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FromLocal")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessageHtml")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessagePlain")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessagePreview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessageSource")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PushNotificationSent")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("To")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ToDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ToLocal")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Visible")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Date");
|
||||
|
||||
b.HasIndex("DateSystem");
|
||||
|
||||
b.HasIndex("PushNotificationSent");
|
||||
|
||||
b.HasIndex("ToLocal");
|
||||
|
||||
b.HasIndex("Visible");
|
||||
|
||||
b.ToTable("Emails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.EmailAttachment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("Bytes")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EmailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Filesize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EmailId");
|
||||
|
||||
b.ToTable("EmailAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Vault", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("VaultBlob")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Vaults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.ToTable("UserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.EmailAttachment", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.Email", "Email")
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("EmailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Email");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Vault", b =>
|
||||
{
|
||||
b.HasOne("AliasServerDb.AliasVaultUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Email", b =>
|
||||
{
|
||||
b.Navigation("Attachments");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAdminUserPasswordSetDate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastPasswordChanged",
|
||||
table: "AdminUsers",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastPasswordChanged",
|
||||
table: "AdminUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,9 @@ namespace AliasServerDb.Migrations
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastPasswordChanged")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
26
src/Utilities/InitializationCLI/Dockerfile
Normal file
26
src/Utilities/InitializationCLI/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
# Copy csproj files and restore as distinct layers
|
||||
COPY ["src/Utilities/InitializationCLI/InitializationCLI.csproj", "src/Utilities/InitializationCLI/"]
|
||||
COPY ["src/Databases/AliasServerDb/AliasServerDb.csproj", "src/Databases/AliasServerDb/"]
|
||||
RUN dotnet restore "src/Utilities/InitializationCLI/InitializationCLI.csproj"
|
||||
|
||||
# Copy the entire source code
|
||||
COPY . .
|
||||
|
||||
# Build the project
|
||||
RUN dotnet build "src/Utilities/InitializationCLI/InitializationCLI.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "src/Utilities/InitializationCLI/InitializationCLI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "InitializationCLI.dll"]
|
||||
21
src/Utilities/InitializationCLI/InitializationCLI.csproj
Normal file
21
src/Utilities/InitializationCLI/InitializationCLI.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
<DependentUpon>Dockerfile</DependentUpon>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Databases\AliasServerDb\AliasServerDb.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
17
src/Utilities/InitializationCLI/Program.cs
Normal file
17
src/Utilities/InitializationCLI/Program.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
using System;
|
||||
using AliasServerDb;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Please provide a password as an argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
var password = args[0];
|
||||
var hasher = new PasswordHasher<IdentityUser>();
|
||||
var user = new AdminUser();
|
||||
var hashedPassword = hasher.HashPassword(user, password);
|
||||
|
||||
Console.WriteLine($"{hashedPassword}");
|
||||
Reference in New Issue
Block a user