Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86d7ee3e9b | ||
|
|
a39ed8c0a7 | ||
|
|
e772e722b5 | ||
|
|
b6bf431062 | ||
|
|
aa41cceff3 | ||
|
|
1baea180aa | ||
|
|
0d8143c62e | ||
|
|
4ae84052e8 | ||
|
|
c73c41ca06 | ||
|
|
5b58418e57 | ||
|
|
7c7f7549c5 | ||
|
|
38203fd767 | ||
|
|
a7b8484a84 | ||
|
|
a091a94737 | ||
|
|
2c299a82b8 | ||
|
|
5ee710750e | ||
|
|
ed5ea31ca8 | ||
|
|
ffdb427184 | ||
|
|
4cef3efa1f | ||
|
|
a5c8908c6b | ||
|
|
88c10b5a9c | ||
|
|
48d3d26be5 | ||
|
|
5caa583240 | ||
|
|
79f4749869 | ||
|
|
4de42e4a33 | ||
|
|
af9fba39f3 | ||
|
|
91b27c1bec | ||
|
|
0fb5327f04 | ||
|
|
57f6b0961c | ||
|
|
c1d70fe504 | ||
|
|
4c379802fc | ||
|
|
6a9a98b7bf | ||
|
|
d2705d0b92 | ||
|
|
fcd0397184 | ||
|
|
a8e35d5e1d | ||
|
|
cf459f748f | ||
|
|
65a2bebd51 | ||
|
|
22e29c6cf5 | ||
|
|
6fd4f7d607 | ||
|
|
dab4762e94 | ||
|
|
bb3d38f50e | ||
|
|
c7f6375fbb | ||
|
|
197248a6ea | ||
|
|
5de9a0b8d8 | ||
|
|
8a99dbf705 | ||
|
|
b6b3b88b1d | ||
|
|
0dc699ea54 | ||
|
|
a228ccb904 | ||
|
|
398e4016dc | ||
|
|
fd1e0c5d15 | ||
|
|
e6ea0c51c8 | ||
|
|
38eef67207 | ||
|
|
7ce253e93d | ||
|
|
c519b80159 | ||
|
|
e128bbf091 | ||
|
|
04315b38ba | ||
|
|
0ee1b5e992 | ||
|
|
b4b2dc3fe7 | ||
|
|
7ac9cdc9e7 | ||
|
|
9ba8bb183a | ||
|
|
7ef8a12fb2 | ||
|
|
ea7aba4ff4 | ||
|
|
aac9694d5d | ||
|
|
06d7666265 | ||
|
|
ca17759727 |
130
.github/workflows/docker-compose-build.yml
vendored
@@ -1,4 +1,3 @@
|
||||
# This workflow will test if building the Docker Compose containers from scratch works.
|
||||
name: Docker Compose Build
|
||||
|
||||
on:
|
||||
@@ -18,6 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set permissions and run install.sh
|
||||
run: |
|
||||
chmod +x install.sh
|
||||
@@ -25,76 +25,80 @@ jobs:
|
||||
|
||||
- name: Set up Docker Compose
|
||||
run: |
|
||||
# Change the exposed host port of the SmtpService from 25 to 2525 because port 25 is not allowed in GitHub Actions
|
||||
sed -i 's/25\:25/2525\:25/g' docker-compose.yml
|
||||
docker compose -f docker-compose.yml up -d
|
||||
sed -i 's/25\:25/2525\:25/g' docker-compose.yml
|
||||
docker compose -f docker-compose.yml up -d
|
||||
|
||||
- name: Wait for services to be up
|
||||
run: |
|
||||
# Wait for a few seconds
|
||||
sleep 10
|
||||
- name: Test if localhost:443 (WASM app) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -k -s -o /dev/null -w "%{http_code}" https://localhost:443)
|
||||
if [ "$http_code" -ne 200 ]; then
|
||||
echo "Service did not respond with 200 OK. Check if client app and/or nginx is configured correctly."
|
||||
exit 1
|
||||
else
|
||||
echo "Service responded with 200 OK"
|
||||
fi
|
||||
- name: Test if services are responding
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 5
|
||||
max_attempts: 5
|
||||
command: |
|
||||
sleep 5
|
||||
|
||||
- name: Test if localhost:443/api (WebApi) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -k -s -o /dev/null -w "%{http_code}" https://localhost:443/api)
|
||||
if [ "$http_code" -ne 200 ]; then
|
||||
echo "Service did not respond with expected 200 OK. Check if WebApi and/or nginx is configured correctly."
|
||||
exit 1
|
||||
else
|
||||
echo "Service responded with $http_code"
|
||||
fi
|
||||
# Array of endpoints to test
|
||||
declare -A endpoints=(
|
||||
["WASM"]="https://localhost:443"
|
||||
["WebApi"]="https://localhost:443/api"
|
||||
["Admin"]="https://localhost:443/admin/user/login"
|
||||
)
|
||||
|
||||
- name: Test if localhost:443/admin (Admin) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -k -s -o /dev/null -w "%{http_code}" https://localhost:443/admin/user/login)
|
||||
if [ "$http_code" -ne 200 ]; then
|
||||
echo "Service did not respond with expected 200 OK. Check if admin app and/or nginx is configured correctly."
|
||||
exit 1
|
||||
else
|
||||
echo "Service responded with $http_code"
|
||||
fi
|
||||
failed=false
|
||||
|
||||
- name: Test if localhost:2525 (SmtpService) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
if ! nc -zv localhost 2525 2>&1 | grep -q 'succeeded'; then
|
||||
echo "SmtpService did not respond on port 2525. Check if the SmtpService service is running."
|
||||
exit 1
|
||||
else
|
||||
echo "SmtpService responded on port 2525"
|
||||
fi
|
||||
# Test HTTP endpoints
|
||||
for name in "${!endpoints[@]}"; do
|
||||
url="${endpoints[$name]}"
|
||||
echo "Testing $name at $url"
|
||||
|
||||
# Store both response body and HTTP code
|
||||
response=$(curl -k -s -w "\nHTTP_CODE=%{http_code}" "$url")
|
||||
http_code=$(echo "$response" | grep "HTTP_CODE=" | cut -d= -f2)
|
||||
body=$(echo "$response" | sed '$d') # Remove the last line (HTTP_CODE)
|
||||
|
||||
if [ "$http_code" -ne 200 ]; then
|
||||
echo "❌ $name failed with HTTP $http_code at $url"
|
||||
echo "Response body:"
|
||||
echo "$body"
|
||||
failed=true
|
||||
else
|
||||
echo "✅ $name responded with HTTP 200"
|
||||
fi
|
||||
done
|
||||
|
||||
# Test SMTP
|
||||
echo "Testing SmtpService at localhost:2525"
|
||||
if ! nc -zv localhost 2525 2>&1 | grep -q 'succeeded'; then
|
||||
echo "❌ SmtpService failed to respond on port 2525"
|
||||
failed=true
|
||||
else
|
||||
echo "✅ SmtpService responded successfully"
|
||||
fi
|
||||
|
||||
# Exit with error if any service failed
|
||||
if [ "$failed" = true ]; then
|
||||
# Get container logs
|
||||
echo "Container Logs admin:"
|
||||
docker compose logs admin
|
||||
echo "Container Logs api:"
|
||||
docker compose logs api
|
||||
echo "Container Logs client:"
|
||||
docker compose logs client
|
||||
echo "Container Logs smtp:"
|
||||
docker compose logs smtp
|
||||
echo "Container Logs reverse-proxy:"
|
||||
docker compose logs reverse-proxy
|
||||
|
||||
# Restart containers for next test in case of failure
|
||||
docker compose restart
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test install.sh reset-password output
|
||||
run: |
|
||||
output=$(./install.sh reset-password)
|
||||
if ! echo "$output" | grep -E '.*New admin password: [A-Za-z0-9+/=]{8,}.*'; then
|
||||
echo "Password reset output format is incorrect. Expected format: 'New admin password: <at least 8 base64 chars>'"
|
||||
echo "Actual output: $output"
|
||||
echo "Password reset output format is incorrect"
|
||||
echo "Expected: 'New admin password: <at least 8 base64 chars>'"
|
||||
echo "Actual: $output"
|
||||
exit 1
|
||||
else
|
||||
echo "Password reset output format is correct"
|
||||
fi
|
||||
|
||||
13
.github/workflows/docker-compose-pull.yml
vendored
@@ -17,7 +17,18 @@ jobs:
|
||||
options: --privileged
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get repository and branch information
|
||||
id: repo-info
|
||||
run: |
|
||||
echo "REPO_FULL_NAME=${GITHUB_REPOSITORY}" >> $GITHUB_ENV
|
||||
echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download install script from current branch
|
||||
run: |
|
||||
INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/$REPO_FULL_NAME/$BRANCH_NAME/install.sh"
|
||||
echo "Downloading install script from: $INSTALL_SCRIPT_URL"
|
||||
curl -f -o install.sh "$INSTALL_SCRIPT_URL"
|
||||
|
||||
- name: Set permissions and run install.sh
|
||||
run: |
|
||||
chmod +x install.sh
|
||||
|
||||
8
.gitignore
vendored
@@ -390,6 +390,9 @@ src/Tests/AliasVault.E2ETests/appsettings.Development.json
|
||||
# .env is generated by install.sh and therefore should be ignored
|
||||
.env
|
||||
|
||||
# install.sh backup files are generated by install.sh self-update and therefore should be ignored
|
||||
install.sh.backup
|
||||
|
||||
# Draw.io diagram temp files
|
||||
*.drawio.*
|
||||
|
||||
@@ -399,3 +402,8 @@ certificates/**/*.key
|
||||
certificates/**/*.pfx
|
||||
certificates/**/*.pem
|
||||
certificates/letsencrypt/**
|
||||
|
||||
# Docs
|
||||
docs/_site
|
||||
docs/vendor
|
||||
docs/.bundle
|
||||
|
||||
62
README.md
@@ -3,7 +3,7 @@
|
||||
<h1><img src="https://github.com/user-attachments/assets/933c8b45-a190-4df6-913e-b7c64ad9938b" width="40" /> AliasVault</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.aliasvault.net">Live demo 🚀</a> • <a href="https://aliasvault.net?utm_source=gh-readme">Website 🏠</a> • <a href="#installation">Installation 📦</a>
|
||||
<a href="https://app.aliasvault.net">Live demo 🔥</a> • <a href="https://aliasvault.net?utm_source=gh-readme">Website 🌐</a> • <a href="https://docs.aliasvault.net?utm_source=gh-readme">Documentation 📚</a> • <a href="#installation">Installation ⚙️</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">
|
||||
@@ -19,6 +19,12 @@ Open-source password and alias manager
|
||||
[<img src="https://img.shields.io/sonar/quality_gate/lanedirt_AliasVault?server=https%3A%2F%2Fsonarcloud.io&label=sonarcloud&logo=sonarcloud">](https://sonarcloud.io/summary/new_code?id=lanedirt_AliasVault)
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[<img alt="Discord" src="https://img.shields.io/discord/1309300619026235422?logo=discord&logoColor=%237289da&label=join%20discord%20chat&color=%237289da">](https://discord.gg/DsaXMTEtpF)
|
||||
|
||||
</div>
|
||||
|
||||
AliasVault is an open-source password and alias manager built with C# ASP.NET technology. AliasVault can be self-hosted on your own server with Docker, providing a secure and private solution for managing your online identities and passwords.
|
||||
|
||||
### What makes AliasVault unique:
|
||||
@@ -32,19 +38,20 @@ AliasVault is an open-source password and alias manager built with C# ASP.NET te
|
||||
## Live demo
|
||||
A live demo of the app is available at the official website at [app.aliasvault.net](https://app.aliasvault.net) (up-to-date with `main` branch). You can create a free account to try it out yourself.
|
||||
|
||||
<img width="700" alt="Screenshot of AliasVault" src="docs/img/screenshot.png">
|
||||
<img width="700" alt="Screenshot of AliasVault" src="docs/assets/img/screenshot.png">
|
||||
|
||||
## Installation
|
||||
|
||||
Choose one of the following installation methods:
|
||||
To install AliasVault, the easiest method is to use the provided install script. This will download the pre-built Docker images and start the containers.
|
||||
|
||||
### Option 1: Quick Install (Pre-built Images)
|
||||
### 1. Install using install script
|
||||
|
||||
This method uses pre-built Docker images and works on minimal hardware specifications:
|
||||
- Linux (Ubuntu or RHEL based distros recommended)
|
||||
- 512MB RAM
|
||||
|
||||
- Linux VM with root access (Ubuntu or RHEL based distros recommended)
|
||||
- 1 vCPU
|
||||
- At least 16GB disk space
|
||||
- 512MB RAM
|
||||
- 16GB disk space
|
||||
- Docker installed
|
||||
|
||||
```bash
|
||||
@@ -56,28 +63,7 @@ chmod +x install.sh
|
||||
./install.sh install
|
||||
```
|
||||
|
||||
### Option 2: Build from Source
|
||||
|
||||
Building from source requires more resources:
|
||||
- Minimum 2GB RAM (more RAM will speed up build time)
|
||||
- At least 1 vCPU
|
||||
- 40GB+ disk space (for dependencies and build artifacts)
|
||||
- Docker installed
|
||||
- Git installed
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/lanedirt/AliasVault.git
|
||||
cd AliasVault
|
||||
|
||||
# Make build script executable and run it. This will create the .env file, build the Docker images from source, and start the AliasVault containers.
|
||||
chmod +x install.sh
|
||||
./install.sh build
|
||||
```
|
||||
|
||||
Note: If you do not wish to run the script, you can set up the environment variables and build the Docker image and containers manually instead. See the [manual setup instructions](docs/install/1-manually-setup-docker.md) for more information.
|
||||
|
||||
### Post-Installation
|
||||
### 2. Post-Installation
|
||||
|
||||
The install script will output the URL where the app is available. By default this is:
|
||||
- Client: https://localhost
|
||||
@@ -85,17 +71,15 @@ The install script will output the URL where the app is available. By default th
|
||||
|
||||
> Note: If you want to change the default AliasVault ports you can do so in the `docker-compose.yml` file for the `nginx` (reverse-proxy) container.
|
||||
|
||||
#### First Time Setup Notes:
|
||||
- When building from source for the first time, it may take several minutes for Docker to download and compile all dependencies. Subsequent builds will be faster.
|
||||
- A SQLite database file will be created in `./database/AliasServerDb.sqlite`. This file will store all (encrypted) password vaults. It should be kept secure and not shared.
|
||||
## Detailed documentation
|
||||
For more detailed information about the installation process and other topics, please see the official documentation website:
|
||||
- [Documentation website (docs.aliasvault.net) 📚](https://docs.aliasvault.net)
|
||||
|
||||
#### Useful Commands:
|
||||
- To reset the admin password: `./install.sh reset-password`
|
||||
- To uninstall AliasVault: `./install.sh uninstall`
|
||||
This will remove all containers, images, and volumes related to AliasVault while keeping configuration files intact for future reinstallation.
|
||||
- If something goes wrong you can run the install script in verbose mode to get more information: `./install.sh [command] --verbose`
|
||||
Here you can also find step-by-step instructions on how to install AliasVault to e.g. Azure, AWS and other popular cloud providers.
|
||||
|
||||
## Security Architecture
|
||||
<a href="https://docs.aliasvault.net/architecture"><img alt="AliasVault Security Architecture Diagram" src="docs/assets/diagrams/security-architecture/aliasvault-security-architecture-thumb.jpg" width="343"></a>
|
||||
|
||||
AliasVault takes security seriously and implements various measures to protect your data:
|
||||
|
||||
- All sensitive user data is encrypted end-to-end using industry-standard encryption algorithms. This includes the complete vault contents and all received emails.
|
||||
@@ -104,7 +88,9 @@ AliasVault takes security seriously and implements various measures to protect y
|
||||
|
||||
For detailed information about our encryption implementation and security architecture, see the following documents:
|
||||
- [SECURITY.md](SECURITY.md)
|
||||
- [Security Architecture Diagram](docs/security-architecture.md)
|
||||
- [Security Architecture Diagram](https://docs.aliasvault.net/architecture)
|
||||
|
||||
|
||||
|
||||
## Tech stack / credits
|
||||
The following technologies, frameworks and libraries are used in this project:
|
||||
|
||||
@@ -19,7 +19,7 @@ The following encryption algorithms are used by AliasVault:
|
||||
|
||||
Below is a detailed explanation of each encryption algorithm.
|
||||
|
||||
For more information about how these algorithms are specifically used in AliasVault, see the [Security Architecture](docs/security-architecture.md) document.
|
||||
For more information about how these algorithms are specifically used in AliasVault, see the [Architecture Documentation](https://docs.aliasvault.net/architecture) section on the documentation site.
|
||||
|
||||
### Argon2id
|
||||
To derive a key from the master password, AliasVault uses the Argon2id key derivation function. Argon2id is a memory-hard
|
||||
|
||||
@@ -62,7 +62,3 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
aliasvault:
|
||||
name: aliasvault_default
|
||||
|
||||
1
docs/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
docs.aliasvault.net
|
||||
8
docs/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM jekyll/jekyll:4.2.2
|
||||
|
||||
WORKDIR /srv/jekyll
|
||||
COPY . .
|
||||
RUN chown -R jekyll:jekyll /srv/jekyll
|
||||
|
||||
# Install the theme and dependencies
|
||||
RUN bundle install
|
||||
8
docs/Gemfile
Normal file
@@ -0,0 +1,8 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# gem "jekyll", "~> 4.3.2"
|
||||
gem "just-the-docs"
|
||||
|
||||
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||
gem "github-pages", group: :jekyll_plugins
|
||||
286
docs/Gemfile.lock
Normal file
@@ -0,0 +1,286 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (7.2.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.8)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.23.11)
|
||||
concurrent-ruby (1.3.4)
|
||||
connection_pool (2.4.1)
|
||||
csv (3.3.0)
|
||||
dnsruby (1.72.3)
|
||||
base64 (~> 0.2.0)
|
||||
simpleidn (~> 0.2.1)
|
||||
drb (2.2.1)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.10.0)
|
||||
faraday (2.12.1)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-net_http (3.4.0)
|
||||
net-http (>= 0.5.0)
|
||||
ffi (1.17.0-x86_64-linux-musl)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (4.1.0)
|
||||
github-pages (232)
|
||||
github-pages-health-check (= 1.18.2)
|
||||
jekyll (= 3.10.0)
|
||||
jekyll-avatar (= 0.8.0)
|
||||
jekyll-coffeescript (= 1.2.2)
|
||||
jekyll-commonmark-ghpages (= 0.5.1)
|
||||
jekyll-default-layout (= 0.1.5)
|
||||
jekyll-feed (= 0.17.0)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.16.1)
|
||||
jekyll-include-cache (= 0.2.1)
|
||||
jekyll-mentions (= 1.6.0)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.16.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.8.0)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.2.0)
|
||||
jekyll-theme-cayman (= 0.2.0)
|
||||
jekyll-theme-dinky (= 0.2.0)
|
||||
jekyll-theme-hacker (= 0.2.0)
|
||||
jekyll-theme-leap-day (= 0.2.0)
|
||||
jekyll-theme-merlot (= 0.2.0)
|
||||
jekyll-theme-midnight (= 0.2.0)
|
||||
jekyll-theme-minimal (= 0.2.0)
|
||||
jekyll-theme-modernist (= 0.2.0)
|
||||
jekyll-theme-primer (= 0.6.0)
|
||||
jekyll-theme-slate (= 0.2.0)
|
||||
jekyll-theme-tactile (= 0.2.0)
|
||||
jekyll-theme-time-machine (= 0.2.0)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.13.0)
|
||||
kramdown (= 2.4.0)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.4)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.16.2, < 2.0)
|
||||
rouge (= 3.30.0)
|
||||
terminal-table (~> 1.4)
|
||||
webrick (~> 1.8)
|
||||
github-pages-health-check (1.18.2)
|
||||
addressable (~> 2.3)
|
||||
dnsruby (~> 1.60)
|
||||
octokit (>= 4, < 8)
|
||||
public_suffix (>= 3.0, < 6.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.10.0)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
csv (~> 3.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (>= 0.7, < 2)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (>= 1.17, < 3)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
webrick (>= 1.0)
|
||||
jekyll-avatar (0.8.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.2.2)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.12)
|
||||
jekyll-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-commonmark-ghpages (0.5.1)
|
||||
commonmarker (>= 0.23.7, < 1.1.0)
|
||||
jekyll (>= 3.9, < 4.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
rouge (>= 2.0, < 5.0)
|
||||
jekyll-default-layout (0.1.5)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-feed (0.17.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.16.1)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (>= 4, < 7, != 4.4.0)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-mentions (1.6.0)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.3)
|
||||
addressable (~> 2.0)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-cayman (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-dinky (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-hacker (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-leap-day (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-merlot (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-midnight (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-minimal (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-modernist (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.6.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-tactile (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-time-machine (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.13.0)
|
||||
gemoji (>= 3, < 5)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
json (2.8.2)
|
||||
just-the-docs (0.10.0)
|
||||
jekyll (>= 3.8.5)
|
||||
jekyll-include-cache
|
||||
jekyll-seo-tag (>= 2.0)
|
||||
rake (>= 12.3.1)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
logger (1.6.1)
|
||||
mercenary (0.3.6)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.25.1)
|
||||
net-http (0.5.0)
|
||||
uri
|
||||
nokogiri (1.16.7-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
octokit (4.25.1)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (5.1.1)
|
||||
racc (1.8.1)
|
||||
rake (13.2.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.3.9)
|
||||
rouge (3.30.0)
|
||||
rubyzip (2.3.2)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sawyer (0.9.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (>= 0.17.3, < 3)
|
||||
securerandom (0.3.2)
|
||||
simpleidn (0.2.3)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
typhoeus (1.4.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (1.8.0)
|
||||
uri (1.0.2)
|
||||
webrick (1.9.0)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
github-pages
|
||||
just-the-docs
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.25
|
||||
@@ -1,5 +0,0 @@
|
||||
# Documentation
|
||||
This is the documentation for the AliasVault project.
|
||||
|
||||
## Description
|
||||
TODO: Work in progress.
|
||||
40
docs/_config.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
remote_theme: just-the-docs/just-the-docs
|
||||
title: AliasVault
|
||||
description: Documentation for the AliasVault password manager
|
||||
|
||||
logo: "/assets/img/logo.svg"
|
||||
favicon_ico: "/assets/img/favicon.png"
|
||||
|
||||
# Navigation settings
|
||||
aux_links:
|
||||
"AliasVault on GitHub":
|
||||
- "https://github.com/lanedirt/AliasVault"
|
||||
"AliasVault Website":
|
||||
- "https://aliasvault.net"
|
||||
aux_links_new_tab: true
|
||||
|
||||
# Search settings
|
||||
search_enabled: true
|
||||
heading_anchors: true
|
||||
|
||||
# Theme settings
|
||||
color_scheme: aliasvault
|
||||
|
||||
# Enable copy code button
|
||||
enable_copy_code_button: true
|
||||
|
||||
# Footer "Edit this page on GitHub" link text
|
||||
gh_edit_link: true # show or hide edit this page link
|
||||
gh_edit_link_text: "Edit this page on GitHub."
|
||||
gh_edit_repository: "https://github.com/lanedirt/AliasVault" # the github URL for your repo
|
||||
gh_edit_branch: "main" # the branch that your docs is served from
|
||||
gh_edit_source: docs # the source that your files originate from
|
||||
gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately
|
||||
|
||||
callouts:
|
||||
warning:
|
||||
title: Warning
|
||||
color: red
|
||||
note:
|
||||
title: Note
|
||||
color: purple
|
||||
0
docs/_includes/footer_custom.html
Normal file
1
docs/_includes/nav_footer_custom.html
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
42
docs/_sass/color_schemes/aliasvault.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
@import "./color_schemes/dark";
|
||||
|
||||
// Base theme colors
|
||||
$link-color: #f49541;
|
||||
$btn-primary-color: #d68338;
|
||||
|
||||
// Main colors
|
||||
$body-background-color: #1f2937;
|
||||
$sidebar-color: #111827;
|
||||
$border-color: #374151;
|
||||
$body-text-color: #f8f9fa;
|
||||
|
||||
// Navigation
|
||||
$nav-child-link-color: #fdde85;
|
||||
$search-result-preview-color: #e9ecef;
|
||||
|
||||
// Content elements
|
||||
$feedback-color: #2d3748;
|
||||
$table-background-color: #374151;
|
||||
$search-background-color: #374151;
|
||||
|
||||
// Code blocks
|
||||
$code-background-color: #2d3748;
|
||||
$code-linenumber-color: #9ca3af;
|
||||
|
||||
// Tables
|
||||
$table-border-color: #4b5563;
|
||||
|
||||
// Search
|
||||
$search-result-preview-color: #d1d5db;
|
||||
|
||||
// Buttons
|
||||
$btn-primary-color-dark: #d68338;
|
||||
|
||||
// Base Colors (kept for compatibility)
|
||||
$purple-000: #f8b963;
|
||||
$purple-100: #ffd5a8;
|
||||
$purple-200: #f49541;
|
||||
$purple-300: #d68338;
|
||||
|
||||
// Navigation additional
|
||||
$nav-button-color: #f49541;
|
||||
@@ -1,4 +1,11 @@
|
||||
# Security Architecture
|
||||
---
|
||||
layout: default
|
||||
title: Architecture
|
||||
has_children: true
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
AliasVault implements a zero-knowledge architecture where sensitive user data and passwords never leave the client device in unencrypted form. Below is a detailed explanation of how the system secures user data and communications.
|
||||
|
||||
@@ -6,12 +13,12 @@ AliasVault implements a zero-knowledge architecture where sensitive user data an
|
||||
The security architecture diagram below illustrates all encryption and authentication processes used in AliasVault to secure user data and communications.
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="diagrams/security-architecture/aliasvault-security-architecture-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="diagrams/security-architecture/aliasvault-security-architecture-light.svg">
|
||||
<img alt="AliasVault Security Architecture Diagram" src="diagrams/security-architecture/aliasvault-security-architecture-light.svg">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../assets/diagrams/security-architecture/aliasvault-security-architecture-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="../assets/diagrams/security-architecture/aliasvault-security-architecture-light.svg">
|
||||
<img alt="AliasVault Security Architecture Diagram" src="../assets/diagrams/security-architecture/aliasvault-security-architecture-light.svg">
|
||||
</picture>
|
||||
|
||||
You can also view the diagram in a browser-friendly HTML format: [AliasVault Security Architecture](diagrams/security-architecture/aliasvault-security-architecture.html)
|
||||
You can also view the diagram in a browser-friendly HTML format: [AliasVault Security Architecture](https://lanedirt.github.io/AliasVault/assets/diagrams/security-architecture/aliasvault-security-architecture.html)
|
||||
|
||||
## Key Components and Process Flow
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
# Diagrams
|
||||
|
||||
This folder contains architecture and flow diagrams for AliasVault in various formats.
|
||||
|
Before Width: | Height: | Size: 1024 KiB After Width: | Height: | Size: 1024 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 113 KiB |
BIN
docs/assets/img/favicon.png
Normal file
|
After Width: | Height: | Size: 936 B |
2
docs/assets/img/logo.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg enable-background="new 0 0 800 500" version="1.1" viewBox="-1.84 7.892 1822.253 474.315" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com" width="1822.253px" height="474.315px"><defs><bx:export><bx:file format="svg"/></bx:export></defs><path d="m459.87 294.95c0.016205 5.4005 0.03241 10.801-0.35022 16.873-1.111 6.3392-1.1941 12.173-2.6351 17.649-10.922 41.508-36.731 69.481-77.351 83.408-7.2157 2.4739-14.972 3.3702-22.479 4.995-23.629 0.042205-47.257 0.11453-70.886 0.12027-46.762 0.011322-93.523-0.01416-140.95-0.43411-8.59-2.0024-16.766-2.8352-24.398-5.3326-21.595-7.0666-39.523-19.656-53.708-37.552-10.227-12.903-17.579-27.17-21.28-43.221-1.475-6.3967-2.4711-12.904-3.6852-19.361-0.051849-5.747-0.1037-11.494 0.26915-17.886 4.159-42.973 27.68-71.638 63.562-92.153 0-0.70761-0.001961-1.6988 3.12e-4 -2.69 0.022484-9.8293-1.3071-19.894 0.35664-29.438 3.2391-18.579 11.08-35.272 23.763-49.773 12.098-13.832 26.457-23.989 43.609-30.029 7.813-2.7512 16.14-4.0417 24.234-5.9948 7.392-0.025734 14.784-0.05146 22.835 0.32253 4.1959 0.95392 7.7946 1.2538 11.258 2.1053 17.16 4.2192 32.287 12.176 45.469 24.104 2.2558 2.0411 4.372 6.6241 9.621 3.868 16.839-8.8419 34.718-11.597 53.603-8.594 16.791 2.6699 31.602 9.4308 44.236 20.636 11.531 10.227 19.84 22.841 25.393 37.236 6.3436 16.445 10.389 33.163 6.0798 49.389 7.9587 8.9321 15.807 16.704 22.421 25.414 9.162 12.065 15.33 25.746 18.144 40.776 0.97046 5.1848 1.9111 10.375 2.8654 15.563m-71.597 71.012c5.5615-5.2284 12.002-9.7986 16.508-15.817 10.474-13.992 14.333-29.916 11.288-47.446-2.2496-12.95-8.1973-24.076-17.243-33.063-12.746-12.663-28.865-18.614-46.786-18.569-69.912 0.17712-139.82 0.56831-209.74 0.96176-15.922 0.089599-29.168 7.4209-39.685 18.296-14.45 14.944-20.408 33.343-16.655 54.368 2.2763 12.754 8.2167 23.748 17.158 32.66 13.299 13.255 30.097 18.653 48.728 18.651 59.321-0.005188 118.64 0.042358 177.96-0.046601 9.5912-0.014374 19.181-0.86588 28.773-0.88855 10.649-0.025146 19.978-3.825 29.687-9.1074z" fill="#EEC170"/><path d="m162.77 293c15.654 4.3883 20.627 22.967 10.304 34.98-5.3104 6.1795-14.817 8.3208-24.278 5.0472-7.0723-2.4471-12.332-10.362-12.876-17.933-1.0451-14.542 11.089-23.176 21.705-23.046 1.5794 0.019287 3.1517 0.61566 5.1461 0.95184z" fill="#EEC170"/><path d="m227.18 293.64c7.8499 2.3973 11.938 8.2143 13.524 15.077 1.8591 8.0439-0.44817 15.706-7.1588 21.121-6.7633 5.4572-14.417 6.8794-22.578 3.1483-8.2972-3.7933-12.836-10.849-12.736-19.438 0.1687-14.497 14.13-25.368 28.948-19.908z" fill="#EEC170"/><path d="m261.57 319.07c-2.495-14.418 4.6853-22.603 14.596-26.108 9.8945-3.4995 23.181 3.4303 26.267 13.779 4.6504 15.591-7.1651 29.064-21.665 28.161-8.5254-0.53088-17.202-6.5094-19.198-15.831z" fill="#EEC170"/><path d="m336.91 333.41c-9.0175-4.2491-15.337-14.349-13.829-21.682 3.0825-14.989 13.341-20.304 23.018-19.585 10.653 0.79141 17.93 7.407 19.765 17.547 1.9588 10.824-4.1171 19.939-13.494 23.703-5.272 2.1162-10.091 1.5086-15.46 0.017883z" fill="#EEC170"/><text style="fill: rgb(255, 255, 255); font-family: Arial, sans-serif; font-size: 268.3px; font-weight: 700; white-space: pre;" x="531.151" y="358.747">AliasVault</text></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
14
docs/contact/index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: default
|
||||
title: Help and Support
|
||||
has_children: true
|
||||
nav_order: 100
|
||||
---
|
||||
|
||||
# Help and Support
|
||||
|
||||
If you need help or have any questions about installing or using AliasVault, you can reach us on [AliasVault Discord](https://discord.gg/DsaXMTEtpF).
|
||||
|
||||
If you have found a bug or have a feature request, please open an issue on [AliasVault GitHub](https://github.com/lanedirt/AliasVault/issues).
|
||||
|
||||
If you have any other questions or feedback, please use the contact form on the [AliasVault website](https://aliasvault.net/contact).
|
||||
12
docs/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
jekyll:
|
||||
build: .
|
||||
volumes:
|
||||
- .:/srv/jekyll
|
||||
ports:
|
||||
- "4000:4000"
|
||||
command: bundle exec jekyll serve --host 0.0.0.0 --watch --force_polling --livereload
|
||||
environment:
|
||||
- JEKYLL_ENV=development
|
||||
- JEKYLL_NO_CACHE=true
|
||||
- DISABLE_DISK_CACHE=true
|
||||
45
docs/index.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
layout: home
|
||||
title: Home
|
||||
nav_order: 1
|
||||
description: "AliasVault Documentation - Open-source password and identity manager"
|
||||
permalink: /
|
||||
---
|
||||
|
||||
# AliasVault Documentation
|
||||
{: .fs-9 }
|
||||
|
||||
Open-source password and identity manager with email alias generation and zero-knowledge architecture.
|
||||
{: .fs-6 .fw-300 }
|
||||
|
||||
[Installation](./installation/install){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
|
||||
[View on GitHub](https://github.com/lanedirt/AliasVault){: .btn .fs-5 .mb-4 .mb-md-0 }
|
||||
|
||||
---
|
||||
|
||||
## What is AliasVault?
|
||||
|
||||
AliasVault is a self-hosted password and identity manager that helps you:
|
||||
|
||||
- 🔐 **Secure Passwords** - Store and manage passwords with zero-knowledge encryption
|
||||
- 📧 **Email Aliases** - Generate unique email addresses for each service
|
||||
- 🎭 **Identity Management** - Create and manage separate online identities
|
||||
- 🏠 **Self-Hosted** - Run on your own infrastructure using Docker
|
||||
- 🔓 **Open Source** - Transparent, auditable, and free to use
|
||||
|
||||
## Key Features
|
||||
|
||||
### Zero-Knowledge Architecture
|
||||
All data is end-to-end encrypted on the client. Your master password never leaves your device, and the server never has access to your data.
|
||||
|
||||
### Built-in Email Server
|
||||
Generate virtual email addresses for each identity. Emails sent to these addresses are instantly visible in the AliasVault app.
|
||||
|
||||
### Virtual Identities
|
||||
Create separate identities for different purposes, each with its own email aliases and credentials.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
Ready to get started with AliasVault? Check out the [installation guide](./installation).
|
||||
33
docs/installation/advanced/build-from-source.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
layout: default
|
||||
title: Build from Source
|
||||
parent: Advanced
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Build from Source
|
||||
Instead of using the pre-built Docker images, you can also build the images from source yourself. This allows you to build a specific version of AliasVault and/or to make changes to the source code.
|
||||
|
||||
Building from source requires more resources:
|
||||
- Minimum 2GB RAM (more RAM will speed up build time)
|
||||
- At least 1 vCPU
|
||||
- 40GB+ disk space (for dependencies and build artifacts)
|
||||
- Docker installed
|
||||
- Git installed
|
||||
|
||||
## Steps
|
||||
1. Clone the repository
|
||||
```bash
|
||||
git clone https://github.com/lanedirt/AliasVault.git
|
||||
cd AliasVault
|
||||
```
|
||||
2. Make the build script executable and run it. This will create the .env file, build the Docker images locally from source, and start the AliasVault containers. Follow the on-screen prompts to configure AliasVault.
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
./install.sh build
|
||||
```
|
||||
> **Note:** The build process can take a while depending on your hardware (5-15 minutes).
|
||||
|
||||
3. After the script completes, you can access AliasVault at:
|
||||
- Client: `https://localhost`
|
||||
- Admin: `https://localhost/admin`
|
||||
9
docs/installation/advanced/index.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
layout: default
|
||||
title: Advanced
|
||||
parent: Installation Guide
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Advanced Installation
|
||||
The following guides provide more advanced installation options for AliasVault. These options are not required for the basic installation, but may be useful for advanced users.
|
||||
@@ -1,6 +1,13 @@
|
||||
# Manual Setup Instructions for AliasVault
|
||||
---
|
||||
layout: default
|
||||
title: Manual Setup
|
||||
parent: Advanced
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
This README provides step-by-step instructions for manually setting up AliasVault without using the `install.sh` script. Follow these steps if you prefer to execute all statements yourself.
|
||||
# Manual Setup
|
||||
|
||||
If you prefer to manually set up AliasVault, this README provides step-by-step instructions. Follow these steps if you prefer to execute all statements yourself.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
8
docs/installation/index.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: default
|
||||
title: Installation Guide
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Installation Guide
|
||||
The following guide will walk you through the steps to install AliasVault on your own server. Minimum experience with Docker and Linux is required.
|
||||
150
docs/installation/install.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
layout: default
|
||||
title: Basic Install
|
||||
parent: Installation Guide
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Basic Install
|
||||
The following guide will walk you through the steps to install AliasVault on your own server. Minimum experience with Docker and Linux is required.
|
||||
|
||||
{: .toc }
|
||||
* TOC
|
||||
{:toc}
|
||||
|
||||
---
|
||||
|
||||
## 1. Basic Installation
|
||||
To get AliasVault up and running quickly, run the install script to pull pre-built Docker images. The install script will also configure the .env file and start the AliasVault containers. You can get up and running in less than 5 minutes.
|
||||
|
||||
### Hardware requirements
|
||||
- Linux VM with root access (Ubuntu or RHEL based distros recommended)
|
||||
- 1 vCPU
|
||||
- 512MB RAM
|
||||
- 16GB disk space
|
||||
- Docker installed
|
||||
|
||||
### Installation steps
|
||||
1. Download the install script to a directory of your choice. All AliasVault files and directories will be created in this directory.
|
||||
```bash
|
||||
curl -o install.sh https://raw.githubusercontent.com/lanedirt/AliasVault/main/install.sh
|
||||
```
|
||||
2. Make the install script executable.
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
```
|
||||
3. Run the install script. This will create the .env file, pull the Docker images, and start the AliasVault containers. Follow the on-screen prompts to configure AliasVault.
|
||||
```bash
|
||||
./install.sh install
|
||||
```
|
||||
> **Note**: AliasVault binds to ports 80 and 443 by default. If you want to change the default AliasVault ports you can do so in the `docker-compose.yml` file for the `reverse-proxy` (nginx) container. Afterwards re-run the `./install.sh install` command to restart the containers with the new port settings.
|
||||
|
||||
3. After the script completes, you can access AliasVault at:
|
||||
- Client: `https://localhost`
|
||||
- Admin: `https://localhost/admin`
|
||||
|
||||
---
|
||||
|
||||
## 2. SSL configuration
|
||||
The default installation will create a self-signed SSL certificate and configure Nginx to use it.
|
||||
|
||||
You can however also use Let's Encrypt to generate valid SSL certificates and configure Nginx to use it. In order to make this work you will need the following:
|
||||
|
||||
- A public IPv4 address assigned to your server
|
||||
- Port 80 and 443 on your server must be open and accessible from the internet
|
||||
- A registered domain name with an A record pointing to your server's public IP address (e.g. mydomain.com)
|
||||
|
||||
### Steps
|
||||
|
||||
1. Run the install script with the `configure-ssl` option
|
||||
```bash
|
||||
./install.sh configure-ssl
|
||||
```
|
||||
2. Follow the prompts to configure Let's Encrypt.
|
||||
|
||||
### Reverting to self-signed SSL
|
||||
If at any point you would like to revert to the self-signed SSL certificate, run the install script again with the `configure-ssl` option
|
||||
and then in the prompt choose option 2.
|
||||
|
||||
---
|
||||
|
||||
## 3. Email Server Setup
|
||||
|
||||
AliasVault includes a built-in email server that can handle multiple custom domains for your aliases.
|
||||
|
||||
To set up the email server, you need the following:
|
||||
- Public IPv4 address
|
||||
- Open ports (25 and 587) in server firewall for SMTP traffic
|
||||
- Access to DNS record management for your domain
|
||||
|
||||
### a) DNS Configuration
|
||||
Configure the following DNS records for your domain:
|
||||
|
||||
| Name | Type | Priority | Content | TTL |
|
||||
|------|------|----------|---------------------------|-----|
|
||||
| mail | A | | `<your-server-public-ip>` | 3600 |
|
||||
| @ | MX | 10 | `mail.<your-domain>` | 3600 |
|
||||
|
||||
> Note: Replace `<your-server-public-ip>` and `<your-domain>` with your actual values.
|
||||
|
||||
### b) Port Configuration
|
||||
The email server requires the following ports to be open:
|
||||
- Port 25: Standard SMTP (unencrypted)
|
||||
- Port 587: SMTP with STARTTLS (encrypted)
|
||||
|
||||
#### Verifying Port Access
|
||||
You can test if the SMTP ports are correctly configured using telnet:
|
||||
|
||||
```bash
|
||||
# Test standard SMTP port
|
||||
telnet <your-server-public-ip> 25
|
||||
|
||||
# Test secure SMTP port
|
||||
telnet <your-server-public-ip> 587
|
||||
```
|
||||
|
||||
If successful, you'll see a connection establishment message. Press Ctrl+C to exit the telnet session.
|
||||
|
||||
### c) Setting Up Email Domains
|
||||
|
||||
1. Run the email configuration script:
|
||||
```bash
|
||||
./install.sh configure-email
|
||||
````
|
||||
2. Follow the interactive prompts to:
|
||||
- Configure your domain(s)
|
||||
- Restart required services
|
||||
|
||||
3. Once configured, you can:
|
||||
- Create new aliases in the AliasVault client
|
||||
- Use your custom domain(s) for email addresses
|
||||
- Note: you can configure the default domain for new aliases in the AliasVault client in Menu > Settings > Email Settings > Default Email Domain
|
||||
- Start receiving emails on your aliases
|
||||
|
||||
{: .note }
|
||||
Important: DNS propagation can take up to 24-48 hours. During this time, email delivery might be inconsistent.
|
||||
|
||||
If you encounter any issues, feel free to open an issue on the [GitHub repository](https://github.com/lanedirt/AliasVault/issues).
|
||||
|
||||
---
|
||||
|
||||
## 4. Troubleshooting
|
||||
|
||||
### Resetting the admin password
|
||||
If you have lost your admin password, you can reset it by running the install script with the `reset-password` option. This will generate a new random password and update the .env file with it. After that it will restart the AliasVault containers to apply the changes.
|
||||
```bash
|
||||
./install.sh reset-password
|
||||
```
|
||||
|
||||
### Verbose output
|
||||
If you need more detailed output from the install script, you can run it with the `--verbose` option. This will print more information to the console.
|
||||
```bash
|
||||
./install.sh install --verbose
|
||||
```
|
||||
|
||||
### No emails being received
|
||||
If you are not receiving emails on your aliases, check the following:
|
||||
- Verify DNS records are correctly configured
|
||||
- Ensure ports 25 and 587 are accessible
|
||||
- Check your server's firewall settings
|
||||
- Verify that your ISP/hosting provider allows SMTP traffic
|
||||
30
docs/installation/start-stop.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
layout: default
|
||||
title: Start/stop
|
||||
parent: Installation Guide
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Starting and stopping AliasVault
|
||||
You can start and stop AliasVault easily by using the install script.
|
||||
|
||||
## Stop
|
||||
To stop AliasVault, run the install script with the `stop` option. This will stop all running AliasVault containers.
|
||||
|
||||
```bash
|
||||
./install.sh stop
|
||||
```
|
||||
|
||||
## Start
|
||||
To start AliasVault, run the install script with the `start` option. This will start all AliasVault containers.
|
||||
|
||||
```bash
|
||||
./install.sh start
|
||||
```
|
||||
|
||||
## Restart
|
||||
To restart AliasVault, run the install script with the `restart` option. This will restart all AliasVault containers.
|
||||
|
||||
```bash
|
||||
./install.sh restart
|
||||
```
|
||||
19
docs/installation/uninstall.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
layout: default
|
||||
title: Uninstall
|
||||
parent: Installation Guide
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# Uninstall
|
||||
|
||||
To uninstall AliasVault, run the install script with the `uninstall` option. This will stop and remove the AliasVault containers, remove the Docker images, and delete the .env file.
|
||||
|
||||
{: .note }
|
||||
This will not delete any data stored in the database. If you wish to delete all data, you should manually delete the `database` directory and the other directories created by AliasVault.
|
||||
|
||||
### Steps
|
||||
1. Run the install script with the `uninstall` option
|
||||
```bash
|
||||
./install.sh uninstall
|
||||
```
|
||||
32
docs/installation/update.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
layout: default
|
||||
title: Update
|
||||
parent: Installation Guide
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# Updating AliasVault
|
||||
To update AliasVault to the latest version, run the install script with the `update` option. This will pull the latest version of AliasVault from GitHub and restart all containers.
|
||||
|
||||
You can see the latest available version of AliasVault on [GitHub](https://github.com/lanedirt/AliasVault/releases).
|
||||
|
||||
{: .warning }
|
||||
Before updating, it's recommended to backup your database and other important data. You can do this by making
|
||||
a copy of the `database` and `certificates` directories.
|
||||
|
||||
## Updating to the latest available version
|
||||
To update to the latest version, run the install script with the `update` option. The script will check for the latest version and prompt you to confirm the update. Follow the prompts to complete the update.
|
||||
|
||||
```bash
|
||||
./install.sh update
|
||||
```
|
||||
|
||||
## Installing a specific version
|
||||
To install a specific version and skip the automatic version checks, run the install script with the `install` option and specify the version you want to install.
|
||||
|
||||
```bash
|
||||
./install.sh install <version>
|
||||
|
||||
# Example:
|
||||
./install.sh install 0.7.0
|
||||
```
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
layout: default
|
||||
title: Configure SQLite for use with WebAssembly
|
||||
parent: Development
|
||||
grand_parent: Miscellaneous
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Configure SQLite for use with WebAssembly
|
||||
To configure SQLite for use with WebAssembly follow these steps:
|
||||
|
||||
1. Add NuGet package
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
layout: default
|
||||
title: Enable WebAuthn
|
||||
parent: Development
|
||||
grand_parent: Miscellaneous
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# WebAuthn
|
||||
The webauthn implementation in order to quick unlock the vault requires the use of a FIDO2 authenticator.
|
||||
|
||||
This can be either the built-in browser authenticator or an external authenticator like a Yubikey.
|
||||
6
docs/misc/dev/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
layout: default
|
||||
title: Development
|
||||
parent: Miscellaneous
|
||||
nav_order: 1
|
||||
---
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
layout: default
|
||||
title: 1. Run GitHub Actions Locally
|
||||
parent: Development
|
||||
grand_parent: Miscellaneous
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Run GitHub Actions Locally
|
||||
|
||||
This guide will help you set up and run GitHub Actions locally on Linux, which can be useful for debugging and testing your workflows without pushing changes to the repository.
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
layout: default
|
||||
title: Upgrade the AliasClientDb EF model
|
||||
parent: Development
|
||||
grand_parent: Miscellaneous
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# Upgrade the AliasClientDb EF model
|
||||
|
||||
To upgrade the AliasClientDb EF model, follow these steps:
|
||||
|
||||
1. Make changes to the AliasClientDb EF model in the `AliasClientDb` project.
|
||||
10
docs/misc/index.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
layout: default
|
||||
title: Miscellaneous
|
||||
has_children: true
|
||||
nav_order: 99
|
||||
---
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
Miscellaneous guides and documentation.
|
||||
671
install.sh
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
# @version 0.8.2
|
||||
|
||||
# Repository information used for downloading files and images from GitHub
|
||||
REPO_OWNER="lanedirt"
|
||||
@@ -22,7 +23,6 @@ REQUIRED_DIRS=(
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
MAGENTA='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
@@ -37,11 +37,16 @@ show_usage() {
|
||||
printf "Usage: $0 [COMMAND] [OPTIONS]\n"
|
||||
printf "\n"
|
||||
printf "Commands:\n"
|
||||
printf " install Install AliasVault by pulling pre-built images from GitHub Container Registry (default)\n"
|
||||
printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n"
|
||||
printf " reset-password Reset admin password\n"
|
||||
printf " install Install AliasVault by pulling pre-built images from GitHub Container Registry (recommended)\n"
|
||||
printf " uninstall Uninstall AliasVault\n"
|
||||
printf " update Update AliasVault to the latest version\n"
|
||||
printf " configure-ssl Configure SSL certificates (Let's Encrypt or self-signed)\n"
|
||||
printf " configure-email Configure email domains for receiving emails\n"
|
||||
printf " start Start AliasVault containers\n"
|
||||
printf " stop Stop AliasVault containers\n"
|
||||
printf " restart Restart AliasVault containers\n"
|
||||
printf " reset-password Reset admin password\n"
|
||||
printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n"
|
||||
|
||||
printf "\n"
|
||||
printf "Options:\n"
|
||||
@@ -52,38 +57,78 @@ show_usage() {
|
||||
|
||||
# Function to parse command line arguments
|
||||
parse_args() {
|
||||
COMMAND="" # Remove default command
|
||||
COMMAND=""
|
||||
VERBOSE=false
|
||||
FORCE_YES=false
|
||||
COMMAND_ARG=""
|
||||
|
||||
# Show usage if no arguments provided
|
||||
if [ $# -eq 0 ]; then
|
||||
show_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# First argument is always the command
|
||||
case $1 in
|
||||
install|i)
|
||||
COMMAND="install"
|
||||
shift
|
||||
# Check for version argument
|
||||
if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
|
||||
COMMAND_ARG="$1"
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
# Other commands remain unchanged
|
||||
build|b)
|
||||
COMMAND="build"
|
||||
shift
|
||||
;;
|
||||
uninstall|u)
|
||||
COMMAND="uninstall"
|
||||
shift
|
||||
;;
|
||||
reset-password|reset-admin-password|rp)
|
||||
COMMAND="reset-password"
|
||||
shift
|
||||
;;
|
||||
configure-ssl|ssl)
|
||||
COMMAND="configure-ssl"
|
||||
shift
|
||||
;;
|
||||
configure-email|email)
|
||||
COMMAND="configure-email"
|
||||
shift
|
||||
;;
|
||||
start|s)
|
||||
COMMAND="start"
|
||||
shift
|
||||
;;
|
||||
stop|st)
|
||||
COMMAND="stop"
|
||||
shift
|
||||
;;
|
||||
restart|r)
|
||||
COMMAND="restart"
|
||||
shift
|
||||
;;
|
||||
update|up)
|
||||
COMMAND="update"
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Parse remaining flags
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
install|i)
|
||||
COMMAND="install"
|
||||
shift
|
||||
;;
|
||||
build|b)
|
||||
COMMAND="build"
|
||||
shift
|
||||
;;
|
||||
uninstall|u)
|
||||
COMMAND="uninstall"
|
||||
shift
|
||||
;;
|
||||
reset-password|reset-admin-password|rp)
|
||||
COMMAND="reset-password"
|
||||
shift
|
||||
;;
|
||||
configure-ssl|ssl)
|
||||
COMMAND="configure-ssl"
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
@@ -92,10 +137,6 @@ parse_args() {
|
||||
FORCE_YES=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_usage
|
||||
@@ -118,7 +159,7 @@ main() {
|
||||
print_logo
|
||||
case $COMMAND in
|
||||
"install")
|
||||
handle_install
|
||||
handle_install "$COMMAND_ARG"
|
||||
;;
|
||||
"build")
|
||||
handle_build
|
||||
@@ -136,6 +177,21 @@ main() {
|
||||
"configure-ssl")
|
||||
handle_ssl_configuration
|
||||
;;
|
||||
"configure-email")
|
||||
handle_email_configuration
|
||||
;;
|
||||
"start")
|
||||
handle_start
|
||||
;;
|
||||
"stop")
|
||||
handle_stop
|
||||
;;
|
||||
"restart")
|
||||
handle_restart
|
||||
;;
|
||||
"update")
|
||||
handle_update
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -173,21 +229,35 @@ initialize_workspace() {
|
||||
|
||||
# Function to handle docker-compose.yml
|
||||
handle_docker_compose() {
|
||||
printf "${CYAN}> Checking docker-compose.yml...${NC}\n"
|
||||
printf "${CYAN}> Checking docker-compose files...${NC}\n"
|
||||
|
||||
if [ -f "docker-compose.yml" ]; then
|
||||
printf " ${GREEN}> docker-compose.yml already exists.${NC}\n"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf " ${CYAN}> Downloading docker-compose.yml...${NC}"
|
||||
if curl -sSf "${GITHUB_RAW_URL}/docker-compose.yml" -o "docker-compose.yml" > /dev/null 2>&1; then
|
||||
printf "\n ${GREEN}> docker-compose.yml downloaded successfully.${NC}\n"
|
||||
return 0
|
||||
# Check and download main docker-compose.yml
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
printf " ${CYAN}> Downloading docker-compose.yml...${NC}"
|
||||
if curl -sSf "${GITHUB_RAW_URL}/docker-compose.yml" -o "docker-compose.yml" > /dev/null 2>&1; then
|
||||
printf "\n ${GREEN}> docker-compose.yml downloaded successfully.${NC}\n"
|
||||
else
|
||||
printf "\n ${YELLOW}> Failed to download docker-compose.yml, please check your internet connection and try again. Alternatively, you can download it manually from https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/docker-compose.yml and place it in the root directory of AliasVault.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
printf "\n ${YELLOW}> Failed to download docker-compose.yml, please check your internet connection and try again. Alternatively, you can download it manually from https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/docker-compose.yml and place it in the root directory of AliasVault.${NC}\n"
|
||||
exit 1
|
||||
printf " ${GREEN}> docker-compose.yml already exists.${NC}\n"
|
||||
fi
|
||||
|
||||
# Check and download docker-compose.letsencrypt.yml
|
||||
if [ ! -f "docker-compose.letsencrypt.yml" ]; then
|
||||
printf " ${CYAN}> Downloading docker-compose.letsencrypt.yml...${NC}"
|
||||
if curl -sSf "${GITHUB_RAW_URL}/docker-compose.letsencrypt.yml" -o "docker-compose.letsencrypt.yml" > /dev/null 2>&1; then
|
||||
printf "\n ${GREEN}> docker-compose.letsencrypt.yml downloaded successfully.${NC}\n"
|
||||
else
|
||||
printf "\n ${YELLOW}> Failed to download docker-compose.letsencrypt.yml, please check your internet connection and try again. Alternatively, you can download it manually from https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/docker-compose.letsencrypt.yml and place it in the root directory of AliasVault.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
printf " ${GREEN}> docker-compose.letsencrypt.yml already exists.${NC}\n"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to print the logo
|
||||
@@ -196,8 +266,8 @@ print_logo() {
|
||||
printf " _ _ _ __ __ _ _ \n"
|
||||
printf " / \ | (_) __ _ ___ \ \ / /_ _ _ _| | |_\n"
|
||||
printf " / _ \ | | |/ _\` / __| \ \/\/ / _\` | | | | | __|\n"
|
||||
printf " / ___ \| | | (_| \__ \ \ / (_| | |_| | | |_ \n"
|
||||
printf "/_/ \_\_|_|\__,_|___/ \/ \__,_|\__,_|_|\__|\n"
|
||||
printf " / ___ \| | | (_| \__ \ \ / / (_| | |_| | | |_ \n"
|
||||
printf "/_/ \_\_|_|\__,_|___/ \/ \__,__|\__,_|_|\__|\n"
|
||||
printf "${NC}\n"
|
||||
}
|
||||
|
||||
@@ -254,22 +324,14 @@ populate_data_protection_cert_pass() {
|
||||
set_private_email_domains() {
|
||||
printf "${CYAN}> Checking PRIVATE_EMAIL_DOMAINS...${NC}\n"
|
||||
if ! grep -q "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" || [ -z "$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||
printf "Please enter the domains that should be allowed to receive email, separated by commas (press Enter to disable email support): "
|
||||
read -r private_email_domains
|
||||
update_env_var "PRIVATE_EMAIL_DOMAINS" "DISABLED.TLD"
|
||||
fi
|
||||
|
||||
private_email_domains=${private_email_domains:-"DISABLED.TLD"}
|
||||
update_env_var "PRIVATE_EMAIL_DOMAINS" "$private_email_domains"
|
||||
|
||||
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
|
||||
printf " ${RED}SMTP is disabled.${NC}\n"
|
||||
fi
|
||||
private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
|
||||
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
|
||||
printf " ${RED}Email server is disabled.${NC} To enable use ./install.sh configure-email command.\n"
|
||||
else
|
||||
private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
|
||||
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
|
||||
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists.${NC} ${RED}Private email domains are disabled.${NC}\n"
|
||||
else
|
||||
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists.${NC}\n"
|
||||
fi
|
||||
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists. Email server is enabled.${NC}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -351,6 +413,16 @@ update_env_var() {
|
||||
printf " ${GREEN}> $key has been set in $ENV_FILE.${NC}\n"
|
||||
}
|
||||
|
||||
# Helper function to delete environment variables
|
||||
delete_env_var() {
|
||||
local key=$1
|
||||
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
sed -i.bak "/^${key}=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
|
||||
printf " ${GREEN}> $key has been removed from $ENV_FILE.${NC}\n"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to print success message
|
||||
print_success_message() {
|
||||
printf "\n"
|
||||
@@ -420,60 +492,38 @@ get_docker_compose_command() {
|
||||
echo "$base_command"
|
||||
}
|
||||
|
||||
# Function to handle installation
|
||||
# Function to handle initial installation or reinstallation
|
||||
handle_install() {
|
||||
printf "${YELLOW}+++ Installing AliasVault +++${NC}\n"
|
||||
printf "\n"
|
||||
local specified_version="$1"
|
||||
|
||||
# Initialize workspace which makes sure all required directories and files exist
|
||||
initialize_workspace
|
||||
|
||||
# Initialize environment
|
||||
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
|
||||
populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }
|
||||
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
|
||||
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
|
||||
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
|
||||
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
|
||||
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
|
||||
|
||||
# Only generate admin password if not already set
|
||||
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
|
||||
# If version specified, install that version directly
|
||||
if [ -n "$specified_version" ]; then
|
||||
handle_install_version "$specified_version"
|
||||
return
|
||||
fi
|
||||
|
||||
# Pull images from GitHub Container Registry
|
||||
printf "\n${YELLOW}+++ Pulling Docker images +++${NC}\n"
|
||||
printf "\n"
|
||||
# Check if .env exists before reading
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
if grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
|
||||
current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
|
||||
printf "${CYAN}> Current AliasVault version: ${current_version}${NC}\n"
|
||||
printf "${YELLOW}> AliasVault is already installed.${NC}\n"
|
||||
printf "1. To reinstall the current version (${current_version}), continue with this script\n"
|
||||
printf "2. To check for updates and to install the latest version, use: ./install.sh update\n"
|
||||
printf "3. To install a specific version, use: ./install.sh install <version>\n\n"
|
||||
|
||||
images=(
|
||||
"${GITHUB_CONTAINER_REGISTRY}-reverse-proxy:latest"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-api:latest"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-client:latest"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-admin:latest"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-smtp:latest"
|
||||
)
|
||||
read -p "Would you like to reinstall the current version? [y/N]: " REPLY
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
printf "${YELLOW}> Installation cancelled.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
printf "${CYAN}> Pulling $image...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker pull $image || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
|
||||
else
|
||||
docker pull $image > /dev/null 2>&1 || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
|
||||
handle_install_version "$current_version"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
# Start containers
|
||||
printf "\n${YELLOW}+++ Starting services +++${NC}\n"
|
||||
printf "\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
$(get_docker_compose_command) up -d || { printf "${RED}> Failed to start Docker containers${NC}\n"; exit 1; }
|
||||
else
|
||||
$(get_docker_compose_command) up -d > /dev/null 2>&1 || { printf "${RED}> Failed to start Docker containers${NC}\n"; exit 1; }
|
||||
fi
|
||||
|
||||
# Only show success message if we made it here without errors
|
||||
print_success_message
|
||||
handle_install_version "latest"
|
||||
}
|
||||
|
||||
# Function to handle build
|
||||
@@ -539,12 +589,12 @@ handle_build() {
|
||||
|
||||
printf "${CYAN}> Starting Docker Compose stack...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
$(get_docker_compose_command "build") up -d || {
|
||||
$(get_docker_compose_command "build") up -d --force-recreate || {
|
||||
printf "${RED}> Failed to start Docker Compose stack${NC}\n"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
$(get_docker_compose_command "build") up -d > /dev/null 2>&1 || {
|
||||
$(get_docker_compose_command "build") up -d --force-recreate > /dev/null 2>&1 || {
|
||||
printf "${RED}> Failed to start Docker Compose stack${NC}\n"
|
||||
exit 1
|
||||
}
|
||||
@@ -584,6 +634,9 @@ handle_uninstall() {
|
||||
fi
|
||||
printf "${GREEN}> Docker containers stopped and removed.${NC}\n"
|
||||
|
||||
# Remove version from .env
|
||||
delete_env_var "ALIASVAULT_VERSION" ""
|
||||
|
||||
printf "${CYAN}> Removing Docker images...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker compose -f docker-compose.yml down --rmi all || {
|
||||
@@ -690,6 +743,147 @@ handle_ssl_configuration() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to handle email server configuration
|
||||
# Function to handle email server configuration
|
||||
handle_email_configuration() {
|
||||
# Setup trap for Ctrl+C and other interrupts
|
||||
trap 'printf "\n${YELLOW}Configuration cancelled by user.${NC}\n"; exit 1' INT TERM
|
||||
|
||||
printf "${YELLOW}+++ Email Server Configuration +++${NC}\n"
|
||||
printf "\n"
|
||||
|
||||
# Check if AliasVault is installed
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current email domains from .env
|
||||
CURRENT_DOMAINS=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
|
||||
|
||||
printf "${CYAN}About Email Server:${NC}\n"
|
||||
printf "AliasVault includes a built-in email server for handling virtual email addresses.\n"
|
||||
printf "When enabled, it can receive emails for one or more configured domains.\n"
|
||||
printf "Each domain must have an MX record in DNS configuration pointing to this server's hostname.\n"
|
||||
printf "\n"
|
||||
printf "${CYAN}Current Configuration:${NC}\n"
|
||||
|
||||
if [ "$CURRENT_DOMAINS" = "DISABLED.TLD" ]; then
|
||||
printf "Email Server Status: ${RED}Disabled${NC}\n"
|
||||
else
|
||||
printf "Email Server Status: ${GREEN}Enabled${NC}\n"
|
||||
printf "Active Domains: ${CYAN}${CURRENT_DOMAINS}${NC}\n"
|
||||
fi
|
||||
|
||||
printf "\n"
|
||||
printf "Email Server Options:\n"
|
||||
printf "1) Enable email server / Update domains\n"
|
||||
printf "2) Disable email server\n"
|
||||
printf "3) Cancel\n"
|
||||
printf "\n"
|
||||
|
||||
read -p "Select an option [1-3]: " email_option
|
||||
|
||||
case $email_option in
|
||||
1)
|
||||
while true; do
|
||||
printf "\n${CYAN}Enter domain(s) for email server${NC}\n"
|
||||
printf "For multiple domains, separate with commas (e.g. domain1.com,domain2.com)\n"
|
||||
printf "IMPORTANT: Each domain must have an MX record in DNS pointing to this server.\n"
|
||||
read -p "Domains: " new_domains
|
||||
|
||||
if [ -z "$new_domains" ]; then
|
||||
printf "${RED}Error: Domains cannot be empty${NC}\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
printf "\n${CYAN}You entered the following domains:${NC}\n"
|
||||
IFS=',' read -ra DOMAIN_ARRAY <<< "$new_domains"
|
||||
for domain in "${DOMAIN_ARRAY[@]}"; do
|
||||
printf " - ${GREEN}${domain}${NC}\n"
|
||||
done
|
||||
printf "\n"
|
||||
|
||||
read -p "Are these domains correct? (y/n): " confirm
|
||||
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
|
||||
read -p "Continue with restart? (y/n): " restart_confirm
|
||||
|
||||
if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
|
||||
printf "${YELLOW}Configuration cancelled.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update .env file and restart
|
||||
if ! update_env_var "PRIVATE_EMAIL_DOMAINS" "$new_domains"; then
|
||||
printf "${RED}Failed to update configuration.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "${GREEN}Email server configuration updated${NC}\n"
|
||||
printf "Restarting AliasVault services...\n"
|
||||
|
||||
if ! handle_restart; then
|
||||
printf "${RED}Failed to restart services.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only show next steps if everything succeeded
|
||||
printf "\n${CYAN}The email server is now succesfully configured.${NC}\n"
|
||||
printf "\n"
|
||||
printf "To test the email server:\n"
|
||||
printf " a. Log in to your AliasVault account\n"
|
||||
printf " b. Create a new alias using one of your configured private domains\n"
|
||||
printf " c. Send a test email from an external email service (e.g., Gmail)\n"
|
||||
printf " d. Check if the email appears in your AliasVault inbox\n"
|
||||
printf "\n"
|
||||
printf "If emails don't arrive, please verify:\n"
|
||||
printf " > DNS MX records are correctly configured\n"
|
||||
printf " > Your server's firewall allows incoming traffic on port 25 and 587\n"
|
||||
printf " > Your ISP/hosting provider doesn't block SMTP traffic\n"
|
||||
printf "\n"
|
||||
;;
|
||||
2)
|
||||
printf "${YELLOW}Warning: Docker containers need to be restarted after disabling the email server.${NC}\n"
|
||||
read -p "Continue with disable and restart? (y/n): " disable_confirm
|
||||
|
||||
if [ "$disable_confirm" != "y" ] && [ "$disable_confirm" != "Y" ]; then
|
||||
printf "${YELLOW}Configuration cancelled.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Disable email server
|
||||
if ! update_env_var "PRIVATE_EMAIL_DOMAINS" "DISABLED.TLD"; then
|
||||
printf "${RED}Failed to update configuration.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "${YELLOW}Email server disabled${NC}\n"
|
||||
printf "Restarting AliasVault services...\n"
|
||||
|
||||
if ! handle_restart; then
|
||||
printf "${RED}Failed to restart services.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
printf "${YELLOW}Email configuration cancelled.${NC}\n"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf "${RED}Invalid option selected.${NC}\n"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove the trap before normal exit
|
||||
trap - INT TERM
|
||||
}
|
||||
|
||||
# Function to configure Let's Encrypt
|
||||
configure_letsencrypt() {
|
||||
printf "${CYAN}> Configuring Let's Encrypt SSL certificate...${NC}\n"
|
||||
@@ -746,7 +940,6 @@ configure_letsencrypt() {
|
||||
# Request certificate using a temporary certbot container
|
||||
printf "${CYAN}> Requesting Let's Encrypt certificate...${NC}\n"
|
||||
docker run --rm \
|
||||
--network aliasvault_default \
|
||||
-v ./certificates/letsencrypt:/etc/letsencrypt:rw \
|
||||
-v ./certificates/letsencrypt/www:/var/www/certbot:rw \
|
||||
certbot/certbot certonly \
|
||||
@@ -776,7 +969,11 @@ configure_letsencrypt() {
|
||||
|
||||
# Restart only the reverse proxy with new configuration so it loads the new certificate
|
||||
printf "${CYAN}> Restarting reverse proxy with Let's Encrypt configuration...${NC}\n"
|
||||
$(get_docker_compose_command) up -d reverse-proxy
|
||||
$(get_docker_compose_command) up -d reverse-proxy --force-recreate
|
||||
|
||||
# Starting certbot container to renew certificates automatically
|
||||
printf "${CYAN}> Starting new certbot container to renew certificates automatically...${NC}\n"
|
||||
$(get_docker_compose_command) up -d certbot
|
||||
|
||||
printf "${GREEN}> Let's Encrypt SSL certificate has been configured successfully!${NC}\n"
|
||||
}
|
||||
@@ -805,4 +1002,262 @@ generate_self_signed_cert() {
|
||||
printf "${GREEN}> New self-signed certificate has been generated successfully!${NC}\n"
|
||||
}
|
||||
|
||||
# New functions to handle container lifecycle:
|
||||
handle_start() {
|
||||
printf "${CYAN}> Starting AliasVault containers...${NC}\n"
|
||||
$(get_docker_compose_command) up -d
|
||||
printf "${GREEN}> AliasVault containers started successfully.${NC}\n"
|
||||
}
|
||||
|
||||
handle_stop() {
|
||||
printf "${CYAN}> Stopping AliasVault containers...${NC}\n"
|
||||
if ! docker compose ps --quiet 2>/dev/null | grep -q .; then
|
||||
printf "${YELLOW}> No containers are currently running.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
$(get_docker_compose_command) down
|
||||
printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n"
|
||||
}
|
||||
|
||||
handle_restart() {
|
||||
printf "${CYAN}> Restarting AliasVault containers...${NC}\n"
|
||||
$(get_docker_compose_command) down
|
||||
$(get_docker_compose_command) up -d
|
||||
printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n"
|
||||
}
|
||||
|
||||
# Function to handle updates
|
||||
handle_update() {
|
||||
printf "${YELLOW}+++ Checking for AliasVault updates +++${NC}\n"
|
||||
printf "\n"
|
||||
|
||||
# First check for install.sh updates
|
||||
check_install_script_update || true
|
||||
|
||||
# Check current version
|
||||
if ! grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
|
||||
printf "${YELLOW}> No version information found. Running first-time update check...${NC}\n"
|
||||
handle_install_version "latest"
|
||||
return
|
||||
fi
|
||||
|
||||
current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
|
||||
latest_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
|
||||
if [ -z "$latest_version" ]; then
|
||||
printf "${RED}> Failed to check for updates. Please try again later.${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "${CYAN}> Current AliasVault version: ${current_version}${NC}\n"
|
||||
printf "${CYAN}> Latest AliasVault version: ${latest_version}${NC}\n"
|
||||
printf "\n"
|
||||
|
||||
if [ "$current_version" = "$latest_version" ]; then
|
||||
printf "${GREEN}> You are already running the latest version of AliasVault!${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "${YELLOW}> A new version of AliasVault is available!${NC}\n"
|
||||
printf "\n"
|
||||
printf "${MAGENTA}Important:${NC}\n"
|
||||
printf "1. It's recommended to backup your database before updating\n"
|
||||
printf "2. The update process will restart all containers\n"
|
||||
printf "\n"
|
||||
|
||||
read -p "Would you like to update to the latest version? [y/N]: " REPLY
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
printf "${YELLOW}> Update cancelled.${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf "${CYAN}> Updating AliasVault...${NC}\n"
|
||||
handle_install_version "$latest_version"
|
||||
|
||||
printf "${GREEN}> Update completed successfully!${NC}\n"
|
||||
}
|
||||
|
||||
# Function to extract version
|
||||
extract_version() {
|
||||
local file="$1"
|
||||
local version=$(head -n 2 "$file" | grep '@version' | cut -d' ' -f3)
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
# Function to compare semantic versions
|
||||
compare_versions() {
|
||||
local version1="$1"
|
||||
local version2="$2"
|
||||
|
||||
# Split versions into arrays
|
||||
IFS='.' read -ra v1_parts <<< "$version1"
|
||||
IFS='.' read -ra v2_parts <<< "$version2"
|
||||
|
||||
# Compare each part numerically
|
||||
for i in {0..2}; do
|
||||
# Default to 0 if part doesn't exist
|
||||
local v1_part=${v1_parts[$i]:-0}
|
||||
local v2_part=${v2_parts[$i]:-0}
|
||||
|
||||
# Compare numerically
|
||||
if [ "$v1_part" -gt "$v2_part" ]; then
|
||||
echo "1" # version1 is greater
|
||||
return
|
||||
elif [ "$v1_part" -lt "$v2_part" ]; then
|
||||
echo "-1" # version1 is lesser
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
echo "0" # versions are equal
|
||||
}
|
||||
|
||||
# Function to check if install.sh needs updating
|
||||
check_install_script_update() {
|
||||
printf "${CYAN}> Checking for install script updates...${NC}\n"
|
||||
|
||||
# Download latest install.sh to temporary file
|
||||
if ! curl -sSf "${GITHUB_RAW_URL}/install.sh" -o "install.sh.tmp"; then
|
||||
printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
|
||||
rm -f install.sh.tmp
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get versions
|
||||
local current_version=$(extract_version "install.sh")
|
||||
local new_version=$(extract_version "install.sh.tmp")
|
||||
|
||||
# Check if versions could be extracted
|
||||
if [ -z "$current_version" ] || [ -z "$new_version" ]; then
|
||||
printf "${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n"
|
||||
# Fall back to file comparison
|
||||
if ! cmp -s "install.sh" "install.sh.tmp"; then
|
||||
printf "${YELLOW}> Changes detected in install script.${NC}\n"
|
||||
else
|
||||
printf "${GREEN}> Install script is up to date.${NC}\n"
|
||||
rm -f install.sh.tmp
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
printf "${CYAN}> Current install script version: ${current_version}${NC}\n"
|
||||
printf "${CYAN}> Latest install script version: ${new_version}${NC}\n"
|
||||
|
||||
# Compare versions using semver comparison
|
||||
if [ "$current_version" = "$new_version" ]; then
|
||||
printf "${GREEN}> Install script is up to date.${NC}\n"
|
||||
rm -f install.sh.tmp
|
||||
return 0
|
||||
else
|
||||
local compare_result=$(compare_versions "$current_version" "$new_version")
|
||||
|
||||
if [ "$compare_result" -ge "0" ]; then
|
||||
printf "${GREEN}> Install script is up to date.${NC}\n"
|
||||
rm -f install.sh.tmp
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we get here, an update is available
|
||||
printf "${YELLOW}> A new version of the install script is available.${NC}\n"
|
||||
printf "Would you like to update the install script before proceeding? [Y/n]: "
|
||||
read -r reply
|
||||
|
||||
if [[ ! $reply =~ ^[Nn]$ ]]; then
|
||||
# Create backup of current script
|
||||
cp "install.sh" "install.sh.backup"
|
||||
|
||||
if mv "install.sh.tmp" "install.sh"; then
|
||||
chmod +x "install.sh"
|
||||
printf "${GREEN}> Install script updated successfully.${NC}\n"
|
||||
printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
|
||||
printf "${YELLOW}> Please run the update command again to continue with the update process.${NC}\n"
|
||||
exit 0
|
||||
else
|
||||
printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n"
|
||||
# Restore from backup if update failed
|
||||
mv "install.sh.backup" "install.sh"
|
||||
rm -f install.sh.tmp
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
printf "${YELLOW}> Continuing with current install script version.${NC}\n"
|
||||
rm -f install.sh.tmp
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to perform the actual installation with specific version
|
||||
handle_install_version() {
|
||||
local target_version="$1"
|
||||
|
||||
# If latest, get actual version number from GitHub API
|
||||
if [ "$target_version" = "latest" ]; then
|
||||
local actual_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
if [ -n "$actual_version" ]; then
|
||||
target_version="$actual_version"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "${YELLOW}+++ Installing AliasVault ${target_version} +++${NC}\n"
|
||||
printf "\n"
|
||||
|
||||
# Initialize workspace which makes sure all required directories and files exist
|
||||
initialize_workspace
|
||||
|
||||
# Initialize environment
|
||||
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
|
||||
populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }
|
||||
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
|
||||
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
|
||||
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
|
||||
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
|
||||
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
|
||||
|
||||
# Only generate admin password if not already set
|
||||
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
|
||||
fi
|
||||
|
||||
# Pull images from GitHub Container Registry
|
||||
printf "\n${YELLOW}+++ Pulling Docker images +++${NC}\n"
|
||||
printf "\n"
|
||||
|
||||
printf "${CYAN}> Installing version: ${target_version}${NC}\n"
|
||||
|
||||
local tag="$target_version"
|
||||
if [ "$target_version" = "latest" ]; then
|
||||
tag="latest"
|
||||
fi
|
||||
|
||||
images=(
|
||||
"${GITHUB_CONTAINER_REGISTRY}-reverse-proxy:${tag}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-api:${tag}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-client:${tag}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-admin:${tag}"
|
||||
"${GITHUB_CONTAINER_REGISTRY}-smtp:${tag}"
|
||||
)
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
printf "${CYAN}> Pulling $image...${NC}\n"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
docker pull $image || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
|
||||
else
|
||||
docker pull $image > /dev/null 2>&1 || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
|
||||
fi
|
||||
done
|
||||
|
||||
# Save version to .env
|
||||
update_env_var "ALIASVAULT_VERSION" "$target_version"
|
||||
|
||||
# Start containers
|
||||
printf "\n${YELLOW}+++ Starting services +++${NC}\n"
|
||||
printf "\n"
|
||||
recreate_docker_containers
|
||||
|
||||
# Only show success message if we made it here without errors
|
||||
print_success_message
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -4,22 +4,17 @@ EXPOSE 3002
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
|
||||
WORKDIR /src
|
||||
COPY ["src/AliasVault.Admin/AliasVault.Admin.csproj", "src/AliasVault.Admin/"]
|
||||
RUN dotnet restore "src/AliasVault.Admin/AliasVault.Admin.csproj"
|
||||
COPY . .
|
||||
|
||||
WORKDIR "/src/src/AliasVault.Admin"
|
||||
RUN dotnet build "AliasVault.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "AliasVault.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:3002
|
||||
ENV ASPNETCORE_PATHBASE=/admin
|
||||
|
||||
@@ -37,6 +37,8 @@ config.LastPasswordChanged = DateTime.Parse(lastPasswordChanged, CultureInfo.Inv
|
||||
|
||||
builder.Services.AddSingleton(config);
|
||||
|
||||
builder.Services.AddAliasVaultDataProtection("AliasVault.Api");
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
@@ -85,7 +87,6 @@ builder.Services.AddIdentityCore<AdminUser>(options =>
|
||||
.AddSignInManager()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.AddAliasVaultDataProtection("AliasVault.Admin");
|
||||
builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
|
||||
{
|
||||
options.TokenLifespan = TimeSpan.FromDays(30);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -137,8 +137,6 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
return Ok(new ValidateLoginResponse(true, string.Empty, null));
|
||||
}
|
||||
|
||||
// If 2FA is not required, then it means the user is successfully authenticated at this point.
|
||||
|
||||
// Reset failed login attempts.
|
||||
await userManager.ResetAccessFailedCountAsync(user);
|
||||
|
||||
@@ -246,6 +244,12 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
{
|
||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// If the token is not provided, return bad request.
|
||||
if (string.IsNullOrWhiteSpace(tokenModel.RefreshToken))
|
||||
{
|
||||
return BadRequest("Refresh token is required.");
|
||||
}
|
||||
|
||||
var principal = GetPrincipalFromToken(tokenModel.Token);
|
||||
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value == null)
|
||||
{
|
||||
@@ -258,16 +262,14 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
return Unauthorized("User not found (name-2)");
|
||||
}
|
||||
|
||||
// Check if the refresh token is valid.
|
||||
var existingToken = await context.AliasVaultUserRefreshTokens.FirstOrDefaultAsync(t => t.UserId == user.Id && t.Value == tokenModel.RefreshToken);
|
||||
if (existingToken == null || existingToken.ExpireDate < timeProvider.UtcNow)
|
||||
// Generate new tokens for the user.
|
||||
var token = await GenerateNewTokensForUser(user, tokenModel.RefreshToken);
|
||||
if (token == null)
|
||||
{
|
||||
await authLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TokenRefresh, AuthFailureReason.InvalidRefreshToken);
|
||||
return Unauthorized("Refresh token expired");
|
||||
return Unauthorized("Invalid refresh token");
|
||||
}
|
||||
|
||||
// Generate new tokens for the user.
|
||||
var token = await GenerateNewTokensForUser(user, existingToken);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.TokenRefresh);
|
||||
@@ -284,6 +286,12 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
{
|
||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// If the token is not provided, return bad request.
|
||||
if (string.IsNullOrWhiteSpace(model.RefreshToken))
|
||||
{
|
||||
return BadRequest("Refresh token is required.");
|
||||
}
|
||||
|
||||
var principal = GetPrincipalFromToken(model.Token);
|
||||
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value == null)
|
||||
{
|
||||
@@ -297,16 +305,18 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
}
|
||||
|
||||
// Check if the refresh token is valid.
|
||||
var deviceIdentifier = GenerateDeviceIdentifier(Request);
|
||||
var existingToken = await context.AliasVaultUserRefreshTokens.FirstOrDefaultAsync(t => t.UserId == user.Id && t.DeviceIdentifier == deviceIdentifier);
|
||||
if (existingToken == null || existingToken.Value != model.RefreshToken)
|
||||
var providedTokenExists = await context.AliasVaultUserRefreshTokens.AnyAsync(t => t.UserId == user.Id && t.Value == model.RefreshToken);
|
||||
if (!providedTokenExists)
|
||||
{
|
||||
await authLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.Logout, AuthFailureReason.InvalidRefreshToken);
|
||||
return Unauthorized("Invalid refresh token");
|
||||
}
|
||||
|
||||
// Remove the existing refresh token.
|
||||
context.AliasVaultUserRefreshTokens.Remove(existingToken);
|
||||
// Remove the provided refresh token and any other existing refresh tokens that are issued to the current device ID.
|
||||
// This to make sure all tokens are revoked for this device that user is "logging out" from.
|
||||
var deviceIdentifier = GenerateDeviceIdentifier(Request);
|
||||
var allDeviceTokens = await context.AliasVaultUserRefreshTokens.Where(t => t.UserId == user.Id && (t.Value == model.RefreshToken || t.DeviceIdentifier == deviceIdentifier)).ToListAsync();
|
||||
context.AliasVaultUserRefreshTokens.RemoveRange(allDeviceTokens);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.Logout);
|
||||
@@ -333,7 +343,7 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
UserName = model.Username,
|
||||
CreatedAt = timeProvider.UtcNow,
|
||||
UpdatedAt = timeProvider.UtcNow,
|
||||
PasswordChangedAt = DateTime.UtcNow,
|
||||
PasswordChangedAt = timeProvider.UtcNow,
|
||||
};
|
||||
|
||||
user.Vaults.Add(new AliasServerDb.Vault
|
||||
@@ -447,6 +457,7 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
private static (bool IsValid, string ErrorMessage) ValidateUsername(string username)
|
||||
{
|
||||
const int minimumUsernameLength = 3;
|
||||
const int maximumUsernameLength = 40;
|
||||
const string adminUsername = "admin";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
@@ -456,7 +467,12 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
|
||||
if (username.Length < minimumUsernameLength)
|
||||
{
|
||||
return (false, $"Username must be at least {minimumUsernameLength} characters long.");
|
||||
return (false, $"Username too short: must be at least {minimumUsernameLength} characters long.");
|
||||
}
|
||||
|
||||
if (username.Length > maximumUsernameLength)
|
||||
{
|
||||
return (false, $"Username too long: cannot be longer than {maximumUsernameLength} characters.");
|
||||
}
|
||||
|
||||
if (string.Equals(username, adminUsername, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -666,9 +682,9 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
/// to the database.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to generate the tokens for.</param>
|
||||
/// <param name="existingToken">The existing token that is being replaced (optional).</param>
|
||||
/// <returns>TokenModel which includes new access and refresh token.</returns>
|
||||
private async Task<TokenModel> GenerateNewTokensForUser(AliasVaultUser user, AliasVaultUserRefreshToken existingToken)
|
||||
/// <param name="existingTokenValue">The existing token value that is being replaced (optional).</param>
|
||||
/// <returns>TokenModel which includes new access and refresh token. Returns null if provided refresh token is invalid.</returns>
|
||||
private async Task<TokenModel?> GenerateNewTokensForUser(AliasVaultUser user, string existingTokenValue)
|
||||
{
|
||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||
await Semaphore.WaitAsync();
|
||||
@@ -681,7 +697,7 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
var existingTokenReuseWindow = timeProvider.UtcNow.AddSeconds(-30);
|
||||
var existingTokenReuse = await context.AliasVaultUserRefreshTokens
|
||||
.FirstOrDefaultAsync(t => t.UserId == user.Id &&
|
||||
t.PreviousTokenValue == existingToken.Value &&
|
||||
t.PreviousTokenValue == existingTokenValue &&
|
||||
t.CreatedAt > existingTokenReuseWindow);
|
||||
|
||||
if (existingTokenReuse is not null)
|
||||
@@ -692,21 +708,26 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
|
||||
return new TokenModel { Token = accessToken, RefreshToken = existingTokenReuse.Value };
|
||||
}
|
||||
|
||||
// Remove the existing refresh token.
|
||||
var tokenToDelete = await context.AliasVaultUserRefreshTokens.FirstOrDefaultAsync(t => t.Id == existingToken.Id);
|
||||
if (tokenToDelete is null)
|
||||
// Check if the refresh token still exists and is not expired.
|
||||
var existingToken = await context.AliasVaultUserRefreshTokens.FirstOrDefaultAsync(t => t.UserId == user.Id && t.Value == existingTokenValue);
|
||||
if (existingToken == null || existingToken.ExpireDate < timeProvider.UtcNow)
|
||||
{
|
||||
await authLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TokenRefresh, AuthFailureReason.InvalidRefreshToken);
|
||||
throw new InvalidOperationException("Refresh token does not exist (anymore).");
|
||||
return null;
|
||||
}
|
||||
|
||||
context.AliasVaultUserRefreshTokens.Remove(tokenToDelete);
|
||||
context.AliasVaultUserRefreshTokens.Remove(existingToken);
|
||||
|
||||
// New refresh token lifetime is the same as the existing one.
|
||||
var existingTokenLifetime = existingToken.ExpireDate - existingToken.CreatedAt;
|
||||
|
||||
// Retrieve new refresh token.
|
||||
var newRefreshToken = await GenerateRefreshToken(user, existingTokenLifetime, existingToken.Value);
|
||||
|
||||
// After successfully retrieving new refresh token, remove the existing one by saving changes.
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Return new refresh token.
|
||||
return await GenerateRefreshToken(user, existingTokenLifetime, existingToken.Value);
|
||||
return newRefreshToken;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ public class SecurityController(IDbContextFactory<AliasServerDbContext> dbContex
|
||||
ExpireDate = x.ExpireDate,
|
||||
CreatedAt = x.CreatedAt,
|
||||
})
|
||||
.Where(x => x.ExpireDate > DateTime.UtcNow)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
@@ -11,15 +11,11 @@ RUN dotnet restore "src/AliasVault.Api/AliasVault.Api.csproj"
|
||||
COPY . .
|
||||
|
||||
WORKDIR "/src/src/AliasVault.Api"
|
||||
RUN dotnet build "AliasVault.Api.csproj" -c "$BUILD_CONFIGURATION" -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "AliasVault.Api.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:3001
|
||||
ENV ASPNETCORE_PATHBASE=/api
|
||||
|
||||
@@ -26,6 +26,7 @@ builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnC
|
||||
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
builder.Services.ConfigureLogging(builder.Configuration, Assembly.GetExecutingAssembly().GetName().Name!, "../../logs");
|
||||
|
||||
builder.Services.AddAliasVaultDataProtection("AliasVault.Api");
|
||||
builder.Services.AddSingleton<ITimeProvider, SystemTimeProvider>();
|
||||
builder.Services.AddScoped<TimeValidationJwtBearerEvents>();
|
||||
builder.Services.AddScoped<AuthLoggingService>();
|
||||
@@ -40,8 +41,6 @@ builder.Services.AddLogging(logging =>
|
||||
});
|
||||
|
||||
builder.Services.AddAliasVaultSqliteConfiguration();
|
||||
builder.Services.AddAliasVaultDataProtection("AliasVault.Api");
|
||||
|
||||
builder.Services.AddIdentity<AliasVaultUser, AliasVaultRole>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = false;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="flex-grow p-6 pt-4 lg:pt-6 pb-28 lg:pb-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<button @onclick="GoBack" class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<button @onclick="GoBack" class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 @(_currentStep == SetupStep.TermsAndConditions ? "invisible" : "")">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
|
||||
@@ -18,19 +18,14 @@ COPY ["src/AliasVault.Client/AliasVault.Client.csproj", "src/AliasVault.Client/"
|
||||
RUN dotnet restore "src/AliasVault.Client/AliasVault.Client.csproj"
|
||||
COPY . .
|
||||
|
||||
# Build the Client project
|
||||
# Build and publish
|
||||
WORKDIR "/src/src/AliasVault.Client"
|
||||
RUN dotnet build "AliasVault.Client.csproj" -c "$BUILD_CONFIGURATION" -o /app/build
|
||||
|
||||
# Publish the Client project
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "AliasVault.Client.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# Final stage
|
||||
FROM nginx:1.24.0 AS final
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=publish /app/publish/wwwroot .
|
||||
COPY --from=build /app/publish/wwwroot .
|
||||
COPY /src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY /src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">Email</h3>
|
||||
</div>
|
||||
<div class="flex justify-end items-center space-x-2">
|
||||
@if (RefreshTimer is not null)
|
||||
@if (DbService.Settings.AutoEmailRefresh)
|
||||
{
|
||||
<div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="Auto-refresh enabled"></div>
|
||||
}
|
||||
@@ -56,27 +56,27 @@
|
||||
<div class="overflow-hidden shadow sm:rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
Subject
|
||||
</th>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
Date & Time
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
Subject
|
||||
</th>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
Date & Time
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
@foreach (var mail in MailboxEmails)
|
||||
{
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
|
||||
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@(mail.Subject.Substring(0, mail.Subject.Length > 30 ? 30 : mail.Subject.Length))...</span>
|
||||
</td>
|
||||
<td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
|
||||
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@mail.DateSystem</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var mail in MailboxEmails)
|
||||
{
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
|
||||
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@(mail.Subject.Substring(0, mail.Subject.Length > 30 ? 30 : mail.Subject.Length))...</span>
|
||||
</td>
|
||||
<td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
|
||||
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@mail.DateSystem</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -99,13 +99,56 @@
|
||||
private EmailApiModel Email { get; set; } = new();
|
||||
private bool EmailModalVisible { get; set; }
|
||||
private string Error { get; set; } = string.Empty;
|
||||
private Timer? RefreshTimer { get; set; }
|
||||
|
||||
private bool IsRefreshing { get; set; } = true;
|
||||
private bool IsLoading { get; set; } = true;
|
||||
|
||||
private bool IsSpamOk { get; set; } = false;
|
||||
|
||||
private bool IsPageVisible { get; set; } = true;
|
||||
private CancellationTokenSource? PollingCancellationTokenSource { get; set; }
|
||||
private const int ACTIVE_TAB_REFRESH_INTERVAL = 2000; // 2 seconds
|
||||
private readonly SemaphoreSlim RefreshSemaphore = new(1, 1);
|
||||
private DateTime LastRefreshTime = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked by JavaScript when the page visibility changes.
|
||||
/// </summary>
|
||||
/// <param name="isVisible">Boolean whether the page is visible or not.</param>
|
||||
/// <returns>Task.</returns>
|
||||
[JSInvokable]
|
||||
public async Task OnVisibilityChange(bool isVisible)
|
||||
{
|
||||
IsPageVisible = isVisible;
|
||||
if (isVisible)
|
||||
{
|
||||
// Only enable auto-refresh if the setting is enabled.
|
||||
if (DbService.Settings.AutoEmailRefresh)
|
||||
{
|
||||
await StartPolling();
|
||||
}
|
||||
|
||||
// Refresh immediately when tab becomes visible
|
||||
await ManualRefresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cancel polling.
|
||||
if (PollingCancellationTokenSource is not null)
|
||||
{
|
||||
await PollingCancellationTokenSource.CancelAsync();
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
PollingCancellationTokenSource?.Cancel();
|
||||
PollingCancellationTokenSource?.Dispose();
|
||||
RefreshSemaphore.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -124,12 +167,29 @@
|
||||
}
|
||||
IsSpamOk = IsSpamOkDomain(EmailAddress);
|
||||
|
||||
// Set up visibility change detection
|
||||
await JsInteropService.RegisterVisibilityCallback(DotNetObjectReference.Create(this));
|
||||
|
||||
// Only enable auto-refresh if the setting is enabled.
|
||||
if (DbService.Settings.AutoEmailRefresh)
|
||||
{
|
||||
RefreshTimer = new Timer(2000);
|
||||
RefreshTimer.Elapsed += async (sender, e) => await TimerRefresh();
|
||||
RefreshTimer.Start();
|
||||
await StartPolling();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (!ShowComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
await ManualRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,25 +206,62 @@
|
||||
IsSpamOk = IsSpamOkDomain(EmailAddress);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
/// <summary>
|
||||
/// Start the polling for new emails.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task StartPolling()
|
||||
{
|
||||
RefreshTimer?.Dispose();
|
||||
if (PollingCancellationTokenSource is not null)
|
||||
{
|
||||
await PollingCancellationTokenSource.CancelAsync();
|
||||
}
|
||||
|
||||
PollingCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
while (!PollingCancellationTokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
if (IsPageVisible)
|
||||
{
|
||||
// Only auto refresh when the tab is visible.
|
||||
await RefreshWithThrottling();
|
||||
await Task.Delay(ACTIVE_TAB_REFRESH_INTERVAL, PollingCancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Normal cancellation, ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
/// <summary>
|
||||
/// Refresh the emails with throttling to prevent multiple refreshes at the same time.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task RefreshWithThrottling()
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
if (!ShowComponent)
|
||||
if (!await RefreshSemaphore.WaitAsync(0)) // Don't wait if a refresh is in progress
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender)
|
||||
try
|
||||
{
|
||||
await ManualRefresh();
|
||||
var timeSinceLastRefresh = DateTime.UtcNow - LastRefreshTime;
|
||||
if (timeSinceLastRefresh.TotalMilliseconds < ACTIVE_TAB_REFRESH_INTERVAL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await LoadRecentEmailsAsync();
|
||||
LastRefreshTime = DateTime.UtcNow;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,15 +281,10 @@
|
||||
return Config.PrivateEmailDomains.Exists(x => email.EndsWith(x));
|
||||
}
|
||||
|
||||
private async Task TimerRefresh()
|
||||
{
|
||||
IsRefreshing = true;
|
||||
StateHasChanged();
|
||||
await LoadRecentEmailsAsync();
|
||||
IsRefreshing = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually refresh the emails.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task ManualRefresh()
|
||||
{
|
||||
IsLoading = true;
|
||||
@@ -202,6 +294,10 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)load recent emails by making an API call to the server.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task LoadRecentEmailsAsync()
|
||||
{
|
||||
if (!ShowComponent || EmailAddress is null)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<ConfirmModal />
|
||||
<FullScreenLoadingIndicator @ref="LoadingIndicator" />
|
||||
<TopMenu />
|
||||
<div class="flex pt-16 pb-4 lg:pb-16 overflow-hidden bg-gray-100 dark:bg-gray-900">
|
||||
<div class="flex pt-16 mb-4 lg:mb-16 overflow-hidden bg-gray-100 dark:bg-gray-900 relative z-20">
|
||||
<div id="main-content" class="relative z-10 w-full max-w-screen-2xl mx-auto h-full overflow-y-auto bg-gray-100 dark:bg-gray-900">
|
||||
<main>
|
||||
<GlobalNotificationDisplay />
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
/// </summary>
|
||||
public async Task DisableWebAuthn()
|
||||
{
|
||||
await AuthService.SetWebAuthnEnabledAsync(false, string.Empty, string.Empty, string.Empty);
|
||||
await AuthService.SetWebAuthnEnabledAsync(false);
|
||||
GlobalNotificationService.AddSuccessMessage("Quick Vault Unlock is successfully disabled.", true);
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
|
||||
/// <param name="webauthSalt">WebAuthn salt.</param>
|
||||
/// <param name="webauthCredentialDerivedKey">WebAuthn credential derived key.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task SetWebAuthnEnabledAsync(bool enabled, string? webauthCredentialId, string? webauthSalt, string? webauthCredentialDerivedKey)
|
||||
public async Task SetWebAuthnEnabledAsync(bool enabled, string? webauthCredentialId = null, string? webauthSalt = null, string? webauthCredentialDerivedKey = null)
|
||||
{
|
||||
await localStorage.SetItemAsStringAsync("webAuthnEnabled", enabled.ToString().ToLower());
|
||||
|
||||
@@ -311,7 +311,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
|
||||
private async Task RevokeTokenAsync()
|
||||
{
|
||||
// Remove webauthn enabled flag.
|
||||
await SetWebAuthnEnabledAsync(false, null, null, null);
|
||||
await SetWebAuthnEnabledAsync(false);
|
||||
|
||||
var tokenInput = new TokenModel
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace AliasVault.Client.Services;
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
/// <summary>
|
||||
@@ -237,6 +238,16 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
|
||||
await jsRuntime.InvokeVoidAsync("window.scrollTo", 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a visibility callback which is invoked when the visibility of component changes in client.
|
||||
/// </summary>
|
||||
/// <typeparam name="TComponent">Component type.</typeparam>
|
||||
/// <param name="objRef">DotNetObjectReference.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task RegisterVisibilityCallback<TComponent>(DotNetObjectReference<TComponent> objRef)
|
||||
where TComponent : class =>
|
||||
await jsRuntime.InvokeVoidAsync("window.registerVisibilityCallback", objRef);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a WebAuthn get credential operation.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
@@ -18,7 +16,6 @@
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -690,6 +690,10 @@ video {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-20 {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.col-span-1 {
|
||||
grid-column: span 1 / span 1;
|
||||
}
|
||||
@@ -1024,6 +1028,10 @@ video {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.max-w-screen-xl {
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -1350,6 +1358,11 @@ video {
|
||||
border-color: rgb(214 131 56 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-red-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(239 68 68 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-amber-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 251 235 / var(--tw-bg-opacity));
|
||||
@@ -1895,6 +1908,11 @@ video {
|
||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-amber-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(245 158 11 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -2070,6 +2088,11 @@ video {
|
||||
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-red-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:from-primary-600:hover {
|
||||
--tw-gradient-from: #d68338 var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(214 131 56 / 0) var(--tw-gradient-to-position);
|
||||
@@ -2125,6 +2148,11 @@ video {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-red-200:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(254 202 202 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:underline:hover {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
@@ -2280,6 +2308,11 @@ video {
|
||||
border-color: rgb(234 179 8 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:border-red-800:is(.dark *) {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(153 27 27 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-blue-800:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||
@@ -2360,6 +2393,11 @@ video {
|
||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-red-900:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(127 29 29 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:is(.dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2638,6 +2676,10 @@ video {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.sm\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sm\:rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
@@ -2764,6 +2806,10 @@ video {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.lg\:mb-16 {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.lg\:block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,30 @@
|
||||
<div id="app">
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
<div id="blazor-error-ui" class="text-white bg-red-700 dark:bg-red-900 p-6 border-t-2 border-red-500 dark:border-red-800">
|
||||
<div class="container mx-auto max-w-screen-xl px-4">
|
||||
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-8 h-8 text-white mr-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<span>An unhandled error has occurred. Please try reloading the page. If the issue persists, please contact support.</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="" class="reload flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 rounded-md transition-colors duration-150">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
</svg>
|
||||
Reload Page
|
||||
</a>
|
||||
<a class="dismiss hover:text-red-200">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -298,3 +298,9 @@ async function createWebAuthnCredentialAndDeriveKey(username) {
|
||||
return { Error: "WEBAUTHN_CREATE_ERROR", Message: createError.message };
|
||||
}
|
||||
}
|
||||
|
||||
window.registerVisibilityCallback = function (dotnetHelper) {
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
dotnetHelper.invokeMethodAsync('OnVisibilityChange', !document.hidden);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -237,7 +237,6 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
/// <param name="optionsBuilder">DbContextOptionsBuilder instance.</param>
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
// If the options are not already configured, use the appsettings.json file.
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
@@ -245,11 +244,14 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build();
|
||||
|
||||
// Add SQLite connection with enhanced settings
|
||||
optionsBuilder
|
||||
.UseSqlite(configuration.GetConnectionString("AliasServerDbContext"))
|
||||
.UseSqlite(
|
||||
configuration.GetConnectionString("AliasServerDbContext") + ";Mode=ReadWriteCreate;Cache=Shared",
|
||||
options => options.CommandTimeout(60))
|
||||
.UseLazyLoadingProxies();
|
||||
|
||||
// Set busy timeout using PRAGMA to avoid "The database file is locked" error.
|
||||
// Set additional PRAGMA settings
|
||||
var connection = Database.GetDbConnection();
|
||||
if (connection.State != System.Data.ConnectionState.Open)
|
||||
{
|
||||
@@ -258,7 +260,13 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "PRAGMA busy_timeout = 5000;";
|
||||
// Increase busy timeout
|
||||
command.CommandText = @"
|
||||
PRAGMA busy_timeout = 30000;
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA synchronous = FULL;
|
||||
PRAGMA temp_store = MEMORY;
|
||||
PRAGMA mmap_size = 1073741824;";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,11 @@ COPY ["src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj", "src/
|
||||
RUN dotnet restore "./src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj"
|
||||
COPY . .
|
||||
|
||||
# Build the SmtpService project
|
||||
# Build and publish the application
|
||||
WORKDIR "/src/src/Services/AliasVault.SmtpService"
|
||||
RUN dotnet build "./AliasVault.SmtpService.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# Publish the application to the /app/publish directory in the container
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./AliasVault.SmtpService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
ENTRYPOINT ["dotnet", "AliasVault.SmtpService.dll"]
|
||||
|
||||
@@ -73,7 +73,7 @@ public class DatabaseMessageStore(ILogger<DatabaseMessageStore> logger, Config c
|
||||
if (toAddressesFailCount == toAddressesCount)
|
||||
{
|
||||
// No valid recipients given.
|
||||
logger.LogWarning("No valid recipients in email, returning error to sender.");
|
||||
logger.LogInformation("No valid recipients in email, returning error to sender.");
|
||||
return SmtpResponse.NoValidRecipientsGiven;
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ public class DatabaseMessageStore(ILogger<DatabaseMessageStore> logger, Config c
|
||||
if (toAddress is null || !config.AllowedToDomains.Contains(toAddress.Host.ToLowerInvariant()))
|
||||
{
|
||||
// ToAddress domain is not allowed.
|
||||
logger.LogWarning(
|
||||
logger.LogInformation(
|
||||
"Rejected email: email for {ToAddress} is not allowed. Domain not in allowed domain list.",
|
||||
toAddress?.User + "@" + toAddress?.Host);
|
||||
return false;
|
||||
|
||||
@@ -25,12 +25,12 @@ public static class AppInfo
|
||||
/// <summary>
|
||||
/// Gets the minor version number.
|
||||
/// </summary>
|
||||
public const int VersionMinor = 7;
|
||||
public const int VersionMinor = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the patch version number.
|
||||
/// </summary>
|
||||
public const int VersionPatch = 0;
|
||||
public const int VersionPatch = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the build number, typically used in CI/CD pipelines.
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NUnit" Version="4.2.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.48.0" />
|
||||
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.49.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -29,7 +29,7 @@ public abstract class PlaywrightTest
|
||||
/// <summary>
|
||||
/// Gets or sets random unique account email that is used for the test.
|
||||
/// </summary>
|
||||
protected virtual string TestUserUsername { get; set; } = $"{Guid.NewGuid()}@test.com";
|
||||
protected virtual string TestUserUsername { get; set; } = $"{Guid.NewGuid().ToString()[..10]}@test.com";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets random unique account password that is used for the test.
|
||||
@@ -201,7 +201,7 @@ public abstract class PlaywrightTest
|
||||
/// </summary>
|
||||
protected void SetRandomTestUserCredentials()
|
||||
{
|
||||
TestUserUsername = $"{Guid.NewGuid()}@test.com";
|
||||
TestUserUsername = $"{Guid.NewGuid().ToString()[..10]}@test.com";
|
||||
TestUserPassword = Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
|
||||
@@ -116,4 +116,55 @@ public class UserSetupTests : ClientPlaywrightTest
|
||||
var errorMessage = await WaitForAndGetElement("text='Username is already in use.'");
|
||||
Assert.That(errorMessage, Is.Not.Null, "The 'Username is already in use' error message should appear.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if the "Username too short" and "Username too long" error appears when trying to register with an invalid username.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
[Order(3)]
|
||||
public async Task UserSetupUsernameLengthTest()
|
||||
{
|
||||
// Logout.
|
||||
await Logout();
|
||||
await Page.GotoAsync(AppBaseUrl);
|
||||
await WaitForUrlAsync("user/start", "Create new vault");
|
||||
|
||||
// Click the "Create new vault" anchor tag.
|
||||
var createVaultButton = await WaitForAndGetElement("a:has-text('Create new vault')");
|
||||
await createVaultButton.ClickAsync();
|
||||
|
||||
// Wait for the terms and conditions to load.
|
||||
await WaitForUrlAsync("user/setup", "Terms and Conditions");
|
||||
|
||||
// Accept the terms and conditions.
|
||||
var acceptTermsCheckbox = await WaitForAndGetElement("input[id='agreeTerms']");
|
||||
await acceptTermsCheckbox.CheckAsync();
|
||||
|
||||
// Wait for the continue button to be enabled.
|
||||
await Task.Delay(100);
|
||||
|
||||
// Press the continue button.
|
||||
var continueButton = await WaitForAndGetElement("button:has-text('Continue')");
|
||||
await continueButton.ClickAsync();
|
||||
|
||||
// Wait for the username step to load.
|
||||
await WaitForUrlAsync("user/setup", "Username");
|
||||
var usernameField = await WaitForAndGetElement("input[id='username']");
|
||||
await usernameField.FillAsync("ts"); // Too short username (2 chars)
|
||||
|
||||
// Check if the "Username is too short" error message appears
|
||||
var errorMessage = await WaitForAndGetElement("text='Username too short: must be at least 3 characters long.'");
|
||||
Assert.That(errorMessage, Is.Not.Null, "The 'Username too short' error message should appear.");
|
||||
|
||||
// Clear the username field.
|
||||
await usernameField.FillAsync(string.Empty);
|
||||
|
||||
// Fill in a too long username (41 chars).
|
||||
await usernameField.FillAsync("asdasdasdasdasdasdasdasdasdaaaasasddsdasd"); // Too long username (41 chars)
|
||||
|
||||
// Check if the "Username is too short" error message appears
|
||||
errorMessage = await WaitForAndGetElement("text='Username too long: cannot be longer than 40 characters.'");
|
||||
Assert.That(errorMessage, Is.Not.Null, "The 'Username too long' error message should appear.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
|
||||
<PackageReference Include="NUnit" Version="4.2.2"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NUnit" Version="4.2.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
|
||||
|
||||
@@ -12,28 +12,6 @@ namespace AliasVault.Tests.Utilities;
|
||||
/// </summary>
|
||||
public class FaviconExtractorTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test extracting a favicon from a known website.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
[Test]
|
||||
public async Task ExtractFaviconSpamOk()
|
||||
{
|
||||
var faviconBytes = await FaviconExtractor.FaviconExtractor.GetFaviconAsync("https://spamok.com");
|
||||
Assert.That(faviconBytes, Is.Not.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting a favicon from a known website.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
[Test]
|
||||
public async Task ExtractFaviconDumpert()
|
||||
{
|
||||
var faviconBytes = await FaviconExtractor.FaviconExtractor.GetFaviconAsync("https://www.dumpert.nl");
|
||||
Assert.That(faviconBytes, Is.Not.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test extracting a favicon from a known website.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,16 +13,11 @@ RUN dotnet restore "src/Utilities/AliasVault.InstallCli/AliasVault.InstallCli.cs
|
||||
# Copy the entire source code
|
||||
COPY . .
|
||||
|
||||
# Build the project
|
||||
RUN dotnet build "src/Utilities/AliasVault.InstallCli/AliasVault.InstallCli.csproj" \
|
||||
-c "$BUILD_CONFIGURATION" -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "src/Utilities/AliasVault.InstallCli/AliasVault.InstallCli.csproj" \
|
||||
# Build and publish in one step
|
||||
RUN dotnet publish "src/Utilities/AliasVault.InstallCli/AliasVault.InstallCli.csproj" \
|
||||
-c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
ENTRYPOINT ["dotnet", "AliasVault.InstallCli.dll"]
|
||||
|
||||