mirror of
https://github.com/aliasvault/aliasvault.git
synced 2025-12-24 06:39:12 -05:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48b96b4151 | ||
|
|
e9064643a6 | ||
|
|
667592411f | ||
|
|
dfdf4981cb | ||
|
|
0f377bdec6 | ||
|
|
ba17474e62 | ||
|
|
c09ad99739 | ||
|
|
799efe1772 | ||
|
|
1d79400df5 | ||
|
|
cc4a2e087f | ||
|
|
64a76f3b9f | ||
|
|
7c1aaab291 | ||
|
|
63556d163a | ||
|
|
c49c0e4ad5 | ||
|
|
3f2121f272 | ||
|
|
ebdcf778be | ||
|
|
fb669df9cf | ||
|
|
cedf7d0733 | ||
|
|
00db83f478 | ||
|
|
03b7f92a44 | ||
|
|
d542a4273d | ||
|
|
dcb27ca543 | ||
|
|
78635b8ba1 | ||
|
|
e18d31ee9b | ||
|
|
0db5fb64a8 | ||
|
|
e36d28eb99 | ||
|
|
dd331f75c9 | ||
|
|
aa11697ee2 | ||
|
|
fdd698dd0a | ||
|
|
c8df588401 | ||
|
|
a8373338c2 | ||
|
|
15abd1f51b | ||
|
|
71407cc86d | ||
|
|
85a3fed127 | ||
|
|
6b8f0d6cdf | ||
|
|
43441831d4 | ||
|
|
319cff8fe1 | ||
|
|
5904204465 | ||
|
|
6c8cc92a67 | ||
|
|
693860acef | ||
|
|
f7626ec15b | ||
|
|
03c6bbc81f | ||
|
|
bbe7ef1b2b | ||
|
|
027b95da15 | ||
|
|
e9c33a808f | ||
|
|
2545e1204f | ||
|
|
970d334b59 | ||
|
|
50a18dc461 | ||
|
|
0dcc77eb0d | ||
|
|
cd84592be1 | ||
|
|
df6de32a4a | ||
|
|
3d24772caa | ||
|
|
1a106e59fc | ||
|
|
290460c095 | ||
|
|
17802dc216 | ||
|
|
0de52a396a | ||
|
|
64705e582d | ||
|
|
b09cdcec1e | ||
|
|
87bb34f3ba | ||
|
|
5b53208a3e | ||
|
|
7a687bba43 | ||
|
|
aafac49bcb | ||
|
|
201af7b88a |
@@ -1,7 +1,8 @@
|
||||
API_URL=
|
||||
HOSTNAME=
|
||||
JWT_KEY=
|
||||
DATA_PROTECTION_CERT_PASS=
|
||||
ADMIN_PASSWORD_HASH=
|
||||
ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z
|
||||
PRIVATE_EMAIL_DOMAINS=
|
||||
SMTP_TLS_ENABLED=false
|
||||
LETSENCRYPT_ENABLED=false
|
||||
|
||||
52
.github/workflows/docker-compose-build.yml
vendored
52
.github/workflows/docker-compose-build.yml
vendored
@@ -1,3 +1,4 @@
|
||||
# This workflow will test if building the Docker Compose containers from scratch works.
|
||||
name: Docker Compose Build
|
||||
|
||||
on:
|
||||
@@ -20,7 +21,7 @@ jobs:
|
||||
- name: Set permissions and run install.sh
|
||||
run: |
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
./install.sh build --verbose
|
||||
|
||||
- name: Set up Docker Compose
|
||||
run: |
|
||||
@@ -32,29 +33,43 @@ jobs:
|
||||
run: |
|
||||
# Wait for a few seconds
|
||||
sleep 10
|
||||
- name: Test if localhost:80 (WASM app) responds
|
||||
- name: Test if localhost:443 (WASM app) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:80)
|
||||
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 is configured correctly."
|
||||
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 localhost:81 (WebApi) responds
|
||||
- name: Test if localhost:443/api (WebApi) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:81)
|
||||
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 is configured correctly."
|
||||
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
|
||||
|
||||
- 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"
|
||||
@@ -73,16 +88,13 @@ jobs:
|
||||
echo "SmtpService responded on port 2525"
|
||||
fi
|
||||
|
||||
- name: Test if localhost:8080 (Admin) responds
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
command: |
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/user/login)
|
||||
if [ "$http_code" -ne 200 ]; then
|
||||
echo "Service did not respond with expected 200 OK. Check if admin app is configured correctly."
|
||||
exit 1
|
||||
else
|
||||
echo "Service responded with $http_code"
|
||||
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"
|
||||
exit 1
|
||||
else
|
||||
echo "Password reset output format is correct"
|
||||
fi
|
||||
|
||||
100
.github/workflows/docker-compose-pull.yml
vendored
Normal file
100
.github/workflows/docker-compose-pull.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
# This workflow will test if pulling the latest Docker Compose containers from the registry works.
|
||||
name: Docker Compose Pull
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test-docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
docker:
|
||||
image: docker:26.0.0
|
||||
options: --privileged
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set permissions and run install.sh
|
||||
run: |
|
||||
chmod +x install.sh
|
||||
./install.sh install --verbose
|
||||
|
||||
- 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
|
||||
|
||||
- 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 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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"
|
||||
exit 1
|
||||
else
|
||||
echo "Password reset output format is correct"
|
||||
fi
|
||||
5
.github/workflows/dotnet-e2e-admin-tests.yml
vendored
5
.github/workflows/dotnet-e2e-admin-tests.yml
vendored
@@ -1,3 +1,4 @@
|
||||
# This workflow will test if running the E2E Admin tests via Playwright CLI works.
|
||||
name: .NET E2E Admin Tests (Playwright)
|
||||
|
||||
on:
|
||||
@@ -16,7 +17,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.304
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
@@ -25,7 +26,7 @@ jobs:
|
||||
run: dotnet build
|
||||
|
||||
- name: Ensure browsers are installed
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps
|
||||
|
||||
- name: Run AdminTests with retry
|
||||
uses: nick-fields/retry@v3
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# This workflow will test if running the E2E Client tests via Playwright CLI works.
|
||||
name: .NET E2E Client Tests (Playwright with Sharding)
|
||||
|
||||
on:
|
||||
@@ -20,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.304
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
run: dotnet build
|
||||
|
||||
- name: Ensure browsers are installed
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps
|
||||
|
||||
- name: Run ClientTests with retry (Shard ${{ matrix.shard }})
|
||||
uses: nick-fields/retry@v3
|
||||
|
||||
5
.github/workflows/dotnet-e2e-misc-tests.yml
vendored
5
.github/workflows/dotnet-e2e-misc-tests.yml
vendored
@@ -1,3 +1,4 @@
|
||||
# This workflow will test if running the E2E Misc tests via Playwright CLI works.
|
||||
name: .NET E2E Misc Tests (Playwright)
|
||||
|
||||
on:
|
||||
@@ -16,7 +17,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.304
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
@@ -25,7 +26,7 @@ jobs:
|
||||
run: dotnet build
|
||||
|
||||
- name: Ensure browsers are installed
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps
|
||||
|
||||
- name: Run remaining tests with retry
|
||||
uses: nick-fields/retry@v3
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
# This workflow will test if running the integration tests works.
|
||||
name: .NET Integration Tests
|
||||
|
||||
on:
|
||||
@@ -19,7 +17,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.304
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
|
||||
6
.github/workflows/dotnet-unit-tests.yml
vendored
6
.github/workflows/dotnet-unit-tests.yml
vendored
@@ -1,6 +1,4 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
# This workflow will test if running the unit tests works.
|
||||
name: .NET Unit Tests
|
||||
|
||||
on:
|
||||
@@ -18,7 +16,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.304
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
|
||||
87
.github/workflows/publish-docker-images.yml
vendored
Normal file
87
.github/workflows/publish-docker-images.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
# This workflow will publish new Docker images to the GitHub Container Registry when a new release is published.
|
||||
name: Publish Docker Images
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Convert repository name to lowercase
|
||||
run: |
|
||||
echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}
|
||||
|
||||
- name: Build and push API image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: src/AliasVault.Api/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-api:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-api:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push Client image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: src/AliasVault.Client/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-client:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-client:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push Admin image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: src/AliasVault.Admin/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push SMTP image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: src/Services/AliasVault.SmtpService/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-smtp:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-smtp:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push Reverse Proxy image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:${{ github.ref_name }}
|
||||
|
||||
- name: Build and push InstallCli image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: src/Utilities/AliasVault.InstallCli/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:${{ github.ref_name }}
|
||||
@@ -1,3 +1,4 @@
|
||||
# This workflow will perform a SonarCloud code analysis on every push to the main branch or when a pull request is opened, synchronized, or reopened.
|
||||
name: SonarCloud code analysis
|
||||
on:
|
||||
push:
|
||||
@@ -10,6 +11,14 @@ jobs:
|
||||
name: Build and analyze
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Install WASM workload
|
||||
run: dotnet workload install wasm-tools
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -268,6 +268,7 @@ ServiceFabricBackup/
|
||||
|
||||
# SQLite files
|
||||
*.sqlite
|
||||
*.sqlite.*
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
|
||||
@@ -392,3 +393,9 @@ src/Tests/AliasVault.E2ETests/appsettings.Development.json
|
||||
# Draw.io diagram temp files
|
||||
*.drawio.*
|
||||
|
||||
# Certificates
|
||||
certificates/**/*.crt
|
||||
certificates/**/*.key
|
||||
certificates/**/*.pfx
|
||||
certificates/**/*.pem
|
||||
certificates/letsencrypt/**
|
||||
|
||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
# Install OpenSSL
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
# Copy configuration and entrypoint script
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
COPY entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
# Create SSL directory
|
||||
RUN mkdir -p /etc/nginx/ssl && chmod 755 /etc/nginx/ssl \
|
||||
&& chmod +x /docker-entrypoint.sh
|
||||
|
||||
EXPOSE 80 443
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
77
README.md
77
README.md
@@ -10,7 +10,7 @@
|
||||
Open-source password and alias manager
|
||||
</h3>
|
||||
|
||||
[<img src="https://img.shields.io/github/v/release/lanedirt/AliasVault?include_prereleases&logo=github">](https://github.com/lanedirt/OGameX/releases)
|
||||
[<img src="https://img.shields.io/github/v/release/lanedirt/AliasVault?include_prereleases&logo=github">](https://github.com/lanedirt/AliasVault/releases)
|
||||
[<img src="https://img.shields.io/github/actions/workflow/status/lanedirt/AliasVault/docker-compose-build.yml?label=docker-compose%20build">](https://github.com/lanedirt/AliasVault/actions/workflows/docker-compose-build.yml)
|
||||
[<img src="https://img.shields.io/github/actions/workflow/status/lanedirt/AliasVault/dotnet-unit-tests.yml?label=unit tests">](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-build-run-tests.yml)
|
||||
[<img src="https://img.shields.io/github/actions/workflow/status/lanedirt/AliasVault/dotnet-integration-tests.yml?label=integration tests">](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-build-run-tests.yml)
|
||||
@@ -35,44 +35,67 @@ A live demo of the app is available at the official website at [app.aliasvault.n
|
||||
<img width="700" alt="Screenshot of AliasVault" src="docs/img/screenshot.png">
|
||||
|
||||
## Installation
|
||||
To install AliasVault on your local machine, follow the steps below. Note: the install process is tested on MacOS and Linux. It should work on Windows too, but you might need to adjust some commands.
|
||||
|
||||
### Requirements:
|
||||
- Access to a terminal
|
||||
- Docker
|
||||
- Git
|
||||
Choose one of the following installation methods:
|
||||
|
||||
### 1. Clone and run install script
|
||||
AliasVault comes with a install script that prepares the .env file, builds the Docker image, and starts the AliasVault containers.
|
||||
### Option 1: Quick Install (Pre-built Images)
|
||||
|
||||
This method uses pre-built Docker images and works on minimal hardware specifications:
|
||||
- Linux (Ubuntu or RHEL based distros recommended)
|
||||
- 512MB RAM
|
||||
- 1 vCPU
|
||||
- At least 16GB disk space
|
||||
- Docker installed
|
||||
|
||||
```bash
|
||||
# Clone this Git repository to "AliasVault" directory
|
||||
$ git clone https://github.com/lanedirt/AliasVault.git
|
||||
# Download install script
|
||||
curl -o install.sh https://raw.githubusercontent.com/lanedirt/AliasVault/main/install.sh
|
||||
|
||||
# Go to the project directory
|
||||
$ cd AliasVault
|
||||
|
||||
# Make install script executable and run it.
|
||||
$ chmod +x install.sh && ./install.sh
|
||||
# Make install script executable and run it. This will create the .env file, pull the Docker images, and start the AliasVault containers.
|
||||
chmod +x install.sh
|
||||
./install.sh install
|
||||
```
|
||||
|
||||
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.
|
||||
### Option 2: Build from Source
|
||||
|
||||
### 2. Ready to use
|
||||
The install script executed in step #1 will output the URL where the app is available. By default this is http://localhost:80 for the client and http://localhost:8080 for the admin.
|
||||
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
|
||||
|
||||
> Note: If you want to change the default AliasVault ports you can do so in the `docker-compose.yml` file.
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/lanedirt/AliasVault.git
|
||||
cd AliasVault
|
||||
|
||||
#### Note for first time build:
|
||||
- When running the init script for the first time, it may take a few minutes for Docker to download all dependencies. Subsequent builds will be faster.
|
||||
# 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
|
||||
|
||||
The install script will output the URL where the app is available. By default this is:
|
||||
- Client: https://localhost
|
||||
- Admin portal: https://localhost/admin
|
||||
|
||||
> 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.
|
||||
|
||||
#### Other useful commands:
|
||||
- To reset the admin password, run the install.sh script with the `--reset-admin-password` flag.
|
||||
- To uninstall AliasVault, make the uninstall script executable with `chmod +x uninstall.sh` first, then run the script: `./uninstall.sh`.
|
||||
This will remove all containers, images, and volumes related to AliasVault. It will keep all files and configuration intact however, so you can easily reinstall AliasVault later.
|
||||
#### 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`
|
||||
|
||||
## Security & Architecture
|
||||
## Security Architecture
|
||||
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.
|
||||
@@ -81,7 +104,7 @@ 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](docs/security-architecture.md)
|
||||
|
||||
## Tech stack / credits
|
||||
The following technologies, frameworks and libraries are used in this project:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
This is the default location where (self-generated) certificates are stored.
|
||||
# Certificates directory structure
|
||||
|
||||
For example, the API and Admin projects make use of the .NET DataProtection API that depends on
|
||||
certificates for encrypting various types of application data such as authentication cookies, anti-forgery tokens etc.
|
||||
This directory contains certificates for AliasVault.
|
||||
|
||||
- `app`: Certificates that AliasVault uses to protect application data at rest (e.g. .NET DataProtection keys)
|
||||
- `ssl`: SSL/TLS certificates for AliasVault hosted services
|
||||
|
||||
7
certificates/ssl/README.md
Normal file
7
certificates/ssl/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# SSL certificates directory structure
|
||||
|
||||
This directory contains SSL/TLS certificates for various AliasVault services:
|
||||
|
||||
- `admin`: Certificate for the Admin UI.
|
||||
- `api`: Certificate for the API service.
|
||||
- `client`: Certificate for the Client UI.
|
||||
30
docker-compose.build.yml
Normal file
30
docker-compose.build.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: aliasvault-reverse-proxy
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
client:
|
||||
image: aliasvault-client
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Client/Dockerfile
|
||||
|
||||
api:
|
||||
image: aliasvault-api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Api/Dockerfile
|
||||
|
||||
admin:
|
||||
image: aliasvault-admin
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Admin/Dockerfile
|
||||
|
||||
smtp:
|
||||
image: aliasvault-smtp
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/AliasVault.SmtpService/Dockerfile
|
||||
7
docker-compose.letsencrypt.yml
Normal file
7
docker-compose.letsencrypt.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
certbot:
|
||||
image: certbot/certbot
|
||||
volumes:
|
||||
- ./certificates/letsencrypt:/etc/letsencrypt:rw
|
||||
- ./certificates/letsencrypt/www:/var/www/certbot:rw
|
||||
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
|
||||
@@ -1,49 +1,58 @@
|
||||
services:
|
||||
admin:
|
||||
image: aliasvault-admin
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Admin/Dockerfile
|
||||
reverse-proxy:
|
||||
image: ghcr.io/lanedirt/aliasvault-reverse-proxy:latest
|
||||
ports:
|
||||
- "8080:8082"
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./certificates:/certificates:rw
|
||||
- ./database:/database:rw
|
||||
- ./logs:/logs:rw
|
||||
- ./certificates/ssl:/etc/nginx/ssl:rw
|
||||
- ./certificates/letsencrypt:/etc/nginx/ssl-letsencrypt:rw
|
||||
- ./certificates/letsencrypt/www:/var/www/certbot:rw
|
||||
depends_on:
|
||||
- admin
|
||||
- client
|
||||
- api
|
||||
- smtp
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
client:
|
||||
image: aliasvault-client
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Client/Dockerfile
|
||||
ports:
|
||||
- "80:8080"
|
||||
image: ghcr.io/lanedirt/aliasvault-client:latest
|
||||
volumes:
|
||||
- ./logs/msbuild:/src/msbuild-logs:rw
|
||||
expose:
|
||||
- "3000"
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
api:
|
||||
image: aliasvault-api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/AliasVault.Api/Dockerfile
|
||||
ports:
|
||||
- "81:8081"
|
||||
image: ghcr.io/lanedirt/aliasvault-api:latest
|
||||
expose:
|
||||
- "3001"
|
||||
volumes:
|
||||
- ./certificates:/certificates:rw
|
||||
- ./database:/database:rw
|
||||
- ./certificates/app:/certificates/app:rw
|
||||
- ./logs:/logs:rw
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
|
||||
admin:
|
||||
image: ghcr.io/lanedirt/aliasvault-admin:latest
|
||||
expose:
|
||||
- "3002"
|
||||
volumes:
|
||||
- ./database:/database:rw
|
||||
- ./certificates/app:/certificates/app:rw
|
||||
- ./logs:/logs:rw
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
smtp:
|
||||
image: aliasvault-smtp
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Services/AliasVault.SmtpService/Dockerfile
|
||||
image: ghcr.io/lanedirt/aliasvault-smtp:latest
|
||||
ports:
|
||||
- "25:25"
|
||||
- "587:587"
|
||||
@@ -53,3 +62,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
|
||||
networks:
|
||||
aliasvault:
|
||||
name: aliasvault_default
|
||||
|
||||
@@ -9,91 +9,114 @@ This README provides step-by-step instructions for manually setting up AliasVaul
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Create .env file**
|
||||
1. **Create required directories**
|
||||
|
||||
Create the following directories in your project root:
|
||||
```bash
|
||||
mkdir -p certificates/ssl certificates/app database logs/msbuild
|
||||
```
|
||||
|
||||
2. **Create .env file**
|
||||
|
||||
Copy the `.env.example` file to create a new `.env` file:
|
||||
```
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. **Generate and set JWT_KEY**
|
||||
3. **Set HOSTNAME**
|
||||
|
||||
Update the .env file and set the JWT_KEY environment variable to a random 32-char string. This key is used for JWT token generation and should be kept secure.
|
||||
|
||||
Generate a random 32 char string for the JWT:
|
||||
Update the .env file with your hostname (default is localhost):
|
||||
```bash
|
||||
HOSTNAME=localhost
|
||||
```
|
||||
|
||||
4. **Generate and set JWT_KEY**
|
||||
|
||||
Generate a random 32-char string for JWT token generation:
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Add the generated key to the .env file:
|
||||
|
||||
Add the generated key to the .env file:
|
||||
```bash
|
||||
JWT_KEY=your_generated_key_here
|
||||
```
|
||||
JWT_KEY=your_32_char_string_here
|
||||
|
||||
3. **Set PRIVATE_EMAIL_DOMAINS**
|
||||
5. **Generate and set DATA_PROTECTION_CERT_PASS**
|
||||
|
||||
Update the .env file and set the PRIVATE_EMAIL_DOMAINS value the allowed domains that can be used for email addresses. Separate multiple domains with commas.
|
||||
Generate a random password for the data protection certificate:
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Add it to the .env file:
|
||||
```bash
|
||||
DATA_PROTECTION_CERT_PASS=your_generated_password_here
|
||||
```
|
||||
|
||||
6. **Set PRIVATE_EMAIL_DOMAINS**
|
||||
|
||||
Update the .env file with allowed email domains. Use DISABLED.TLD to disable email support:
|
||||
```bash
|
||||
PRIVATE_EMAIL_DOMAINS=yourdomain.com,anotherdomain.com
|
||||
```
|
||||
Replace `yourdomain.com,anotherdomain.com` with your actual allowed domains.
|
||||
|
||||
4. **Set SMTP_TLS_ENABLED**
|
||||
|
||||
Decide whether to enable TLS for email and add it to the .env file:
|
||||
Or to disable email:
|
||||
```bash
|
||||
PRIVATE_EMAIL_DOMAINS=DISABLED.TLD
|
||||
```
|
||||
SMTP_TLS_ENABLED=true
|
||||
|
||||
7. **Set SUPPORT_EMAIL (Optional)**
|
||||
|
||||
Add a support email address if desired:
|
||||
```bash
|
||||
SUPPORT_EMAIL=support@yourdomain.com
|
||||
```
|
||||
Or set it to `false` if you don't want to enable TLS.
|
||||
|
||||
5. **Generate admin password**
|
||||
|
||||
Set the admin password hash in the .env file. The password hash is generated using the `InitializationCLI` utility.
|
||||
8. **Generate admin password**
|
||||
|
||||
Build the Docker image for password hashing:
|
||||
```
|
||||
docker build -t initcli -f src/Utilities/InitializationCLI/Dockerfile .
|
||||
```bash
|
||||
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile .
|
||||
```
|
||||
|
||||
Generate the password hash:
|
||||
```
|
||||
docker run --rm initcli "<your_prefered_admin_password_here>"
|
||||
```bash
|
||||
docker run --rm installcli "your_preferred_admin_password_here"
|
||||
```
|
||||
|
||||
Add the password hash and generation timestamp to the .env file:
|
||||
```
|
||||
ADMIN_PASSWORD_HASH=<output_of_step_above>
|
||||
```bash
|
||||
ADMIN_PASSWORD_HASH=<output_from_previous_command>
|
||||
ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z
|
||||
```
|
||||
|
||||
6. **Build and start Docker containers**
|
||||
9. **Build and start Docker containers**
|
||||
|
||||
Build the Docker Compose stack:
|
||||
```
|
||||
docker-compose build
|
||||
```
|
||||
Build the Docker Compose stack:
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
Start the Docker Compose stack:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
Start the Docker Compose stack:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
7. **Access AliasVault**
|
||||
10. **Access AliasVault**
|
||||
|
||||
AliasVault should now be running. You can access it as follows:
|
||||
AliasVault should now be running. You can access it at:
|
||||
|
||||
- Admin Panel: http://localhost:8080/
|
||||
- Admin Panel: https://localhost/admin
|
||||
- Username: admin
|
||||
- Password: [Use the ADMIN_PASSWORD generated in step 5]
|
||||
- Password: [Use the password you set in step 8]
|
||||
|
||||
- Client Website: http://localhost:80/
|
||||
- Client Website: https://localhost/
|
||||
- Create your own account from here
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Make sure to save the admin password (ADMIN_PASSWORD) generated in step 5 in a secure location. It won't be shown again.
|
||||
- If you need to reset the admin password in the future, you'll need to generate a new hash and update the .env file manually.
|
||||
Afterwards restart the docker containers which will update the admin password in the database.
|
||||
- Make sure to save the admin password you used in step 8 in a secure location.
|
||||
- If you need to reset the admin password in the future, repeat step 8 and restart the Docker containers.
|
||||
- Always keep your .env file secure and do not share it, as it contains sensitive information.
|
||||
|
||||
## Troubleshooting
|
||||
@@ -101,10 +124,10 @@ Afterwards restart the docker containers which will update the admin password in
|
||||
If you encounter any issues during the setup:
|
||||
|
||||
1. Check the Docker logs:
|
||||
```bash
|
||||
docker compose logs
|
||||
```
|
||||
docker-compose logs
|
||||
```
|
||||
2. Ensure all required ports (8080 and 80) are available and not being used by other services.
|
||||
2. Ensure all required ports (80 and 443) are available and not being used by other services.
|
||||
3. Verify that all environment variables in the .env file are set correctly.
|
||||
|
||||
For further assistance, please refer to the project documentation or seek support through the appropriate channels.
|
||||
|
||||
33
entrypoint.sh
Normal file
33
entrypoint.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Create SSL directory if it doesn't exist
|
||||
mkdir -p /etc/nginx/ssl
|
||||
|
||||
# Generate self-signed SSL certificate if not exists
|
||||
if [ ! -f /etc/nginx/ssl/cert.pem ] || [ ! -f /etc/nginx/ssl/key.pem ]; then
|
||||
echo "Generating new SSL certificate..."
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /etc/nginx/ssl/key.pem \
|
||||
-out /etc/nginx/ssl/cert.pem \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 /etc/nginx/ssl/cert.pem
|
||||
chmod 600 /etc/nginx/ssl/key.pem
|
||||
fi
|
||||
|
||||
# Create the appropriate SSL configuration based on LETSENCRYPT_ENABLED
|
||||
if [ "${LETSENCRYPT_ENABLED}" = "true" ]; then
|
||||
cat > /etc/nginx/ssl.conf << EOF
|
||||
ssl_certificate /etc/nginx/ssl-letsencrypt/live/${HOSTNAME}/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl-letsencrypt/live/${HOSTNAME}/privkey.pem;
|
||||
EOF
|
||||
else
|
||||
cat > /etc/nginx/ssl.conf << EOF
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Start nginx
|
||||
nginx -g "daemon off;"
|
||||
1153
install.sh
1153
install.sh
File diff suppressed because it is too large
Load Diff
100
nginx.conf
Normal file
100
nginx.conf
Normal file
@@ -0,0 +1,100 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream client {
|
||||
server client:3000;
|
||||
}
|
||||
|
||||
upstream api {
|
||||
server api:3001;
|
||||
}
|
||||
|
||||
upstream admin {
|
||||
server admin:3002;
|
||||
}
|
||||
|
||||
# Preserve any existing X-Forwarded-* headers, this is relevant if AliasVault
|
||||
# is running behind another reverse proxy.
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Enable gzip compression, which reduces the amount of data that needs to be transferred
|
||||
# to speed up WASM load times.
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Handle ACME challenge for Let's Encrypt certificate validation
|
||||
location /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
root /var/www/certbot;
|
||||
try_files $uri =404;
|
||||
default_type "text/plain";
|
||||
add_header Cache-Control "no-cache";
|
||||
break;
|
||||
}
|
||||
|
||||
# Redirect all other HTTP traffic to HTTPS
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name _;
|
||||
|
||||
# Include the appropriate SSL certificate configuration generated
|
||||
# by the entrypoint script.
|
||||
include /etc/nginx/ssl.conf;
|
||||
|
||||
# Admin interface
|
||||
location /admin {
|
||||
proxy_pass http://admin;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# Add WebSocket support for Blazor server
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
# API endpoints
|
||||
location /api {
|
||||
proxy_pass http://api;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
# Client app (root path)
|
||||
location / {
|
||||
proxy_pass http://client;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>aspnet-AliasVault.Admin-1DAADE35-C01B-43BB-B440-AA5E1E0B672D</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<NoWarn>1701;1702;NU1900</NoWarn>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Admin.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Admin.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Admin.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Admin.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<a href="/">
|
||||
@using AliasVault.Admin.Services
|
||||
@inject NavigationService NavigationService
|
||||
|
||||
<a href="@NavigationService.BaseUri">
|
||||
<div class="text-5xl font-bold text-gray-900 dark:text-white mb-4 flex items-center">
|
||||
<img src="img/logo.svg" alt="AliasVault" class="w-20 h-20 mr-2" />
|
||||
<span>AliasVault</span>
|
||||
<span class="ps-2 self-center hidden sm:flex text-lg font-bold whitespace-nowrap text-white bg-red-600 rounded-full px-2 py-1 ml-2">Admin</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
</a>
|
||||
@@ -72,7 +72,7 @@ public class AuthBase : OwningComponentBase
|
||||
// Redirect to home if the user is already authenticated
|
||||
if (SignInManager.IsSignedIn(user))
|
||||
{
|
||||
NavigationService.RedirectTo("/");
|
||||
NavigationService.RedirectTo("./");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="remember" class="font-medium text-gray-900 dark:text-white">Remember me</label>
|
||||
</div>
|
||||
<a href="/user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">Lost Password?</a>
|
||||
<a href="user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">Lost Password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Login to your account</button>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
catch
|
||||
{
|
||||
// Redirect to the home page with hard refresh.
|
||||
NavigationService.RedirectTo("/", true);
|
||||
NavigationService.RedirectTo("./", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class Config
|
||||
/// Gets or sets the admin password hash which is generated by install.sh and will be set
|
||||
/// as the default password for the admin user.
|
||||
/// </summary>
|
||||
public string AdminPasswordHash { get; set; } = "false";
|
||||
public string AdminPasswordHash { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last time the password was changed. This is used to check if the
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 8082
|
||||
EXPOSE 3002
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the project files and restore dependencies
|
||||
COPY ["src/AliasVault.Admin/AliasVault.Admin.csproj", "src/AliasVault.Admin/"]
|
||||
RUN dotnet restore "src/AliasVault.Admin/AliasVault.Admin.csproj"
|
||||
COPY . .
|
||||
|
||||
# Build the WebApi project
|
||||
WORKDIR "/src/src/AliasVault.Admin"
|
||||
RUN dotnet build "AliasVault.Admin.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.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
@@ -24,6 +20,7 @@ RUN dotnet publish "AliasVault.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/p
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
EXPOSE 8082
|
||||
ENV ASPNETCORE_URLS=http://+:8082
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:3002
|
||||
ENV ASPNETCORE_PATHBASE=/admin
|
||||
ENTRYPOINT ["dotnet", "AliasVault.Admin.dll"]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
|
||||
<base href="/"/>
|
||||
<base href="@NavigationService.BaseUri"/>
|
||||
<link rel="stylesheet" href="@VersionService.GetVersionedPath("css/tailwind.css")"/>
|
||||
<link rel="stylesheet" href="@VersionService.GetVersionedPath("css/app.css")"/>
|
||||
<link rel="stylesheet" href="AliasVault.Admin.styles.css"/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<nav class="fixed z-30 w-full border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700 py-3 px-4 bg-primary-100">
|
||||
<div class="flex justify-between items-center max-w-screen-2xl mx-auto">
|
||||
<div class="flex justify-start items-center">
|
||||
<a href="/" class="flex mr-14 flex-shrink-0">
|
||||
<a href="@NavigationService.BaseUri" class="flex mr-14 flex-shrink-0">
|
||||
<img src="/img/logo.svg" class="mr-3 h-8" alt="AliasVault Logo">
|
||||
<span class="self-center hidden sm:flex text-2xl font-semibold whitespace-nowrap dark:text-white">AliasVault</span>
|
||||
<span class="ps-2 self-center hidden sm:flex text-sm font-bold whitespace-nowrap text-white bg-red-600 rounded-full px-2 py-1 ml-2">Admin</span>
|
||||
@@ -13,16 +13,16 @@
|
||||
|
||||
<div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1">
|
||||
<ul class="flex flex-col mt-4 space-x-6 text-sm font-medium lg:flex-row xl:space-x-8 lg:mt-0">
|
||||
<NavLink href="/users" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="users" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Users
|
||||
</NavLink>
|
||||
<NavLink href="/emails" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="emails" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Emails
|
||||
</NavLink>
|
||||
<NavLink href="/logging/general" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="logging/general" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
General logs
|
||||
</NavLink>
|
||||
<NavLink href="/logging/auth" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="logging/auth" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Auth logs
|
||||
</NavLink>
|
||||
</ul>
|
||||
@@ -57,7 +57,7 @@
|
||||
</ul>
|
||||
<ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="dropdown">
|
||||
<li>
|
||||
<a href="/user/logout" class="block py-2 px-4 font-bold text-sm text-primary-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-primary-200 dark:hover:text-white">Sign out</a>
|
||||
<a href="user/logout" class="block py-2 px-4 font-bold text-sm text-primary-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-primary-200 dark:hover:text-white">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -75,27 +75,27 @@
|
||||
<nav class="bg-white dark:bg-gray-900">
|
||||
<ul id="mobileMenu" class="flex-col mt-0 pt-16 w-full text-sm font-medium lg:hidden">
|
||||
<li class="block border-b dark:border-gray-700">
|
||||
<NavLink href="/" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="./" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="block border-b dark:border-gray-700">
|
||||
<NavLink href="/users" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="users" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Users
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="block border-b dark:border-gray-700">
|
||||
<NavLink href="/emails" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="emails" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Emails
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="block border-b dark:border-gray-700">
|
||||
<NavLink href="/logging/general" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="logging/general" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
General logs
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="block border-b dark:border-gray-700">
|
||||
<NavLink href="/logging/auth" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
<NavLink href="logging/auth" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
Auth logs
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
base.OnInitialized();
|
||||
|
||||
// Redirect to users page.
|
||||
NavigationService.RedirectTo("/users");
|
||||
NavigationService.RedirectTo("users");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +89,11 @@ else
|
||||
GlobalNotificationService.AddSuccessMessage("User successfully deleted.");
|
||||
GlobalLoadingSpinner.Hide();
|
||||
|
||||
NavigationService.RedirectTo("/users");
|
||||
NavigationService.RedirectTo("users");
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationService.RedirectTo("/users/" + Id);
|
||||
NavigationService.RedirectTo("users/" + Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ else
|
||||
Description="View details of the user below.">
|
||||
<CustomActions>
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
<LinkButton Color="danger" Href="@($"/users/{Id}/delete")" Text="Delete user" />
|
||||
<LinkButton Color="danger" Href="@($"users/{Id}/delete")" Text="Delete user" />
|
||||
</CustomActions>
|
||||
</PageHeader>
|
||||
|
||||
@@ -133,7 +133,7 @@ else
|
||||
{
|
||||
// Error loading user.
|
||||
GlobalNotificationService.AddErrorMessage("This user does not exist (anymore). Please try again.");
|
||||
NavigationService.RedirectTo("/users");
|
||||
NavigationService.RedirectTo("users");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ using AliasVault.Cryptography.Server;
|
||||
using AliasVault.Logging;
|
||||
using AliasVault.RazorComponents.Services;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -32,7 +33,7 @@ var adminPasswordHash = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_HASH"
|
||||
config.AdminPasswordHash = adminPasswordHash;
|
||||
|
||||
var lastPasswordChanged = Environment.GetEnvironmentVariable("ADMIN_PASSWORD_GENERATED") ?? throw new KeyNotFoundException("ADMIN_PASSWORD_GENERATED environment variable is not set.");
|
||||
config.LastPasswordChanged = DateTime.ParseExact(lastPasswordChanged, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
|
||||
config.LastPasswordChanged = DateTime.Parse(lastPasswordChanged, CultureInfo.InvariantCulture);
|
||||
|
||||
builder.Services.AddSingleton(config);
|
||||
|
||||
@@ -85,7 +86,6 @@ builder.Services.AddIdentityCore<AdminUser>(options =>
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.AddAliasVaultDataProtection("AliasVault.Admin");
|
||||
|
||||
builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
|
||||
{
|
||||
options.TokenLifespan = TimeSpan.FromDays(30);
|
||||
@@ -105,8 +105,24 @@ else
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
// If the ASPNETCORE_PATHBASE environment variable is set, use it as the path base for the application.
|
||||
// This is required for running the admin interface behind a reverse proxy on the same port as the client app.
|
||||
// E.g. default Docker Compose setup makes admin app available on /admin path.
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_PATHBASE")))
|
||||
{
|
||||
app.UsePathBase(Environment.GetEnvironmentVariable("ASPNETCORE_PATHBASE"));
|
||||
}
|
||||
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedProto,
|
||||
});
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
app.UseAntiforgery();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
@@ -72,7 +72,6 @@ public static class StartupTasks
|
||||
|
||||
// Clear existing recovery codes
|
||||
await userManager.GenerateNewTwoFactorRecoveryCodesAsync(adminUser, 0);
|
||||
|
||||
await userManager.UpdateAsync(adminUser);
|
||||
|
||||
Console.WriteLine("Admin password hash updated.");
|
||||
|
||||
29
src/AliasVault.Admin/entrypoint.sh
Normal file
29
src/AliasVault.Admin/entrypoint.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Create SSL directory if it doesn't exist
|
||||
mkdir -p /app/ssl
|
||||
|
||||
# Generate self-signed SSL certificate if not exists
|
||||
if [ ! -f /app/ssl/admin.crt ] || [ ! -f /app/ssl/admin.key ]; then
|
||||
echo "Generating new SSL certificate..."
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /app/ssl/admin.key \
|
||||
-out /app/ssl/admin.crt \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 /app/ssl/admin.crt
|
||||
chmod 600 /app/ssl/admin.key
|
||||
|
||||
# Create PFX for ASP.NET Core
|
||||
openssl pkcs12 -export -out /app/ssl/admin.pfx \
|
||||
-inkey /app/ssl/admin.key \
|
||||
-in /app/ssl/admin.crt \
|
||||
-password pass:YourSecurePassword
|
||||
fi
|
||||
|
||||
export ASPNETCORE_Kestrel__Certificates__Default__Path=/app/ssl/admin.pfx
|
||||
export ASPNETCORE_Kestrel__Certificates__Default__Password=YourSecurePassword
|
||||
|
||||
# Start the application
|
||||
dotnet AliasVault.Admin.dll
|
||||
6
src/AliasVault.Admin/package-lock.json
generated
6
src/AliasVault.Admin/package-lock.json
generated
@@ -357,9 +357,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
||||
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>AliasVault.Api</RootNamespace>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<DefineConstants Condition="'$(E2ETEST)' == 'true'">$(DefineConstants);E2ETEST</DefineConstants>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
@@ -21,14 +22,14 @@
|
||||
<ItemGroup>
|
||||
<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="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
/// Base controller that concrete controllers can extend from if all requests require authentication.
|
||||
/// </summary>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Route("v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public abstract class AuthenticatedRequestController(UserManager<AliasVaultUser> userManager) : ControllerBase
|
||||
|
||||
@@ -39,7 +39,7 @@ using SecureRemotePassword;
|
||||
/// <param name="cache">IMemoryCache instance for persisting SRP values during multistep login process.</param>
|
||||
/// <param name="timeProvider">ITimeProvider instance. This returns the time which can be mutated for testing.</param>
|
||||
/// <param name="authLoggingService">AuthLoggingService instance. This is used to log auth attempts to the database.</param>
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Route("v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFactory, UserManager<AliasVaultUser> userManager, SignInManager<AliasVaultUser> signInManager, IConfiguration configuration, IMemoryCache cache, ITimeProvider timeProvider, AuthLoggingService authLoggingService) : ControllerBase
|
||||
|
||||
@@ -44,6 +44,7 @@ public class EmailController(ILogger<VaultController> logger, IDbContextFactory<
|
||||
{
|
||||
Id = email!.Id,
|
||||
Subject = email.Subject,
|
||||
FromDisplay = email.From,
|
||||
FromDomain = email.FromDomain,
|
||||
FromLocal = email.FromLocal,
|
||||
ToDomain = email.ToDomain,
|
||||
|
||||
@@ -18,8 +18,9 @@ using Microsoft.AspNetCore.Mvc;
|
||||
/// Controller for retrieving favicons from external websites.
|
||||
/// </summary>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
[ApiVersion("1")]
|
||||
public class FaviconController(UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
public class FaviconController(UserManager<AliasVaultUser> userManager, ILogger<FaviconController> logger) : AuthenticatedRequestController(userManager)
|
||||
{
|
||||
/// <summary>
|
||||
/// Proxies the request to the identity generator to generate a random identity.
|
||||
@@ -36,9 +37,22 @@ public class FaviconController(UserManager<AliasVaultUser> userManager) : Authen
|
||||
}
|
||||
|
||||
// Get the favicon from the URL.
|
||||
var image = await FaviconExtractor.FaviconExtractor.GetFaviconAsync(url);
|
||||
try
|
||||
{
|
||||
var image = await FaviconExtractor.FaviconExtractor.GetFaviconAsync(url);
|
||||
|
||||
// Return the favicon as base64 string of image representation.
|
||||
return Ok(new FaviconExtractModel { Image = image });
|
||||
// Return the favicon as base64 string of image representation.
|
||||
return Ok(new FaviconExtractModel { Image = image });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Anonymize the URL by replacing all a-Z characters with 'x' before logging.
|
||||
// This will still allow to see the host structure but not the actual domain.
|
||||
var anonymizedUrl = new string(url.Select(c => char.IsLetter(c) ? 'x' : c).ToArray());
|
||||
logger.LogInformation(ex, "Failed to extract favicon from {Url}", anonymizedUrl);
|
||||
}
|
||||
|
||||
// Return null if favicon extraction failed.
|
||||
return Ok(new FaviconExtractModel { Image = null });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">AliasServerDbContext instance.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Route("v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class SecurityController(IDbContextFactory<AliasServerDbContext> dbContextFactory, UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
|
||||
@@ -24,7 +24,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// <param name="urlEncoder">UrlEncoder instance.</param>
|
||||
/// <param name="authLoggingService">AuthLoggingService instance. This is used to log auth attempts to the database.</param>
|
||||
/// <param name="userManager">UserManager instance.</param>
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Route("v{version:apiVersion}/[controller]")]
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
public class TwoFactorAuthController(IDbContextFactory<AliasServerDbContext> dbContextFactory, UrlEncoder urlEncoder, AuthLoggingService authLoggingService, UserManager<AliasVaultUser> userManager) : AuthenticatedRequestController(userManager)
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 8081
|
||||
EXPOSE 3001
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the project files and restore dependencies
|
||||
COPY ["src/AliasVault.Api/AliasVault.Api.csproj", "src/AliasVault.Api/"]
|
||||
RUN dotnet restore "src/AliasVault.Api/AliasVault.Api.csproj"
|
||||
COPY . .
|
||||
|
||||
# Build the WebApi project
|
||||
WORKDIR "/src/src/AliasVault.Api"
|
||||
RUN dotnet build "AliasVault.Api.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.Api.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
|
||||
@@ -24,8 +20,7 @@ RUN dotnet publish "AliasVault.Api.csproj" -c "$BUILD_CONFIGURATION" -o /app/pub
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY /src/AliasVault.Api/entrypoint.sh /app
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
EXPOSE 8081
|
||||
ENV ASPNETCORE_URLS=http://+:8081
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
ENV ASPNETCORE_URLS=http://+:3001
|
||||
ENV ASPNETCORE_PATHBASE=/api
|
||||
ENTRYPOINT ["dotnet", "AliasVault.Api.dll"]
|
||||
|
||||
@@ -157,6 +157,12 @@ if (app.Environment.IsDevelopment())
|
||||
|
||||
app.UseCors("CorsPolicy");
|
||||
|
||||
// If the ASPNETCORE_PATHBASE environment variable is set, use it as the path base
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_PATHBASE")))
|
||||
{
|
||||
app.UsePathBase(Environment.GetEnvironmentVariable("ASPNETCORE_PATHBASE"));
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start the application
|
||||
echo "Starting application..."
|
||||
dotnet /app/AliasVault.Api.dll
|
||||
@@ -1,23 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>AliasVault.Client</RootNamespace>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<BuildVersion>$([System.DateTime]::UtcNow.ToString("yyyy-MM-dd HH:mm:ss"))</BuildVersion>
|
||||
<WasmBuildNative>true</WasmBuildNative>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Client.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Client.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CacheBuster>dev</CacheBuster>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Client.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Client.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Optimize>True</Optimize>
|
||||
<CacheBuster>$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</CacheBuster>
|
||||
@@ -48,15 +49,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Auth.Layout.MainLayout)">
|
||||
<p>Sorry, there's nothing at this address.</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
|
||||
@@ -207,7 +207,7 @@ else
|
||||
var username = _loginModel.Username.ToLowerInvariant().Trim();
|
||||
|
||||
// Send request to server with username to get server ephemeral public key.
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/login", new LoginInitiateRequest(username));
|
||||
var result = await Http.PostAsJsonAsync("v1/Auth/login", new LoginInitiateRequest(username));
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@@ -238,7 +238,7 @@ else
|
||||
username);
|
||||
|
||||
// 4. Client sends proof of session key to server.
|
||||
result = await Http.PostAsJsonAsync("api/v1/Auth/validate", new ValidateLoginRequest(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof));
|
||||
result = await Http.PostAsJsonAsync("v1/Auth/validate", new ValidateLoginRequest(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof));
|
||||
responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@@ -280,7 +280,7 @@ else
|
||||
var username = _loginModel.Username.ToLowerInvariant().Trim();
|
||||
|
||||
// Validate 2-factor auth code auth and login
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/validate-recovery-code", new ValidateLoginRequestRecoveryCode(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof, _loginModelRecoveryCode.RecoveryCode));
|
||||
var result = await Http.PostAsJsonAsync("v1/Auth/validate-recovery-code", new ValidateLoginRequestRecoveryCode(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof, _loginModelRecoveryCode.RecoveryCode));
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@@ -338,7 +338,7 @@ else
|
||||
var username = _loginModel.Username.ToLowerInvariant().Trim();
|
||||
|
||||
// Validate 2-factor auth code auth and login
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/validate-2fa", new ValidateLoginRequest2Fa(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof, _loginModel2Fa.TwoFactorCode ?? 0));
|
||||
var result = await Http.PostAsJsonAsync("v1/Auth/validate-2fa", new ValidateLoginRequest2Fa(username, _loginModel.RememberMe, _clientEphemeral.Public, _clientSession.Proof, _loginModel2Fa.TwoFactorCode ?? 0));
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
var response = await Http.PostAsJsonAsync("api/v1/Auth/validate-username", new { Username });
|
||||
var response = await Http.PostAsJsonAsync("v1/Auth/validate-username", new { Username });
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
|
||||
@@ -123,7 +123,7 @@ else
|
||||
await StatusCheck();
|
||||
|
||||
// Send request to server with email to get user salt.
|
||||
var result = await Http.PostAsJsonAsync("api/v1/Auth/login", new LoginInitiateRequest(Username!));
|
||||
var result = await Http.PostAsJsonAsync("v1/Auth/login", new LoginInitiateRequest(Username!));
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
@@ -234,7 +234,7 @@ else
|
||||
// If user has no valid authentication an automatic redirect to login page will take place.
|
||||
try
|
||||
{
|
||||
await Http.GetAsync("api/v1/Auth/status");
|
||||
await Http.GetAsync("v1/Auth/status");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
# Add environment variable for opting out of telemetry which fixes
|
||||
# "error MSB4166: Child node "8" exited prematurely." issues.
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
ENV MSBUILDDEBUGPATH=/src/msbuild-logs
|
||||
WORKDIR /src
|
||||
|
||||
# Install Python which is required by the WebAssembly tools
|
||||
RUN apt-get update && apt-get install -y python3 && apt-get clean
|
||||
# Create the debug directory and install Python which is required by the WebAssembly tools
|
||||
RUN mkdir -p /src/msbuild-logs && apt-get update && apt-get install -y python3 && apt-get clean
|
||||
|
||||
# Install the WebAssembly tools
|
||||
RUN dotnet workload install wasm-tools
|
||||
@@ -30,12 +29,13 @@ RUN dotnet publish "AliasVault.Client.csproj" -c "$BUILD_CONFIGURATION" -o /app/
|
||||
|
||||
# Final stage
|
||||
FROM nginx:1.24.0 AS final
|
||||
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=publish /app/publish/wwwroot .
|
||||
COPY /src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY /src/AliasVault.Client/entrypoint.sh /app/
|
||||
COPY /src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
EXPOSE 8080
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
|
||||
EXPOSE 3000
|
||||
ENV ASPNETCORE_URLS=http://+:3000
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">From: @Email?.FromDisplay</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">From: @(Email?.FromLocal)@@@(Email?.FromDomain)</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">To: @(Email?.ToLocal)@@@(Email?.ToDomain)</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Date: @Email?.DateSystem</p>
|
||||
</div>
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
try
|
||||
{
|
||||
var response = await HttpClient.DeleteAsync($"api/v1/Email/{Email.Id}");
|
||||
var response = await HttpClient.DeleteAsync($"v1/Email/{Email.Id}");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage("Email deleted successfully", true);
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
/// </summary>
|
||||
private async Task LoadAliasVaultEmails()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/EmailBox/{EmailAddress}");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, $"v1/EmailBox/{EmailAddress}");
|
||||
try
|
||||
{
|
||||
var response = await HttpClient.SendAsync(request);
|
||||
@@ -351,7 +351,7 @@
|
||||
/// </summary>
|
||||
private async Task ShowAliasVaultEmailInModal(int emailId)
|
||||
{
|
||||
EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"api/v1/Email/{emailId}");
|
||||
EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
|
||||
if (mail != null)
|
||||
{
|
||||
// Decrypt the email content locally.
|
||||
|
||||
@@ -11,12 +11,21 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Title tag text of the loading indicator.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the loading indicator is spinning.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool Spinning { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The content to display inside the loading indicator.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ else
|
||||
Addresses = emailClaimList,
|
||||
};
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/EmailBox/bulk");
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"v1/EmailBox/bulk");
|
||||
request.Content = new StringContent(JsonSerializer.Serialize(requestModel), Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
@@ -245,7 +245,7 @@ else
|
||||
/// </summary>
|
||||
private async Task ShowAliasVaultEmailInModal(int emailId)
|
||||
{
|
||||
EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"api/v1/Email/{emailId}");
|
||||
EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
|
||||
if (mail != null)
|
||||
{
|
||||
// Decrypt the email content locally.
|
||||
|
||||
@@ -108,7 +108,7 @@ else
|
||||
if (firstRender)
|
||||
{
|
||||
// Get the QR code and secret for the authenticator app.
|
||||
var response = await Http.GetFromJsonAsync<PasswordChangeInitiateResponse>("api/v1/Auth/change-password/initiate");
|
||||
var response = await Http.GetFromJsonAsync<PasswordChangeInitiateResponse>("v1/Auth/change-password/initiate");
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
@@ -191,7 +191,7 @@ else
|
||||
PasswordChangeModel = new PasswordChangeModel();
|
||||
|
||||
// 4. Client sends proof of session key to server.
|
||||
var response = await Http.PostAsJsonAsync("api/v1/Vault/change-password", vaultPasswordChangeObject);
|
||||
var response = await Http.PostAsJsonAsync("v1/Vault/change-password", vaultPasswordChangeObject);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
var sessionsResponse = await Http.GetFromJsonAsync<List<RefreshTokenModel>>("api/v1/Security/sessions");
|
||||
var sessionsResponse = await Http.GetFromJsonAsync<List<RefreshTokenModel>>("v1/Security/sessions");
|
||||
if (sessionsResponse is not null)
|
||||
{
|
||||
Sessions = sessionsResponse;
|
||||
@@ -89,7 +89,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await Http.DeleteAsync($"api/v1/Security/sessions/{id}");
|
||||
var response = await Http.DeleteAsync($"v1/Security/sessions/{id}");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage("Session revoked successfully.", true);
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
var authlogResponse = await Http.GetFromJsonAsync<List<AuthLogModel>>("api/v1/Security/authlogs");
|
||||
var authlogResponse = await Http.GetFromJsonAsync<List<AuthLogModel>>("v1/Security/authlogs");
|
||||
if (authlogResponse is not null)
|
||||
{
|
||||
AuthLogs = authlogResponse;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
var twoFactorResponse = await Http.GetFromJsonAsync<TwoFactorEnabledResult>("api/v1/TwoFactorAuth/status");
|
||||
var twoFactorResponse = await Http.GetFromJsonAsync<TwoFactorEnabledResult>("v1/TwoFactorAuth/status");
|
||||
if (twoFactorResponse is not null)
|
||||
{
|
||||
TwoFactorEnabled = twoFactorResponse.TwoFactorEnabled;
|
||||
|
||||
@@ -48,7 +48,7 @@ else
|
||||
// Check on server if 2FA is enabled
|
||||
if (firstRender)
|
||||
{
|
||||
var response = await Http.GetFromJsonAsync<TwoFactorEnabledResult>("api/v1/TwoFactorAuth/status");
|
||||
var response = await Http.GetFromJsonAsync<TwoFactorEnabledResult>("v1/TwoFactorAuth/status");
|
||||
if (response is not null && !response.TwoFactorEnabled)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage("Two-factor authentication is not enabled.");
|
||||
@@ -63,7 +63,7 @@ else
|
||||
|
||||
private async Task DisableTwoFactor()
|
||||
{
|
||||
var response = await Http.PostAsync("api/v1/TwoFactorAuth/disable", null);
|
||||
var response = await Http.PostAsync("v1/TwoFactorAuth/disable", null);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage("Two-factor authentication is now successfully disabled.");
|
||||
|
||||
@@ -74,7 +74,7 @@ else
|
||||
if (firstRender)
|
||||
{
|
||||
// Get the QR code and secret for the authenticator app.
|
||||
var response = await Http.PostAsync("api/v1/TwoFactorAuth/enable", null);
|
||||
var response = await Http.PostAsync("v1/TwoFactorAuth/enable", null);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadFromJsonAsync<TwoFactorSetupResult>();
|
||||
@@ -100,7 +100,7 @@ else
|
||||
|
||||
private async Task VerifySetup()
|
||||
{
|
||||
var response = await Http.PostAsJsonAsync("api/v1/TwoFactorAuth/verify", VerifyModel.Code);
|
||||
var response = await Http.PostAsJsonAsync("v1/TwoFactorAuth/verify", VerifyModel.Code);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadFromJsonAsync<TwoFactorVerifyResult>();
|
||||
|
||||
@@ -35,7 +35,7 @@ else
|
||||
|
||||
private async Task MakeWebApiCall()
|
||||
{
|
||||
await HttpClient.GetAsync("api/v1/Test");
|
||||
await HttpClient.GetAsync("v1/Test");
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
|
||||
@@ -35,7 +35,7 @@ else
|
||||
|
||||
private async Task MakeWebApiCall()
|
||||
{
|
||||
await HttpClient.GetAsync("api/v1/Test");
|
||||
await HttpClient.GetAsync("v1/Test");
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
|
||||
@@ -54,12 +54,11 @@ builder.Services.AddScoped(sp =>
|
||||
{
|
||||
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
|
||||
var httpClient = httpClientFactory.CreateClient("AliasVault.Api");
|
||||
if (builder.Configuration["ApiUrl"] is null)
|
||||
{
|
||||
throw new InvalidOperationException("The 'ApiUrl' configuration value is required.");
|
||||
}
|
||||
var apiConfig = sp.GetRequiredService<Config>();
|
||||
|
||||
httpClient.BaseAddress = new Uri(builder.Configuration["ApiUrl"]!);
|
||||
// Ensure the API URL ends with a forward slash
|
||||
var baseUrl = apiConfig.ApiUrl.TrimEnd('/') + "/";
|
||||
httpClient.BaseAddress = new Uri(baseUrl);
|
||||
return httpClient;
|
||||
});
|
||||
builder.Services.AddTransient<AliasVaultApiHandlerService>();
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
|
||||
var accessToken = await GetAccessTokenAsync();
|
||||
var refreshToken = await GetRefreshTokenAsync();
|
||||
var tokenInput = new TokenModel { Token = accessToken, RefreshToken = refreshToken };
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/Auth/refresh")
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "v1/Auth/refresh")
|
||||
{
|
||||
Content = JsonContent.Create(tokenInput),
|
||||
};
|
||||
@@ -319,7 +319,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
|
||||
RefreshToken = await GetRefreshTokenAsync(),
|
||||
};
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/Auth/revoke")
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "v1/Auth/revoke")
|
||||
{
|
||||
Content = JsonContent.Create(tokenInput),
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ public class UserRegistrationService(HttpClient httpClient, AuthenticationStateP
|
||||
var srpSignup = Srp.PasswordChangeAsync(client, salt, username, passwordHashString);
|
||||
|
||||
var registerRequest = new RegisterRequest(srpSignup.Username, srpSignup.Salt, srpSignup.Verifier, encryptionType, encryptionSettings);
|
||||
var result = await httpClient.PostAsJsonAsync("api/v1/Auth/register", registerRequest);
|
||||
var result = await httpClient.PostAsJsonAsync("v1/Auth/register", registerRequest);
|
||||
var responseContent = await result.Content.ReadAsStringAsync();
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
|
||||
@@ -375,7 +375,7 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
|
||||
try
|
||||
{
|
||||
var apiReturn =
|
||||
await httpClient.GetFromJsonAsync<FaviconExtractModel>($"api/v1/Favicon/Extract?url={url}");
|
||||
await httpClient.GetFromJsonAsync<FaviconExtractModel>($"v1/Favicon/Extract?url={url}");
|
||||
if (apiReturn?.Image is not null)
|
||||
{
|
||||
credentialObject.Service.Logo = apiReturn.Image;
|
||||
|
||||
@@ -93,7 +93,7 @@ public static class DbMergeUtility
|
||||
// Record exists, compare UpdatedAt if it exists.
|
||||
logger.LogDebug("Comparing UpdatedAt in {tableName}.", tableName);
|
||||
logger.LogDebug("UpdatedAt: {existingRecord}", existingRecord);
|
||||
var baseUpdatedAt = DateTime.Parse((string)existingRecord, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
|
||||
var baseUpdatedAt = DateTime.Parse((string)existingRecord, CultureInfo.InvariantCulture);
|
||||
if (updatedAt > baseUpdatedAt)
|
||||
{
|
||||
// Source record is newer, update the base record.
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class DbService : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var vaultsToMerge = await _httpClient.GetFromJsonAsync<VaultMergeResponse>($"api/v1/Vault/merge?currentRevisionNumber={_vaultRevisionNumber}");
|
||||
var vaultsToMerge = await _httpClient.GetFromJsonAsync<VaultMergeResponse>($"v1/Vault/merge?currentRevisionNumber={_vaultRevisionNumber}");
|
||||
if (vaultsToMerge == null || vaultsToMerge.Vaults.Count == 0)
|
||||
{
|
||||
// No vaults to merge found, set error state.
|
||||
@@ -558,7 +558,7 @@ public sealed class DbService : IDisposable
|
||||
// Load from webapi.
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetFromJsonAsync<VaultGetResponse>("api/v1/Vault");
|
||||
var response = await _httpClient.GetFromJsonAsync<VaultGetResponse>("v1/Vault");
|
||||
if (response is not null)
|
||||
{
|
||||
if (response.Status == VaultStatus.MergeRequired)
|
||||
@@ -621,7 +621,7 @@ public sealed class DbService : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("api/v1/Vault", vaultObject);
|
||||
var response = await _httpClient.PostAsJsonAsync("v1/Vault", vaultObject);
|
||||
|
||||
// Ensure the request was successful
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Set the default API URL for localhost debugging
|
||||
DEFAULT_API_URL="http://localhost:81"
|
||||
# Set the default hostname for localhost debugging
|
||||
DEFAULT_HOSTNAME="localhost"
|
||||
DEFAULT_PRIVATE_EMAIL_DOMAINS="localmail.tld"
|
||||
DEFAULT_SUPPORT_EMAIL=""
|
||||
|
||||
# Use the provided API_URL environment variable if it exists, otherwise use the default
|
||||
API_URL=${API_URL:-$DEFAULT_API_URL}
|
||||
# Use the provided HOSTNAME environment variable if it exists, otherwise use the default
|
||||
HOSTNAME=${HOSTNAME:-$DEFAULT_HOSTNAME}
|
||||
PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}
|
||||
SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}
|
||||
|
||||
# Replace the default URL with the actual API URL
|
||||
sed -i "s|http://localhost:5092|${API_URL}|g" /usr/share/nginx/html/appsettings.json
|
||||
# Replace the default SMTP allowed domains with the actual allowed SMTP domains
|
||||
# Note: this is used so the client knows which email addresses should be registered with the AliasVault server
|
||||
# in order to be able to receive emails.
|
||||
# Create SSL directory if it doesn't exist
|
||||
mkdir -p /etc/nginx/ssl
|
||||
|
||||
# Generate self-signed SSL certificate if not exists
|
||||
if [ ! -f /etc/nginx/ssl/nginx.crt ] || [ ! -f /etc/nginx/ssl/nginx.key ]; then
|
||||
echo "Generating new SSL certificate..."
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /etc/nginx/ssl/nginx.key \
|
||||
-out /etc/nginx/ssl/nginx.crt \
|
||||
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 /etc/nginx/ssl/nginx.crt
|
||||
chmod 600 /etc/nginx/ssl/nginx.key
|
||||
fi
|
||||
|
||||
# Replace the default URL with the actual API URL constructed from hostname
|
||||
sed -i "s|http://localhost:5092|https://${HOSTNAME}/api|g" /usr/share/nginx/html/appsettings.json
|
||||
|
||||
# Convert comma-separated list to JSON array
|
||||
json_array=$(echo $PRIVATE_EMAIL_DOMAINS | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
||||
|
||||
@@ -16,7 +16,7 @@ http {
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
|
||||
6
src/AliasVault.Client/package-lock.json
generated
6
src/AliasVault.Client/package-lock.json
generated
@@ -357,9 +357,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
||||
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
||||
@@ -638,18 +638,6 @@ video {
|
||||
bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.-right-12 {
|
||||
right: -3rem;
|
||||
}
|
||||
|
||||
.-right-2 {
|
||||
right: -0.5rem;
|
||||
}
|
||||
|
||||
.-top-1 {
|
||||
top: -0.25rem;
|
||||
}
|
||||
|
||||
.-top-2 {
|
||||
top: -0.5rem;
|
||||
}
|
||||
@@ -690,22 +678,6 @@ video {
|
||||
top: 5rem;
|
||||
}
|
||||
|
||||
.-right-10 {
|
||||
right: -2.5rem;
|
||||
}
|
||||
|
||||
.-right-8 {
|
||||
right: -2rem;
|
||||
}
|
||||
|
||||
.right-2 {
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.right-4 {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -845,10 +817,6 @@ video {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.mt-12 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@@ -1040,10 +1008,6 @@ video {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.min-w-\[3rem\] {
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
.max-w-7xl {
|
||||
max-width: 80rem;
|
||||
}
|
||||
@@ -1068,11 +1032,6 @@ video {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.translate-x-2 {
|
||||
--tw-translate-x: 0.5rem;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.transform {
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
@@ -1594,16 +1553,6 @@ video {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.px-1 {
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.px-1\.5 {
|
||||
padding-left: 0.375rem;
|
||||
padding-right: 0.375rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@@ -1718,10 +1667,6 @@ video {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.pe-14 {
|
||||
padding-inline-end: 3.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -1766,6 +1711,10 @@ video {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-\[10px\] {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
@@ -1791,18 +1740,6 @@ video {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.text-\[10px\] {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.text-\[11px\] {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasClientDb.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasClientDb.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DocumentationFile>bin\Release\net8.0\AliasClientDb.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasClientDb.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
@@ -16,20 +17,20 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -248,6 +248,19 @@ public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyCon
|
||||
optionsBuilder
|
||||
.UseSqlite(configuration.GetConnectionString("AliasServerDbContext"))
|
||||
.UseLazyLoadingProxies();
|
||||
|
||||
// Set busy timeout using PRAGMA to avoid "The database file is locked" error.
|
||||
var connection = Database.GetDbConnection();
|
||||
if (connection.State != System.Data.ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "PRAGMA busy_timeout = 5000;";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Generators.Identity.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Generators.Identity.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Generators.Identity.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Generators.Identity.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Generators.Password.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Generators.Password.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Generators.Password.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Generators.Password.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>dotnet-AliasVault.SmtpService-eaac287e-32a7-4ff9-bbf9-1925c446ef73</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>..\..\..</DockerfileContext>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.SmtpService.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.SmtpService.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.SmtpService.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.SmtpService.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,7 +25,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||
<PackageReference Include="MimeKit" Version="4.8.0" />
|
||||
<PackageReference Include="NUglify" Version="1.21.10" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
|
||||
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
|
||||
@@ -97,9 +97,8 @@ builder.Services.AddSingleton(
|
||||
// If we don't do this, the certificate will be loaded without the private key and
|
||||
// will throw error on Windows:
|
||||
// "The TLS server credential's certificate does not have a private key information property attached to it"
|
||||
cert = new X509Certificate2(cert.Export(X509ContentType.Pfx));
|
||||
|
||||
return cert;
|
||||
var certBytes = cert.Export(X509ContentType.Pfx, "password");
|
||||
return X509CertificateLoader.LoadPkcs12(certBytes, "password", X509KeyStorageFlags.DefaultKeySet);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.RazorComponents.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.RazorComponents.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.RazorComponents.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.RazorComponents.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -22,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -27,11 +27,13 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <inheritdoc/>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
ModalService.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
ModalService.OnChange -= StateHasChanged;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<nav class="flex mb-4">
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<nav class="flex mb-4">
|
||||
<ol class="inline-flex items-center space-x-1 text-sm font-medium md:space-x-2">
|
||||
<li class="inline-flex items-center">
|
||||
<a href="/" class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-primary-500">
|
||||
<a href="@NavigationManager.BaseUri" class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-primary-500">
|
||||
<svg class="w-5 h-5 mr-2.5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg>
|
||||
Home
|
||||
</a>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Shared.Core.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Shared.Core.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Shared.Core.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Shared.Core.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public static class AppInfo
|
||||
/// <summary>
|
||||
/// Gets the minor version number.
|
||||
/// </summary>
|
||||
public const int VersionMinor = 6;
|
||||
public const int VersionMinor = 7;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the patch version number.
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.Shared.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.Shared.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.Shared.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.Shared.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -21,7 +22,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.70" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.71" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.E2ETests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.E2ETests.xml</DocumentationFile>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.E2ETests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.E2ETests.xml</DocumentationFile>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -27,11 +28,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="NUnit" Version="4.2.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -14,8 +14,8 @@ using AliasServerDb;
|
||||
/// </summary>
|
||||
public class AdminPlaywrightTest : PlaywrightTest
|
||||
{
|
||||
private static readonly int _basePort = 5700;
|
||||
private static int _currentPort = _basePort;
|
||||
private const int BasePort = 5700;
|
||||
private static int _currentPort = BasePort;
|
||||
|
||||
/// <summary>
|
||||
/// For starting the Admin project in-memory.
|
||||
@@ -58,7 +58,7 @@ public class AdminPlaywrightTest : PlaywrightTest
|
||||
AppBaseUrl = "http://localhost:" + appPort + "/";
|
||||
|
||||
// Start Admin project in-memory.
|
||||
_webAppFactory.HostUrl = "http://localhost:" + appPort;
|
||||
_webAppFactory.Port = appPort;
|
||||
_webAppFactory.CreateDefaultClient();
|
||||
|
||||
await SetupPlaywrightBrowserAndContext();
|
||||
|
||||
@@ -16,8 +16,8 @@ using Microsoft.Playwright;
|
||||
/// </summary>
|
||||
public class ClientPlaywrightTest : PlaywrightTest
|
||||
{
|
||||
private static readonly int _basePort = 5600;
|
||||
private static int _currentPort = _basePort;
|
||||
private const int BasePort = 5600;
|
||||
private static int _currentPort = BasePort;
|
||||
|
||||
/// <summary>
|
||||
/// For starting the WebAPI project in-memory.
|
||||
@@ -85,11 +85,11 @@ public class ClientPlaywrightTest : PlaywrightTest
|
||||
ApiBaseUrl = "http://localhost:" + apiPort + "/";
|
||||
|
||||
// Start WebAPI in-memory.
|
||||
_apiFactory.HostUrl = "http://localhost:" + apiPort;
|
||||
_apiFactory.Port = apiPort;
|
||||
_apiFactory.CreateDefaultClient();
|
||||
|
||||
// Start Blazor WASM in-memory.
|
||||
_clientFactory.HostUrl = "http://localhost:" + appPort;
|
||||
_clientFactory.Port = appPort;
|
||||
_clientFactory.CreateDefaultClient();
|
||||
|
||||
await SetupPlaywrightBrowserAndContext();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="KestrelTestServer.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.E2ETests.Infrastructure;
|
||||
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="TestServer"/> that uses Kestrel as the server.
|
||||
/// </summary>
|
||||
public class KestrelTestServer : TestServer, IServer
|
||||
{
|
||||
private readonly KestrelServer _server;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KestrelTestServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use.</param>
|
||||
public KestrelTestServer(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
// We get all the transport factories registered, and the first one is the correct one
|
||||
// Getting the IConnectionListenerFactory directly from the service provider does not work
|
||||
var transportFactory = serviceProvider.GetRequiredService<IEnumerable<IConnectionListenerFactory>>().First();
|
||||
|
||||
var kestrelOptions = serviceProvider.GetRequiredService<IOptions<KestrelServerOptions>>();
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
_server = new KestrelServer(kestrelOptions, transportFactory, loggerFactory);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task IServer.StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||
{
|
||||
// We need to also invoke the TestServer's StartAsync method to ensure that the test server is started
|
||||
// Because the TestServer's StartAsync method is implemented explicitly, we need to use reflection to invoke it
|
||||
await InvokeExplicitInterfaceMethod(nameof(IServer.StartAsync), typeof(TContext), [application, cancellationToken]);
|
||||
|
||||
// We also start the Kestrel server in order for localhost to work
|
||||
await _server.StartAsync(application, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task IServer.StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await InvokeExplicitInterfaceMethod(nameof(IServer.StopAsync), null, [cancellationToken]);
|
||||
await _server.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private Task InvokeExplicitInterfaceMethod(string methodName, Type? genericParameter, object[] args)
|
||||
{
|
||||
var baseMethod = typeof(TestServer).GetInterfaceMap(typeof(IServer)).TargetMethods.First(m => m.Name.EndsWith(methodName));
|
||||
var method = genericParameter == null ? baseMethod : baseMethod.MakeGenericMethod(genericParameter);
|
||||
var task = method.Invoke(this, args) as Task ?? throw new InvalidOperationException("Task not returned");
|
||||
return task;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using System.Data.Common;
|
||||
using AliasServerDb;
|
||||
using AliasVault.Admin.Services;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -49,9 +50,9 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL the web application host will listen on.
|
||||
/// Gets or sets the port the web application kestrel host will listen on.
|
||||
/// </summary>
|
||||
public string HostUrl { get; set; } = "https://localhost:5003";
|
||||
public int Port { get; set; } = 5003;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the DbContext instance for the test. This can be used to seed the database with test data.
|
||||
@@ -82,28 +83,23 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
|
||||
/// <inheritdoc />
|
||||
protected override IHost CreateHost(IHostBuilder builder)
|
||||
{
|
||||
var dummyHost = builder.Build();
|
||||
builder.ConfigureWebHost(webHostBuilder =>
|
||||
{
|
||||
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
|
||||
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
|
||||
});
|
||||
|
||||
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
|
||||
|
||||
var host = builder.Build();
|
||||
host.Start();
|
||||
var host = base.CreateHost(builder);
|
||||
|
||||
// Get the DbContextFactory instance and store it for later use during tests.
|
||||
_dbContextFactory = host.Services.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
|
||||
|
||||
// This delay prevents "ERR_CONNECTION_REFUSED" errors
|
||||
// which happened like 1 out of 10 times when running tests.
|
||||
Thread.Sleep(100);
|
||||
|
||||
return dummyHost;
|
||||
return host;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseUrls(HostUrl);
|
||||
|
||||
SetEnvironmentVariables();
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
@@ -129,19 +125,13 @@ public class WebApplicationAdminFactoryFixture<TEntryPoint> : WebApplicationFact
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to modify.</param>
|
||||
private static void RemoveExistingRegistrations(IServiceCollection services)
|
||||
{
|
||||
var descriptorsToRemove = new[]
|
||||
{
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory<AliasServerDbContext>)),
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AliasServerDbContext>)),
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(VersionedContentService)),
|
||||
};
|
||||
var descriptorsToRemove = services.Where(d =>
|
||||
d.ServiceType.ToString().Contains("AliasServerDbContext") ||
|
||||
d.ServiceType == typeof(VersionedContentService)).ToList();
|
||||
|
||||
foreach (var descriptor in descriptorsToRemove)
|
||||
{
|
||||
if (descriptor != null)
|
||||
{
|
||||
services.Remove(descriptor);
|
||||
}
|
||||
services.Remove(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Data.Common;
|
||||
using AliasServerDb;
|
||||
using AliasVault.Shared.Providers.Time;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -49,9 +50,9 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL the web application host will listen on.
|
||||
/// Gets or sets the port the web application kestrel host will listen on.
|
||||
/// </summary>
|
||||
public string HostUrl { get; set; } = "https://localhost:5001";
|
||||
public int Port { get; set; } = 5001;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time provider instance for mutating the current time in tests.
|
||||
@@ -87,28 +88,23 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
|
||||
/// <inheritdoc />
|
||||
protected override IHost CreateHost(IHostBuilder builder)
|
||||
{
|
||||
var dummyHost = builder.Build();
|
||||
builder.ConfigureWebHost(webHostBuilder =>
|
||||
{
|
||||
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
|
||||
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
|
||||
});
|
||||
|
||||
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
|
||||
|
||||
var host = builder.Build();
|
||||
host.Start();
|
||||
var host = base.CreateHost(builder);
|
||||
|
||||
// Get the DbContextFactory instance and store it for later use during tests.
|
||||
_dbContextFactory = host.Services.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
|
||||
|
||||
// This delay prevents "ERR_CONNECTION_REFUSED" errors
|
||||
// which happened like 1 out of 10 times when running tests.
|
||||
Thread.Sleep(100);
|
||||
|
||||
return dummyHost;
|
||||
return host;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseUrls(HostUrl);
|
||||
|
||||
SetEnvironmentVariables();
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
@@ -133,19 +129,13 @@ public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactor
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to modify.</param>
|
||||
private static void RemoveExistingRegistrations(IServiceCollection services)
|
||||
{
|
||||
var descriptorsToRemove = new[]
|
||||
{
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory<AliasServerDbContext>)),
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AliasServerDbContext>)),
|
||||
services.SingleOrDefault(d => d.ServiceType == typeof(ITimeProvider)),
|
||||
};
|
||||
var descriptorsToRemove = services.Where(d =>
|
||||
d.ServiceType.ToString().Contains("AliasServerDbContext") ||
|
||||
d.ServiceType == typeof(ITimeProvider)).ToList();
|
||||
|
||||
foreach (var descriptor in descriptorsToRemove)
|
||||
{
|
||||
if (descriptor != null)
|
||||
{
|
||||
services.Remove(descriptor);
|
||||
}
|
||||
services.Remove(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
namespace AliasVault.E2ETests.Infrastructure;
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
/// <summary>
|
||||
@@ -19,30 +21,19 @@ public class WebApplicationClientFactoryFixture<TEntryPoint> : WebApplicationFac
|
||||
where TEntryPoint : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the URL the web application host will listen on.
|
||||
/// Gets or sets the port the web application kestrel host will listen on.
|
||||
/// </summary>
|
||||
public string HostUrl { get; set; } = "https://localhost:5002";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseUrls(HostUrl);
|
||||
}
|
||||
public int Port { get; set; } = 5002;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IHost CreateHost(IHostBuilder builder)
|
||||
{
|
||||
var dummyHost = builder.Build();
|
||||
builder.ConfigureWebHost(webHostBuilder =>
|
||||
{
|
||||
webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port));
|
||||
webHostBuilder.ConfigureServices(s => s.AddSingleton<IServer, KestrelTestServer>());
|
||||
});
|
||||
|
||||
builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());
|
||||
|
||||
var host = builder.Build();
|
||||
host.Start();
|
||||
|
||||
// This delay prevents "ERR_CONNECTION_REFUSED" errors
|
||||
// which happened like 1 out of 10 times when running tests.
|
||||
Thread.Sleep(100);
|
||||
|
||||
return dummyHost;
|
||||
return base.CreateHost(builder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class ApiLoggingTests : ClientPlaywrightTest
|
||||
// Call webapi endpoint that throws an exception.
|
||||
try
|
||||
{
|
||||
await Page.GotoAsync(ApiBaseUrl + "api/v1/Test/Error");
|
||||
await Page.GotoAsync(ApiBaseUrl + "v1/Test/Error");
|
||||
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
}
|
||||
catch
|
||||
@@ -38,7 +38,7 @@ public class ApiLoggingTests : ClientPlaywrightTest
|
||||
// Read from database to check if the log entry was created.
|
||||
var logEntry = await ApiDbContext.Logs.Where(x => x.Application == "AliasVault.Api").OrderByDescending(x => x.Id).FirstOrDefaultAsync();
|
||||
|
||||
Assert.That(logEntry, Is.Not.Null, "Log entry for triggered exception not found in database. Check Serilog configuration and /api/v1/Test/Error endpoint.");
|
||||
Assert.That(logEntry, Is.Not.Null, "Log entry for triggered exception not found in database. Check Serilog configuration and /v1/Test/Error endpoint.");
|
||||
Assert.That(logEntry.Exception, Does.Contain("Test error"), "Log entry in database does not contain expected message. Check exception and Serilog configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.IntegrationTests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.IntegrationTests.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.IntegrationTests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.IntegrationTests.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<PackageReference Include="MailKit" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
|
||||
<PackageReference Include="NUnit" Version="4.2.2"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>AliasVault.Tests</RootNamespace>
|
||||
<LangVersion>13</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Debug\net8.0\AliasVault.UnitTests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Debug\net9.0\AliasVault.UnitTests.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<DocumentationFile>bin\Release\net8.0\AliasVault.UnitTests.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\net9.0\AliasVault.UnitTests.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,7 +34,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="NUnit" Version="4.2.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user