mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-02-18 15:05:58 -05:00
Add reset admin password script for all-in-one image (#1108)
This commit is contained in:
committed by
Leendert de Borst
parent
db874d3799
commit
d587f3fd5c
45
.github/workflows/docker-build.yml
vendored
45
.github/workflows/docker-build.yml
vendored
@@ -95,6 +95,51 @@ jobs:
|
||||
fi
|
||||
echo "✅ Admin endpoint (/admin) returned HTTP 200"
|
||||
|
||||
- name: Verify admin password hash file does not exist initially
|
||||
run: |
|
||||
if [ -f "./secrets/admin_password_hash" ]; then
|
||||
echo "❌ Admin password hash file should not exist initially"
|
||||
cat ./secrets/admin_password_hash
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin password hash file correctly does not exist initially"
|
||||
|
||||
- name: Test admin password reset flow
|
||||
run: |
|
||||
echo "🔧 Testing admin password reset flow..."
|
||||
|
||||
# Run the reset password script with auto-confirm
|
||||
echo "Running reset-admin-password.sh script..."
|
||||
password_output=$(docker exec aliasvault-test reset-admin-password.sh -y 2>&1)
|
||||
echo "Script output:"
|
||||
echo "$password_output"
|
||||
|
||||
# Extract the generated password from the output
|
||||
generated_password=$(echo "$password_output" | grep -E "^Password: " | sed 's/Password: //')
|
||||
if [ -z "$generated_password" ]; then
|
||||
echo "❌ Failed to extract generated password from script output"
|
||||
echo "Full output was:"
|
||||
echo "$password_output"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Generated password extracted: $generated_password"
|
||||
|
||||
# Verify that the admin_password_hash file now exists in the container
|
||||
if ! docker exec aliasvault-test test -f /secrets/admin_password_hash; then
|
||||
echo "❌ Admin password hash file was not created in container"
|
||||
docker exec aliasvault-test ls -la /secrets/
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin password hash file created in container"
|
||||
|
||||
# Verify that the admin_password_hash file exists locally (mounted volume)
|
||||
if [ ! -f "./secrets/admin_password_hash" ]; then
|
||||
echo "❌ Admin password hash file not found in local secrets folder"
|
||||
ls -la ./secrets/
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Admin password hash file exists in local secrets folder"
|
||||
|
||||
- name: Test SMTP port
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
|
||||
@@ -9,31 +9,60 @@
|
||||
|
||||
<ServerValidationErrors @ref="ServerValidationErrors" />
|
||||
|
||||
<EditForm Model="Input" FormName="LoginForm" OnValidSubmit="LoginUser" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<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" type="text" 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>
|
||||
<InputTextField id="password" @bind-Value="Input.Password" type="password" placeholder="••••••••" />
|
||||
<ValidationMessage For="() => Input.Password"/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start">
|
||||
<div class="flex items-center h-5">
|
||||
<input id="remember" aria-describedby="remember" name="remember" type="checkbox" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
|
||||
@if (!IsAdminConfigured)
|
||||
{
|
||||
<div class="mt-8 p-6 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
||||
Admin User Not Configured
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
||||
<p class="mb-3">The admin user has not been configured yet. To set up admin access:</p>
|
||||
<ol class="list-decimal list-inside space-y-1 mb-3">
|
||||
<li>Connect to your Docker container: <code class="bg-yellow-100 dark:bg-yellow-800 px-1 py-0.5 rounded text-xs">docker exec -it [container-name] /bin/bash</code></li>
|
||||
<li>Run the password reset script: <code class="bg-yellow-100 dark:bg-yellow-800 px-1 py-0.5 rounded text-xs">reset-admin-password.sh</code></li>
|
||||
<li>Restart the container to apply changes: <code class="bg-yellow-100 dark:bg-yellow-800 px-1 py-0.5 rounded text-xs">docker restart [container-name]</code></li>
|
||||
</ol>
|
||||
<p class="text-xs">Replace <code class="bg-yellow-100 dark:bg-yellow-800 px-1 py-0.5 rounded">[container-name]</code> with your actual container name, e.g. "aliasvault".</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="remember" class="font-medium text-gray-900 dark:text-white">Remember me</label>
|
||||
</div>
|
||||
<a href="user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">Lost Password?</a>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditForm Model="Input" FormName="LoginForm" OnValidSubmit="LoginUser" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<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" type="text" 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>
|
||||
<InputTextField id="password" @bind-Value="Input.Password" type="password" placeholder="••••••••" />
|
||||
<ValidationMessage For="() => Input.Password"/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Login to your account</button>
|
||||
</EditForm>
|
||||
<div class="flex items-start">
|
||||
<div class="flex items-center h-5">
|
||||
<input id="remember" aria-describedby="remember" name="remember" type="checkbox" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="remember" class="font-medium text-gray-900 dark:text-white">Remember me</label>
|
||||
</div>
|
||||
<a href="user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">Lost Password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Login to your account</button>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
@@ -43,10 +72,17 @@
|
||||
|
||||
[SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
|
||||
|
||||
private bool IsAdminConfigured { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Check if admin user exists
|
||||
var adminUser = await UserManager.FindByNameAsync("admin");
|
||||
IsAdminConfigured = adminUser != null;
|
||||
|
||||
if (HttpMethods.IsGet(HttpContext.Request.Method))
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
|
||||
@@ -34,9 +34,19 @@ builder.Services.ConfigureLogging(builder.Configuration, Assembly.GetExecutingAs
|
||||
var config = new Config();
|
||||
|
||||
// Get admin password hash and generation timestamp using SecretReader
|
||||
var (adminPasswordHash, lastPasswordChanged) = SecretReader.GetAdminPasswordHash();
|
||||
config.AdminPasswordHash = adminPasswordHash;
|
||||
config.LastPasswordChanged = lastPasswordChanged;
|
||||
// If the admin password hash file doesn't exist, leave config values empty (admin user won't be created)
|
||||
try
|
||||
{
|
||||
var (adminPasswordHash, lastPasswordChanged) = SecretReader.GetAdminPasswordHash();
|
||||
config.AdminPasswordHash = adminPasswordHash;
|
||||
config.LastPasswordChanged = lastPasswordChanged;
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
// Admin password hash not configured - this is expected when no password has been set yet
|
||||
config.AdminPasswordHash = string.Empty;
|
||||
config.LastPasswordChanged = DateTime.MinValue;
|
||||
}
|
||||
|
||||
var ipLoggingEnabled = Environment.GetEnvironmentVariable("IP_LOGGING_ENABLED") ?? "false";
|
||||
config.IpLoggingEnabled = bool.Parse(ipLoggingEnabled);
|
||||
|
||||
@@ -33,7 +33,7 @@ public static class StartupTasks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the admin user if it does not exist.
|
||||
/// Creates the admin user if it does not exist and admin password hash is configured.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">IServiceProvider instance.</param>
|
||||
/// <returns>Async Task.</returns>
|
||||
@@ -44,6 +44,14 @@ public static class StartupTasks
|
||||
var adminUser = await userManager.FindByNameAsync("admin");
|
||||
var config = serviceProvider.GetRequiredService<Config>();
|
||||
|
||||
// Skip admin user creation if no admin password hash is configured
|
||||
if (string.IsNullOrEmpty(config.AdminPasswordHash))
|
||||
{
|
||||
Console.WriteLine("Admin password hash not configured - skipping admin user creation.");
|
||||
Console.WriteLine("Run 'reset-admin-password.sh' to configure the admin password.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (adminUser == null)
|
||||
{
|
||||
var adminPasswordHash = config.AdminPasswordHash;
|
||||
@@ -59,9 +67,9 @@ public static class StartupTasks
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the password hash is different AND the password in .env file is newer than the password of user.
|
||||
// If so, update the password hash of the user in the database so it matches the one in the .env file.
|
||||
if (adminUser.PasswordHash != config.AdminPasswordHash && (adminUser.LastPasswordChanged is null || config.LastPasswordChanged > adminUser.LastPasswordChanged))
|
||||
// Check if the password hash is different AND the hash in secret file is newer than the password of user.
|
||||
// If so, update the password hash of the user in the database so it matches the one in the admin_password_hash file.
|
||||
if (adminUser.PasswordHash != config.AdminPasswordHash && (adminUser.LastPasswordChanged is null || (config.LastPasswordChanged != DateTime.MinValue && config.LastPasswordChanged > adminUser.LastPasswordChanged)))
|
||||
{
|
||||
// The password has been changed in the .env file, update the user's password hash.
|
||||
adminUser.PasswordHash = config.AdminPasswordHash;
|
||||
|
||||
@@ -554,6 +554,40 @@ video {
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
@@ -978,6 +1012,10 @@ video {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.list-inside {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.list-decimal {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
@@ -1245,6 +1283,11 @@ video {
|
||||
border-color: rgb(234 179 8 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-yellow-200 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(254 240 138 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
||||
@@ -1794,6 +1837,11 @@ video {
|
||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-yellow-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(202 138 4 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
@@ -2092,6 +2140,11 @@ video {
|
||||
border-color: rgb(234 179 8 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:border-yellow-800:is(.dark *) {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(133 77 14 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-blue-800:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
@@ -2183,6 +2236,10 @@ video {
|
||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-yellow-900\/20:is(.dark *) {
|
||||
background-color: rgb(113 63 18 / 0.2);
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:is(.dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2282,6 +2339,16 @@ video {
|
||||
color: rgb(254 240 138 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-yellow-300:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(253 224 71 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-yellow-400:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(250 204 21 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:placeholder-gray-400:is(.dark *)::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||
|
||||
@@ -174,10 +174,6 @@ public class UserManagementTests : AdminPlaywrightTest
|
||||
// Wait for the user details page to load
|
||||
await WaitForUrlAsync($"users/{_testUserId}**", _testUserEmail);
|
||||
|
||||
// Verify we're on the correct user's page
|
||||
var pageContent = await Page.TextContentAsync("body");
|
||||
Assert.That(pageContent, Does.Contain(_testUserEmail), "Test user email should be visible on the user details page");
|
||||
|
||||
// Click the edit username button (the SVG edit icon)
|
||||
var editButton = Page.Locator("button[id='edit-username-button']");
|
||||
await editButton.ClickAsync();
|
||||
@@ -186,7 +182,7 @@ public class UserManagementTests : AdminPlaywrightTest
|
||||
await Page.WaitForSelectorAsync("text=Change Username");
|
||||
|
||||
// Verify the form appeared
|
||||
pageContent = await Page.TextContentAsync("body");
|
||||
var pageContent = await Page.TextContentAsync("body");
|
||||
Assert.That(pageContent, Does.Contain("Change Username"), "Username change form should appear after clicking edit button");
|
||||
Assert.That(pageContent, Does.Contain("Changing a username is permanent"), "Warning message should be visible");
|
||||
|
||||
|
||||
@@ -106,6 +106,10 @@ COPY --from=dotnet-builder /app/installcli /usr/local/bin/aliasvault-cli
|
||||
RUN chmod +x /usr/local/bin/aliasvault-cli/AliasVault.InstallCli && \
|
||||
ln -s /usr/local/bin/aliasvault-cli/AliasVault.InstallCli /usr/local/bin/aliasvault-cli.sh
|
||||
|
||||
# Copy password reset script and make it executable
|
||||
COPY dockerfiles/all-in-one/reset-admin-password.sh /usr/local/bin/reset-admin-password.sh
|
||||
RUN chmod +x /usr/local/bin/reset-admin-password.sh
|
||||
|
||||
# Copy client nginx configuration and ensure wwwroot is accessible
|
||||
COPY apps/server/AliasVault.Client/nginx.conf /app/client/nginx.conf
|
||||
|
||||
|
||||
182
dockerfiles/all-in-one/reset-admin-password.sh
Executable file
182
dockerfiles/all-in-one/reset-admin-password.sh
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
CONFIRM_RESET=false
|
||||
PASSWORD_LENGTH=16
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-y|--yes)
|
||||
CONFIRM_RESET=true
|
||||
shift
|
||||
;;
|
||||
-l|--length)
|
||||
PASSWORD_LENGTH="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Reset the admin password with a randomly generated password"
|
||||
echo ""
|
||||
echo "OPTIONS:"
|
||||
echo " -y, --yes Skip confirmation prompt"
|
||||
echo " -l, --length NUM Password length (default: 16)"
|
||||
echo " -h, --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown option: $1${NC}"
|
||||
echo "Use -h or --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Function to generate a secure random password
|
||||
generate_password() {
|
||||
local length=$1
|
||||
# Generate password with uppercase, lowercase, numbers, and special characters
|
||||
# Using /dev/urandom for cryptographically secure randomness
|
||||
local password=$(tr -dc 'A-Za-z0-9!@#$%^&*()_+=-' < /dev/urandom | head -c "$length")
|
||||
echo "$password"
|
||||
}
|
||||
|
||||
# Function to hash the password using the aliasvault-cli
|
||||
hash_password() {
|
||||
local password=$1
|
||||
local hash
|
||||
|
||||
# Check if aliasvault-cli.sh exists
|
||||
if [ ! -f /usr/local/bin/aliasvault-cli.sh ] && [ ! -L /usr/local/bin/aliasvault-cli.sh ]; then
|
||||
echo -e "${RED}Error: aliasvault-cli.sh not found${NC}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Hash the password
|
||||
hash=$(/usr/local/bin/aliasvault-cli.sh hash-password "$password" 2>/dev/null)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$hash" ]; then
|
||||
echo -e "${RED}Error: Failed to hash password${NC}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$hash"
|
||||
}
|
||||
|
||||
# Function to update the admin password hash file
|
||||
update_hash_file() {
|
||||
local hash=$1
|
||||
local hash_file="/secrets/admin_password_hash"
|
||||
|
||||
# Create /secrets directory if it doesn't exist
|
||||
if [ ! -d "/secrets" ]; then
|
||||
mkdir -p /secrets
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Failed to create /secrets directory${NC}" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get current timestamp in ISO8601 format (UTC)
|
||||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Write hash and timestamp to file
|
||||
cat > "$hash_file" <<EOF
|
||||
$hash|$timestamp
|
||||
EOF
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Failed to write hash to $hash_file${NC}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set appropriate permissions (readable by the application, not world-readable)
|
||||
chmod 600 "$hash_file"
|
||||
|
||||
echo -e "${GREEN}Password hash updated successfully${NC}"
|
||||
echo -e "Hash file: $hash_file"
|
||||
echo -e "Updated at: $timestamp"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
echo -e "${YELLOW}=== AliasVault Admin Password Reset ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if running in Docker container
|
||||
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then
|
||||
echo -e "${YELLOW}Warning: This script appears to be running outside of a Docker container${NC}"
|
||||
echo -e "${YELLOW}The password hash file will be created at: /secrets/admin_password_hash${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Confirmation prompt
|
||||
if [ "$CONFIRM_RESET" = false ]; then
|
||||
echo -e "${YELLOW}This will reset the admin password with a new randomly generated password.${NC}"
|
||||
echo -e "${YELLOW}The current admin password (if any) will be permanently overwritten.${NC}"
|
||||
echo ""
|
||||
read -p "Are you sure you want to reset the admin password? (yes/no): " confirm
|
||||
|
||||
if [[ ! "$confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
|
||||
echo -e "${RED}Password reset cancelled${NC}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Generating new password..."
|
||||
|
||||
# Generate random password
|
||||
NEW_PASSWORD=$(generate_password "$PASSWORD_LENGTH")
|
||||
|
||||
if [ -z "$NEW_PASSWORD" ]; then
|
||||
echo -e "${RED}Error: Failed to generate password${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Hash the password
|
||||
PASSWORD_HASH=$(hash_password "$NEW_PASSWORD")
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$PASSWORD_HASH" ]; then
|
||||
echo -e "${RED}Error: Failed to hash password${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update the hash file
|
||||
update_hash_file "$PASSWORD_HASH"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Failed to update password hash file${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Admin password reset successful!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}New Admin Credentials:${NC}"
|
||||
echo -e "Username: ${GREEN}admin${NC}"
|
||||
echo -e "Password: ${GREEN}$NEW_PASSWORD${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}IMPORTANT:${NC}"
|
||||
echo -e "1. Save this password securely - it will not be shown again"
|
||||
echo -e "2. The password hash has been saved to /secrets/admin_password_hash"
|
||||
echo -e "3. Restart the Docker container for the new password to take effect"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
@@ -62,33 +62,9 @@ else
|
||||
log 0 "[init] ✅ JWT key already exists"
|
||||
fi
|
||||
|
||||
if [ ! -f /secrets/admin_password_hash ]; then
|
||||
log 0 "[init] → Generating admin password hash..."
|
||||
# Generate a random admin password if not provided
|
||||
if [ -z "$ADMIN_PASSWORD" ]; then
|
||||
ADMIN_PASSWORD=$(openssl rand -base64 16 | tr -d "\n")
|
||||
# Only show password in verbose mode during init
|
||||
if [ "$VERBOSITY" -ge 2 ]; then
|
||||
echo "[init] ⚠️ Generated random admin password: $ADMIN_PASSWORD"
|
||||
echo "[init] ⚠️ Please save this password securely and/or optionally change it after first login!"
|
||||
fi
|
||||
# Save the password temporarily for the final notification (only if newly generated)
|
||||
echo "$ADMIN_PASSWORD" > /secrets/admin_password_temp
|
||||
chmod 600 /secrets/admin_password_temp
|
||||
fi
|
||||
# Use the InstallCLI to hash the password and append generation timestamp
|
||||
if [ "$VERBOSITY" -ge 2 ]; then
|
||||
HASH=$(/usr/local/bin/aliasvault-cli/AliasVault.InstallCli hash-password "$ADMIN_PASSWORD")
|
||||
else
|
||||
HASH=$(/usr/local/bin/aliasvault-cli/AliasVault.InstallCli hash-password "$ADMIN_PASSWORD" 2>/dev/null)
|
||||
fi
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
echo "${HASH}|${TIMESTAMP}" > /secrets/admin_password_hash
|
||||
chmod 600 /secrets/admin_password_hash
|
||||
log 0 "[init] Admin password hash saved to /secrets/admin_password_hash"
|
||||
else
|
||||
log 0 "[init] ✅ Admin password hash already exists"
|
||||
fi
|
||||
# Admin password is not created by default
|
||||
# Users must run reset-admin-password.sh to configure the admin password
|
||||
log 0 "[init] ✅ Admin password configuration deferred to manual setup"
|
||||
|
||||
# Read PostgreSQL password for database initialization
|
||||
POSTGRES_PASSWORD=$(cat /secrets/postgres_password)
|
||||
|
||||
@@ -32,22 +32,21 @@ if [ "$VERBOSITY" -le 1 ]; then
|
||||
echo " • Admin: https://localhost:443/admin"
|
||||
echo ""
|
||||
|
||||
# Show admin credentials if available
|
||||
if [ -f /secrets/admin_password_temp ]; then
|
||||
ADMIN_PASSWORD=$(cat /secrets/admin_password_temp)
|
||||
# Check if admin password hash file exists to determine which message to show
|
||||
if [ -f /secrets/admin_password_hash ]; then
|
||||
# Admin password hash exists - show the legacy warning
|
||||
echo "🔑 Admin Login:"
|
||||
echo " • Username: admin"
|
||||
echo " • Password: ${ADMIN_PASSWORD}"
|
||||
echo " • Password: (previously set - to reset the admin password, login to this container via \`docker exec -it [container-name] /bin/bash\` and run: reset-admin-password.sh)"
|
||||
echo ""
|
||||
echo "⚠️ IMPORTANT: Save these credentials securely!"
|
||||
echo " This password won't be shown again."
|
||||
echo ""
|
||||
# Clean up the temporary password file
|
||||
rm -f /secrets/admin_password_temp
|
||||
else
|
||||
echo "🔑 Admin Login:"
|
||||
echo " • Username: admin"
|
||||
echo " • Password: (previously set - to reset the admin password, delete the file ./secrets/admin_password_hash and restart the container)"
|
||||
# No admin password hash file - show setup instructions
|
||||
echo "🔑 Admin Setup:"
|
||||
echo " • Admin user is not configured by default"
|
||||
echo " • To configure admin access:"
|
||||
echo " 1. docker exec -it [container-name] /bin/bash"
|
||||
echo " 2. reset-admin-password.sh"
|
||||
echo " 3. Restart the container"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user