Compare commits

...

718 Commits
0.2.0 ... 0.7.0

Author SHA1 Message Date
Leendert de Borst
48b96b4151 Merge pull request #390 from lanedirt/389-prepare-070-release
Update documentation for 0.7.0 release
2024-11-20 17:01:20 +01:00
Leendert de Borst
e9064643a6 Add busy timeout to SQLite connections to prevent errors (#389) 2024-11-20 16:52:20 +01:00
Leendert de Borst
667592411f Update for 0.7.0 release (#389) 2024-11-20 16:51:48 +01:00
Leendert de Borst
dfdf4981cb Add LetsEncrypt ssl certificate generation to docker setup (#388)
* Add LetsEncrypt scaffolding to docker compose setup (#367)

* Update install.sh (#367)

* Add certificate request logic (#367)

* Update domain validation regex (#367)

* Update install.sh (#367)

* Update install.sh (#367)

* Update nginx.conf for LetsEncrypt validation (#367)

* Update nginx.conf (#367)

* Add certbot volume mapping to nginx (#367)

* Update nginx conf to template to use env vars (#367)

* Update nginx certbot root (#367)

* Update install.sh (#367)

* Update nginx ssl letsencrypt paths (#367)

* Update install.sh (#367)

* Use conditional nginx.conf include instead of vars (#367)

* Update install.sh so it doesn't restart docker stack but expects it to be running already (#367)

* Update permissions (#367)

* Update install.sh (#367)

* Refactor and cleanup (#367)
2024-11-20 16:25:35 +01:00
Leendert de Borst
0f377bdec6 Merge pull request #384 from lanedirt/383-add-try-catch-around-favicon-extractor-to-prevent-hostname-could-not-be-parsed-exceptions
Log failed favicon extraction as information instead of warning
2024-11-20 10:13:30 +01:00
Leendert de Borst
ba17474e62 Merge pull request #385 from lanedirt/370-from-is-always-empty-in-email-popup-in-client
Fix email from value which was empty
2024-11-20 10:13:22 +01:00
Leendert de Borst
c09ad99739 Merge pull request #387 from lanedirt/386-admin-menu-absolute-urls-do-not-work-when-ran-from-subdirectory
Update absolute urls to relative URLs in admin
2024-11-20 10:13:16 +01:00
Leendert de Borst
799efe1772 Update absolute urls to relative URLs in admin (#386) 2024-11-19 21:51:37 +01:00
Leendert de Borst
1d79400df5 Fix email from value which didn't show (#370) 2024-11-19 21:42:50 +01:00
Leendert de Borst
cc4a2e087f Update FaviconController to log failed favicon extraction as information instead of warning (#383) 2024-11-19 21:30:57 +01:00
Leendert de Borst
64a76f3b9f Merge pull request #381 from lanedirt/372-installsh-reset-password-throws-sed-notice-error
Fix bug in reset-password regex check
2024-11-18 20:28:28 +01:00
Leendert de Borst
7c1aaab291 Fix bug in reset-password regex check (#372) 2024-11-18 20:21:55 +01:00
Leendert de Borst
63556d163a Merge pull request #380 from lanedirt/374-publish-docker-images-on-release
Add -y flag to install.sh for uninstall action
2024-11-18 20:13:13 +01:00
Leendert de Borst
c49c0e4ad5 Update install.sh (#374) 2024-11-18 19:51:05 +01:00
Leendert de Borst
3f2121f272 Merge pull request #379 from lanedirt/374-publish-docker-images-on-release
Publish docker images on release
2024-11-18 19:42:55 +01:00
Leendert de Borst
ebdcf778be Update README.md (#374) 2024-11-18 19:00:10 +01:00
Leendert de Borst
fb669df9cf Update docs (#374) 2024-11-18 17:18:28 +01:00
Leendert de Borst
cedf7d0733 Update README.md (#374) 2024-11-18 17:13:17 +01:00
Leendert de Borst
00db83f478 Update github actions to use new install.sh (#374) 2024-11-18 16:39:09 +01:00
Leendert de Borst
03b7f92a44 Fix admin absolute redirect issues (#374) 2024-11-18 16:32:20 +01:00
Leendert de Borst
d542a4273d Fix DataProtection issues (#374) 2024-11-18 16:32:06 +01:00
Leendert de Borst
dcb27ca543 Update install.sh to generate/download external dependencies (#374) 2024-11-18 16:31:11 +01:00
Leendert de Borst
78635b8ba1 Combine all CLI actions to a single file (#374) 2024-11-18 13:06:10 +01:00
Leendert de Borst
e18d31ee9b Fix 404 dark mode text (#374) 2024-11-18 12:56:31 +01:00
Leendert de Borst
0db5fb64a8 Run install and build in verbose mode in workflows (#374) 2024-11-18 11:33:41 +01:00
Leendert de Borst
e36d28eb99 Update README (#374) 2024-11-18 11:33:18 +01:00
Leendert de Borst
dd331f75c9 Fix regex (#374) 2024-11-18 11:15:10 +01:00
dependabot[bot]
aa11697ee2 Bump NUnit.Analyzers from 4.3.0 to 4.4.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.3.0...4.4.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 11:05:37 +01:00
dependabot[bot]
fdd698dd0a Bump Microsoft.IdentityModel.Tokens from 8.2.0 to 8.2.1
Bumps [Microsoft.IdentityModel.Tokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 8.2.0 to 8.2.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.2.0...8.2.1)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.Tokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 11:05:29 +01:00
Leendert de Borst
c8df588401 Fix admin password check (#374) 2024-11-18 11:05:07 +01:00
dependabot[bot]
a8373338c2 Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /src/AliasVault.Admin directory: [cross-spawn](https://github.com/moxystudio/node-cross-spawn).
Bumps the npm_and_yarn group with 1 update in the /src/AliasVault.Client directory: [cross-spawn](https://github.com/moxystudio/node-cross-spawn).


Updates `cross-spawn` from 7.0.3 to 7.0.5
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.5)

Updates `cross-spawn` from 7.0.3 to 7.0.5
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.5)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: cross-spawn
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 10:47:17 +01:00
Leendert de Borst
15abd1f51b Update workflows (#374) 2024-11-18 10:46:22 +01:00
Leendert de Borst
71407cc86d Publish InstallCli image used for resetting admin password (#374) 2024-11-18 10:46:00 +01:00
Leendert de Borst
85a3fed127 Create docker-compose-pull.yml (#374) 2024-11-18 10:44:05 +01:00
Leendert de Borst
6b8f0d6cdf Add separate install/build.sh files (#374) 2024-11-18 10:43:50 +01:00
Leendert de Borst
43441831d4 Convert repository name to lowercase (#374) 2024-11-18 09:41:39 +01:00
Leendert de Borst
319cff8fe1 Merge pull request #375 from lanedirt/374-publish-docker-images-on-release
Publish docker images on release
2024-11-18 09:38:47 +01:00
Leendert de Borst
5904204465 Add docker image publish workflow (#374) 2024-11-18 09:33:52 +01:00
Leendert de Borst
6c8cc92a67 Merge pull request #365 from lanedirt/364-update-docker-setup-to-run-https-by-default
Update docker setup to run https by default
2024-11-15 18:56:34 +01:00
Leendert de Borst
693860acef Update Dockerfile (#364) 2024-11-15 18:48:50 +01:00
Leendert de Borst
f7626ec15b Update ApiLoggingTests to set correct base url (#364) 2024-11-15 18:40:09 +01:00
Leendert de Borst
03c6bbc81f Update README.md (#364) 2024-11-15 17:27:57 +01:00
Leendert de Borst
bbe7ef1b2b Update install.sh (#364) 2024-11-15 17:25:59 +01:00
Leendert de Borst
027b95da15 Fix dataprotection certificate errors (#364) 2024-11-15 17:20:57 +01:00
Leendert de Borst
e9c33a808f Make apps work when run in local debug mode (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
2545e1204f Update API and admin apps to be able to run under subdirectories (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
970d334b59 Make all apps available through single container and HTTPS port (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
50a18dc461 Add -k flag to ignore self-signed certs, refactor (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
0dcc77eb0d Update docker-compose-build.yml (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
cd84592be1 Fix AliasVault.InstallCli dockerfile names (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
df6de32a4a Update docker setup to run under HTTPS by default (#364) 2024-11-15 16:58:20 +01:00
Leendert de Borst
3d24772caa Merge pull request #366 from lanedirt/362-tweak-dataprotection-certificate-tweaks-so-its-not-dependent-on-local-machine-keystore
Change DataProtection certificate generation so its not dependent on local machine keystore
2024-11-15 16:56:00 +01:00
Leendert de Borst
1a106e59fc Update CertificateGenerator.cs (#362) 2024-11-13 21:20:02 +01:00
Leendert de Borst
290460c095 Merge pull request #361 from lanedirt/360-upgrade-all-projects-to-net-9
Upgrade all projects to .NET 9
2024-11-13 17:55:55 +01:00
Leendert de Borst
17802dc216 Fix dataprotection, refactor (#360) 2024-11-13 17:24:03 +01:00
Leendert de Borst
0de52a396a Add .NET 9 to sonarcloud workflow explicitly (#360) 2024-11-13 17:06:15 +01:00
Leendert de Borst
64705e582d Update E2E github workflow to use new .NET 9 (#360) 2024-11-13 16:50:37 +01:00
Leendert de Borst
b09cdcec1e Fix E2E tests by switching to new KestrelTestServer (#360) 2024-11-13 16:44:44 +01:00
Leendert de Borst
87bb34f3ba Update dotnet version in github workflows (#360) 2024-11-13 14:27:50 +01:00
Leendert de Borst
5b53208a3e Update .gitignore to also ignore sqlite bak files (#360) 2024-11-13 14:25:57 +01:00
Leendert de Borst
7a687bba43 Update dockerfiles to use .NET9 (#360) 2024-11-13 14:16:16 +01:00
Leendert de Borst
aafac49bcb Disable DataProtection temporary (#360) 2024-11-13 12:48:11 +01:00
Leendert de Borst
201af7b88a Upgrade all projects to .NET 9 (#360) 2024-11-13 11:47:05 +01:00
Leendert de Borst
43ddd394c2 Merge pull request #357 from lanedirt/356-prepare-060-release
Update AppInfo.cs with new release version
2024-11-11 18:47:12 +01:00
Leendert de Borst
f248f9cd26 Update AppInfo.cs (#356) 2024-11-11 18:46:48 +01:00
Leendert de Borst
24f6168a7e Merge pull request #355 from lanedirt/350-remove-first-step-from-create-wizard
Remove first step from create wizard
2024-11-11 18:45:11 +01:00
Leendert de Borst
46b0d7b24b Merge pull request #354 from lanedirt/353-add-support-email-option-to-env-to-show-in-client
Add support email to .env to show in client application
2024-11-11 18:30:32 +01:00
Leendert de Borst
37bc557278 Update tests (#350) 2024-11-11 18:30:16 +01:00
Leendert de Borst
29d7b6dfdb Remove WelcomeStep from new user registration flow (#350) 2024-11-11 18:11:18 +01:00
Leendert de Borst
ec84792034 Add support email to .env to show in client application (#353) 2024-11-11 18:05:59 +01:00
Leendert de Borst
250ab24654 Merge pull request #344 from lanedirt/341-improve-documentation-to-explain-architecture
Improve documentation
2024-11-11 14:56:35 +01:00
Leendert de Borst
9b8770bb20 Update README with architecture docs (#341) 2024-11-11 14:50:42 +01:00
Leendert de Borst
192d35d2dd Update security architecture diagram (#341) 2024-11-11 11:43:49 +01:00
Leendert de Borst
9d2f7a15dd Update ENCRYPTION.md (#341) 2024-11-11 11:43:36 +01:00
dependabot[bot]
95ca7ab014 Bump Microsoft.IdentityModel.JsonWebTokens and Microsoft.IdentityModel.Tokens
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) and [Microsoft.IdentityModel.Tokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet). These dependencies needed to be updated together.

Updates `Microsoft.IdentityModel.JsonWebTokens` from 8.1.2 to 8.2.0
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.1.2...8.2.0)

Updates `Microsoft.IdentityModel.Tokens` from 8.2.0 to 8.2.0
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.2.0...8.2.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Microsoft.IdentityModel.Tokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 11:30:49 +01:00
dependabot[bot]
d29a33a95e Bump NUglify from 1.21.9 to 1.21.10
Bumps [NUglify](https://github.com/trullock/NUglify) from 1.21.9 to 1.21.10.
- [Release notes](https://github.com/trullock/NUglify/releases)
- [Changelog](https://github.com/trullock/NUglify/blob/master/changelog.md)
- [Commits](https://github.com/trullock/NUglify/compare/v1.21.9...v1.21.10)

---
updated-dependencies:
- dependency-name: NUglify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 11:30:41 +01:00
Leendert de Borst
a2486b67d7 Update security architecture diagram (#341) 2024-11-10 22:12:42 +01:00
Leendert de Borst
abcd887e49 Update security architecture diagram (#341) 2024-11-10 14:21:08 +01:00
Leendert de Borst
f400387630 Update security architecture diagram (#341) 2024-11-09 17:52:34 +01:00
Leendert de Borst
3613a0cae1 Merge pull request #348 from lanedirt/347-reverting-vault-to-previous-version-does-not-work-correctly-when-password-has-been-changed
Get latest vault by revision number instead of updated at timestamp
2024-11-09 15:29:51 +01:00
Leendert de Borst
13e16d1a51 Update admin tests with new home to users redirect (#347) 2024-11-08 17:44:46 +01:00
Leendert de Borst
a2eb12d6b9 Refactor (#347) 2024-11-08 17:41:54 +01:00
Leendert de Borst
dbb0a33179 Get latest vault by revision number instead of updated at timestamp (#347) 2024-11-08 17:31:32 +01:00
Leendert de Borst
32bb4502d6 Add security architecture documentation and diagrams (#341) 2024-11-08 17:27:48 +01:00
Leendert de Borst
f12642873d Update ENCRYPTION.md (#341) 2024-11-08 17:27:08 +01:00
Leendert de Borst
e9b9d6c363 Merge pull request #346 from lanedirt/345-deleting-user-with-emails-throws-error
Deleting user that has email (claims) throws error
2024-11-07 19:06:50 +01:00
Leendert de Borst
a92f074099 Preserve email claims when user is deleted (#345) 2024-11-07 18:51:28 +01:00
Leendert de Borst
5fbaed57cc Update ENCRYPTION.md (#341) 2024-11-06 12:16:34 +01:00
Leendert de Borst
9e24442d84 Add encryption information (#341) 2024-11-06 12:12:17 +01:00
dependabot[bot]
7737a586ab Bump Microsoft.AspNetCore.Components.WebAssembly from 8.0.8 to 8.0.10
Bumps [Microsoft.AspNetCore.Components.WebAssembly](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 13:06:57 +01:00
dependabot[bot]
ce97c26b05 Bump Microsoft.IdentityModel.Tokens from 8.1.2 to 8.2.0
Bumps [Microsoft.IdentityModel.Tokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 8.1.2 to 8.2.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.1.2...8.2.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.Tokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 12:42:54 +01:00
dependabot[bot]
fa52643d48 Bump Microsoft.AspNetCore.Components.WebAssembly.Authentication
Bumps [Microsoft.AspNetCore.Components.WebAssembly.Authentication](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.Authentication
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 12:42:45 +01:00
Leendert de Borst
be3ea8e198 Bump Microsoft.Extensions.Configuration.Json in all projects (#300) 2024-11-04 09:39:08 +01:00
dependabot[bot]
574dcb3048 Bump Microsoft.Extensions.Hosting and Microsoft.Extensions.Configuration.Json
Bumps [Microsoft.Extensions.Hosting](https://github.com/dotnet/runtime) and [Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/runtime). These dependencies needed to be updated together.

Updates `Microsoft.Extensions.Hosting` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

Updates `Microsoft.Extensions.Configuration.Json` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Hosting
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.Configuration.Json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 09:39:08 +01:00
Leendert de Borst
bb20f16b0f Merge pull request #337 from lanedirt/336-include-app-version-and-show-it-in-client-and-admin-project-footer
Add static app version and show in footer
2024-11-03 17:22:19 +01:00
Leendert de Borst
8284241be6 Add static app version and show in footer (#336) 2024-11-03 15:14:41 +01:00
dependabot[bot]
a0fb68a68c Bump Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
Bumps [Microsoft.AspNetCore.DataProtection.EntityFrameworkCore](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-03 13:44:01 +01:00
dependabot[bot]
2e1497795d Bump Microsoft.AspNetCore.Components.Web and Microsoft.AspNetCore.Authorization
Bumps [Microsoft.AspNetCore.Components.Web](https://github.com/dotnet/aspnetcore) and [Microsoft.AspNetCore.Authorization](https://github.com/dotnet/aspnetcore). These dependencies needed to be updated together.

Updates `Microsoft.AspNetCore.Components.Web` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.AspNetCore.Authorization` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.Web
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.AspNetCore.Authorization
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:49:49 +01:00
dependabot[bot]
ddeab13cc3 Bump HtmlAgilityPack from 1.11.67 to 1.11.70
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.67 to 1.11.70.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/commits/v1.11.70)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:49:29 +01:00
dependabot[bot]
8841a51376 Bump Microsoft.AspNetCore.Identity.EntityFrameworkCore
Bumps [Microsoft.AspNetCore.Identity.EntityFrameworkCore](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Identity.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:49:04 +01:00
dependabot[bot]
e8e380bd22 Bump Serilog from 4.0.2 to 4.1.0
Bumps [Serilog](https://github.com/serilog/serilog) from 4.0.2 to 4.1.0.
- [Release notes](https://github.com/serilog/serilog/releases)
- [Commits](https://github.com/serilog/serilog/compare/v4.0.2...v4.1.0)

---
updated-dependencies:
- dependency-name: Serilog
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:49:00 +01:00
dependabot[bot]
ce5ad91ef5 Bump Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Bumps [Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:48:54 +01:00
dependabot[bot]
b253772152 Bump Microsoft.EntityFrameworkCore.Sqlite from 8.0.8 to 8.0.10
Bumps [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:48:49 +01:00
dependabot[bot]
b0cb9e9b8d Bump Microsoft.EntityFrameworkCore.Design from 8.0.8 to 8.0.10
Bumps [Microsoft.EntityFrameworkCore.Design](https://github.com/dotnet/efcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Design
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:48:43 +01:00
dependabot[bot]
085706eae5 Bump Microsoft.AspNetCore.Components.WebAssembly.DevServer
Bumps [Microsoft.AspNetCore.Components.WebAssembly.DevServer](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.DevServer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-02 13:48:37 +01:00
Leendert de Borst
f57dcfa894 Update README.md 2024-11-02 13:18:49 +01:00
dependabot[bot]
1b9ff1754a Bump Serilog.Settings.Configuration from 8.0.2 to 8.0.4
Bumps [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) from 8.0.2 to 8.0.4.
- [Release notes](https://github.com/serilog/serilog-settings-configuration/releases)
- [Changelog](https://github.com/serilog/serilog-settings-configuration/blob/dev/CHANGES.md)
- [Commits](https://github.com/serilog/serilog-settings-configuration/compare/v8.0.2...v8.0.4)

---
updated-dependencies:
- dependency-name: Serilog.Settings.Configuration
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:17:01 +01:00
dependabot[bot]
86a49de886 Bump Microsoft.AspNetCore.Authentication.JwtBearer from 8.0.8 to 8.0.10
Bumps [Microsoft.AspNetCore.Authentication.JwtBearer](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Authentication.JwtBearer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:54 +01:00
dependabot[bot]
f6473b263b Bump Microsoft.Extensions.Http from 8.0.0 to 8.0.1
Bumps [Microsoft.Extensions.Http](https://github.com/dotnet/runtime) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.Http
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:48 +01:00
dependabot[bot]
eae0aaf2a5 Bump Swashbuckle.AspNetCore from 6.8.1 to 6.9.0
Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.8.1 to 6.9.0.
- [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases)
- [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.8.1...v6.9.0)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:42 +01:00
dependabot[bot]
e086ce3684 Bump Microsoft.Playwright.NUnit from 1.47.0 to 1.48.0
Bumps [Microsoft.Playwright.NUnit](https://github.com/microsoft/playwright-dotnet) from 1.47.0 to 1.48.0.
- [Release notes](https://github.com/microsoft/playwright-dotnet/releases)
- [Commits](https://github.com/microsoft/playwright-dotnet/compare/v1.47.0...v1.48.0)

---
updated-dependencies:
- dependency-name: Microsoft.Playwright.NUnit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:35 +01:00
dependabot[bot]
3401dcf402 Bump Microsoft.AspNetCore.Components.WebAssembly.Server
Bumps [Microsoft.AspNetCore.Components.WebAssembly.Server](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.Server
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:30 +01:00
dependabot[bot]
30ec77ef56 Bump Microsoft.EntityFrameworkCore from 8.0.8 to 8.0.10
Bumps [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:16:21 +01:00
Leendert de Borst
5e6e05cfd3 Update dependabot.yml 2024-11-01 19:16:06 +01:00
Leendert de Borst
d06187879b Merge pull request #328 from lanedirt/326-add-beta-indication
Add beta indication
2024-10-31 00:05:46 +01:00
Leendert de Borst
d5e6b7a5c7 Update text (#326) 2024-10-30 23:25:06 +01:00
Leendert de Borst
609006d199 Add beta flag to logo (#326) 2024-10-29 20:44:53 +01:00
Leendert de Borst
1eb6f46f3c Merge pull request #327 from lanedirt/177-replace-welcome-screen-with-step-by-step-wizard-interface-for-creating-first-credential
Replace welcome screen with simple tutorial
2024-10-28 17:11:19 +01:00
Leendert de Borst
ad03613316 Tweak admin layout for mobile (#177) 2024-10-28 16:52:32 +01:00
Leendert de Borst
d71194b3b8 Add E2E tests for welcome screen tutorial (#177) 2024-10-28 16:48:00 +01:00
Leendert de Borst
7b43acec09 Refactor (#177) 2024-10-28 16:13:16 +01:00
Leendert de Borst
8e24686578 Update tests (#177) 2024-10-28 15:13:10 +01:00
Leendert de Borst
2d91fdfaed Tweak layout for mobile (#177) 2024-10-28 14:59:54 +01:00
Leendert de Borst
ce93c01039 Show credential placeholder on empty page (#177) 2024-10-28 14:11:07 +01:00
Leendert de Borst
0631daf61b Add TutorialDone settings flag (#177) 2024-10-28 13:36:55 +01:00
Leendert de Borst
50c3c64db6 Delete transitionEffect.js (#177) 2024-10-28 13:36:36 +01:00
Leendert de Borst
5b22a41aa6 Rework welcome screen (#177) 2024-10-28 13:02:06 +01:00
Leendert de Borst
e2be93ec85 Change welcome page to tutorial steps (#177) 2024-10-27 20:45:30 +01:00
Leendert de Borst
bc6c7a10dc Update light mode background color for improved constrast (#177) 2024-10-27 20:28:29 +01:00
Leendert de Borst
bca0d3ee6b Merge pull request #325 from lanedirt/314-improve-ux-for-slow-clients
Show page refresh button if loading takes too long
2024-10-26 18:02:04 +02:00
Leendert de Borst
c860899f8e Show refresh button after 30 sec (#314) 2024-10-25 23:32:20 +02:00
Leendert de Borst
fba8c171b6 Show page refresh button if loading takes too long (#314) 2024-10-25 18:08:40 +02:00
Leendert de Borst
557deee352 Merge pull request #324 from lanedirt/320-add-client-styled-error-messages
Improve error message for unlock page if API cannot be reached
2024-10-24 20:44:31 +02:00
Leendert de Borst
9ec1d83de9 Update Unlock.razor (#320) 2024-10-24 20:02:35 +02:00
Leendert de Borst
6760a9c89a Merge pull request #322 from lanedirt/317-refactor-client-appsettingsjson-to-always-use-config-object
Expose all config settings through config object
2024-10-23 22:01:49 +02:00
Leendert de Borst
86ccccb95d Improve error message for unlock page if API cannot be reached (#320) 2024-10-23 22:01:22 +02:00
Leendert de Borst
8cca485930 Merge pull request #321 from lanedirt/312-login-flow-optimizations
Login flow optimizations
2024-10-23 21:53:13 +02:00
Leendert de Borst
353631bcda Expose all config settings through config object (#317) 2024-10-23 21:48:04 +02:00
Leendert de Borst
2f79eabd1b Add hidden submit button to allow enter to submit the form if it has multiple elements (#312) 2024-10-23 21:44:56 +02:00
Leendert de Borst
0ef1a9b118 Add form wrapper around setup fields to intercept enter (#312) 2024-10-22 23:10:45 +02:00
Leendert de Borst
7a374d9730 Improve webauthn unlock flow (#312) 2024-10-22 22:52:11 +02:00
Leendert de Borst
faa578b5b5 Update unlock.razor webauthn flow (#312) 2024-10-22 21:42:36 +02:00
Leendert de Borst
d59757c8fb Merge pull request #318 from lanedirt/315-fix-account-creation-mobile-interface-for-safari-browser
Fix account creation mobile interface for safari browser
2024-10-22 21:27:33 +02:00
Leendert de Borst
88e5142049 Add scroll to top when switching between steps (#315) 2024-10-22 21:18:26 +02:00
Leendert de Borst
f6c9000bec Fix uninstall script (#315) 2024-10-21 23:37:08 +02:00
Leendert de Borst
d3b3281ca4 Update README.md 2024-10-21 23:10:50 +02:00
Leendert de Borst
55178006c8 Fix setup theme for mobile devices (#315) 2024-10-21 19:50:50 +02:00
Leendert de Borst
92b65bad20 Add margin to loading screens in client (#315) 2024-10-21 12:22:54 +02:00
Leendert de Borst
443721dc75 Update css rebuild task names to make client and admin unique (#315) 2024-10-21 12:19:33 +02:00
Leendert de Borst
997335205f Update launchSettings to bind to all network interfaces to allow mobile phone debugging on same LAN (#315) 2024-10-21 11:56:23 +02:00
Leendert de Borst
2d4c865709 Merge pull request #316 from lanedirt/311-uploading-multiple-attachments-doesnt-work
Uploading multiple attachments doesnt work
2024-10-20 15:47:52 +02:00
Leendert de Borst
5f4290add1 Update credential update logic to save all new attachments (#311) 2024-10-19 12:08:42 +02:00
Leendert de Borst
f553f4e596 Add attachment tests to reproduce issue (#311) 2024-10-19 11:59:13 +02:00
Leendert de Borst
acdeb8bc28 Allow for uploading multiple attachments (#311) 2024-10-19 11:59:03 +02:00
Leendert de Borst
030b90eb76 Update style for mobile responsive setup (#315) 2024-10-19 11:37:59 +02:00
Leendert de Borst
022a32358d Merge pull request #313 from lanedirt/308-optimize-credential-edit-page-for-mobile
Optimize credential AddEdit page for mobile
2024-10-18 21:50:24 +02:00
Leendert de Borst
75f71dcc43 Optimize credential AddEdit page for mobile (#308) 2024-10-18 21:41:59 +02:00
Leendert de Borst
dae7e1d065 Merge pull request #310 from lanedirt/306-improve-ux-for-login--create-account-flow
Improve ux for login and create account flow
2024-10-18 18:04:22 +02:00
Leendert de Borst
d0b680c20c Add E2E test for new user setup account creation flow (#306) 2024-10-18 17:56:40 +02:00
Leendert de Borst
95bc774d2d Refactor (#306) 2024-10-18 17:39:32 +02:00
Leendert de Borst
11c680471f Fix release mode conditional variable name (#306) 2024-10-18 17:36:07 +02:00
Leendert de Borst
c5ccde298f Fix existing tests (#306) 2024-10-18 17:01:09 +02:00
Leendert de Borst
c969cd738f Fix note bugs (#306) 2024-10-18 16:04:34 +02:00
Leendert de Borst
be62fc9127 Replaced png logo with SVG (#306) 2024-10-18 15:33:36 +02:00
Leendert de Borst
da5754d720 Cleanup setup components (#306) 2024-10-18 15:05:52 +02:00
Leendert de Borst
658d03bc02 Improve flow with separate creating step, minor tweaks (#306) 2024-10-18 14:28:50 +02:00
Leendert de Borst
862f013bda Add terms and conditions step (#306) 2024-10-18 12:53:07 +02:00
Leendert de Borst
c0d250a35c Update back and continue button position (#306) 2024-10-18 12:01:44 +02:00
Leendert de Borst
26580f72ca Tweak account create layout for small and large screens (#306) 2024-10-18 11:46:14 +02:00
Leendert de Borst
3ff418945c Tweak start page (#306) 2024-10-17 22:56:15 +02:00
Leendert de Borst
c729a06e25 Add focus on input, tweak password notices (#306) 2024-10-17 22:44:03 +02:00
Leendert de Borst
b62a13ce76 Improve username step validation (#306) 2024-10-16 18:01:34 +02:00
Leendert de Borst
9dca684e4c Add password step and working account creation (#306) 2024-10-15 22:21:16 +02:00
Leendert de Borst
5d7433674c Add username validation logic (#306) 2024-10-15 21:27:32 +02:00
Leendert de Borst
458a336526 Add user create tutorial scaffolding (#306) 2024-10-14 17:05:38 +02:00
Leendert de Borst
9e16e30ad0 Add start screen, replace logo with text and img cleanup (#306) 2024-10-14 13:42:40 +02:00
Leendert de Borst
de0cce7b64 Merge pull request #296 from lanedirt/287-saving-existing-credential-with-one-or-more-attachments-fails
Saving existing credential with one or more attachments fails
2024-10-14 09:58:30 +02:00
dependabot[bot]
7b315dc87a Bump MailKit from 4.7.1.1 to 4.8.0
Bumps [MailKit](https://github.com/jstedfast/MailKit) from 4.7.1.1 to 4.8.0.
- [Changelog](https://github.com/jstedfast/MailKit/blob/master/ReleaseNotes.md)
- [Commits](https://github.com/jstedfast/MailKit/compare/4.7.1.1...4.8.0)

---
updated-dependencies:
- dependency-name: MailKit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 09:56:48 +02:00
Leendert de Borst
dcf04f040d Add attachment delete test and fix bug with IsDeleted flag (#287) 2024-10-13 19:04:55 +02:00
Leendert de Borst
6d795c6370 Added attachment test that reproduces issue (#287) 2024-10-13 16:32:06 +02:00
Leendert de Borst
827f677af8 Add attachment upload test scaffolding (#287) 2024-10-12 12:10:56 +02:00
dependabot[bot]
be96d45275 Bump Serilog from 4.0.1 to 4.0.2
Bumps [Serilog](https://github.com/serilog/serilog) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/serilog/serilog/releases)
- [Commits](https://github.com/serilog/serilog/compare/v4.0.1...v4.0.2)

---
updated-dependencies:
- dependency-name: Serilog
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:29:33 +02:00
dependabot[bot]
7ef6e3ad8f Bump MimeKit from 4.7.1 to 4.8.0
Bumps [MimeKit](https://github.com/jstedfast/MimeKit) from 4.7.1 to 4.8.0.
- [Changelog](https://github.com/jstedfast/MimeKit/blob/master/ReleaseNotes.md)
- [Commits](https://github.com/jstedfast/MimeKit/compare/4.7.1...4.8.0)

---
updated-dependencies:
- dependency-name: MimeKit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:29:26 +02:00
dependabot[bot]
df83bdd329 Bump Swashbuckle.AspNetCore from 6.8.0 to 6.8.1
Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.8.0 to 6.8.1.
- [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases)
- [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.8.0...v6.8.1)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:29:19 +02:00
dependabot[bot]
9d1341e02a Bump HtmlAgilityPack from 1.11.66 to 1.11.67
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.66 to 1.11.67.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/commits)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:29:14 +02:00
dependabot[bot]
1f249af022 Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /src/AliasVault.Admin directory: [micromatch](https://github.com/micromatch/micromatch).
Bumps the npm_and_yarn group with 1 update in the /src/AliasVault.Client directory: [micromatch](https://github.com/micromatch/micromatch).


Updates `micromatch` from 4.0.7 to 4.0.8
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

Updates `micromatch` from 4.0.7 to 4.0.8
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: micromatch
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:29:05 +02:00
dependabot[bot]
5f9f607290 Bump Microsoft.EntityFrameworkCore.Tools, Microsoft.EntityFrameworkCore.Design and Microsoft.EntityFrameworkCore
Bumps [Microsoft.EntityFrameworkCore.Tools](https://github.com/dotnet/efcore), [Microsoft.EntityFrameworkCore.Design](https://github.com/dotnet/efcore) and [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore). These dependencies needed to be updated together.

Updates `Microsoft.EntityFrameworkCore.Tools` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.EntityFrameworkCore.Design` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.EntityFrameworkCore` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.EntityFrameworkCore.Design
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:54 +02:00
dependabot[bot]
16baa7ad74 Bump Microsoft.AspNetCore.DataProtection from 8.0.8 to 8.0.10
Bumps [Microsoft.AspNetCore.DataProtection](https://github.com/dotnet/aspnetcore) from 8.0.8 to 8.0.10.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.DataProtection
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:46 +02:00
dependabot[bot]
c4197e6cd8 Bump Microsoft.EntityFrameworkCore.Proxies and Microsoft.EntityFrameworkCore
Bumps [Microsoft.EntityFrameworkCore.Proxies](https://github.com/dotnet/efcore) and [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore). These dependencies needed to be updated together.

Updates `Microsoft.EntityFrameworkCore.Proxies` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.EntityFrameworkCore` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Proxies
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:40 +02:00
dependabot[bot]
8aa2ec925a Bump Microsoft.IdentityModel.JsonWebTokens and Microsoft.IdentityModel.Tokens
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) and [Microsoft.IdentityModel.Tokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet). These dependencies needed to be updated together.

Updates `Microsoft.IdentityModel.JsonWebTokens` from 8.1.0 to 8.1.2
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.1.0...8.1.2)

Updates `Microsoft.IdentityModel.Tokens` from 8.1.0 to 8.1.2
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.1.0...8.1.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.IdentityModel.Tokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:32 +02:00
dependabot[bot]
b3ac7064ef Bump Microsoft.AspNetCore.Mvc.Testing, Microsoft.Extensions.Hosting and Microsoft.Extensions.Configuration.Json
Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore), [Microsoft.Extensions.Hosting](https://github.com/dotnet/runtime) and [Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/runtime). These dependencies needed to be updated together.

Updates `Microsoft.AspNetCore.Mvc.Testing` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.Extensions.Hosting` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

Updates `Microsoft.Extensions.Configuration.Json` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Mvc.Testing
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.Hosting
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.Configuration.Json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:25 +02:00
dependabot[bot]
91f554187e Bump Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore
Bumps [Microsoft.EntityFrameworkCore.SqlServer](https://github.com/dotnet/efcore) and [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore). These dependencies needed to be updated together.

Updates `Microsoft.EntityFrameworkCore.SqlServer` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

Updates `Microsoft.EntityFrameworkCore` from 8.0.8 to 8.0.10
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.8...v8.0.10)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.SqlServer
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 16:28:17 +02:00
Leendert de Borst
e161478614 Merge pull request #288 from lanedirt/175-make-all-ui-components-compatible-with-dark-mode
Make all UI components compatible with dark mode
2024-10-11 16:27:05 +02:00
Leendert de Borst
92e2de76ba Refactor (#175) 2024-10-11 16:17:04 +02:00
Leendert de Borst
5644f89db0 Updated admin for dark mode (#175) 2024-10-11 14:57:44 +02:00
Leendert de Borst
8ea8382c7d Fix dark mode init, update loading screen (#175) 2024-10-11 11:41:03 +02:00
Leendert de Borst
869c3c86be Update dark mode body color (#175) 2024-10-10 18:11:48 +02:00
Leendert de Borst
cd865d70c6 Update alerts and sync messages for dark mode (#175) 2024-10-09 22:37:44 +02:00
Leendert de Borst
5bb2e3d5ce Set dark mode during page init (#175) 2024-10-08 22:35:48 +02:00
Leendert de Borst
e80c351ee6 Update client for dark theme compatibility (#175) 2024-10-08 21:50:41 +02:00
Leendert de Borst
359fa1752f Merge pull request #286 from lanedirt/220-refactor-blazor-components-for-reuse
Update ClientPlaywrightTest.cs (#220)
2024-10-07 21:07:48 +02:00
Leendert de Borst
259de77a12 Update ClientPlaywrightTest.cs (#220) 2024-10-07 21:07:27 +02:00
Leendert de Borst
f15ef80839 Merge pull request #284 from lanedirt/220-refactor-blazor-components-for-reuse
Refactor blazor components to reduce duplicated code
2024-10-07 20:58:54 +02:00
Leendert de Borst
62d2249f40 Refactor (#220) 2024-10-07 20:50:20 +02:00
Leendert de Borst
14845e77e0 Update tests (#220) 2024-10-07 20:29:29 +02:00
Leendert de Borst
fa664ea918 Update all admin tables to use new SortableTable component (#220) 2024-10-07 20:03:23 +02:00
Leendert de Borst
bbb168d764 Refactor table to common SortableTable component (#220) 2024-10-07 17:47:07 +02:00
Leendert de Borst
d5ba3a63e4 Refactor misc buttons in client app to shared button components (#220) 2024-10-07 16:29:59 +02:00
Leendert de Borst
afc605afd0 Update LinkButton (#220) 2024-10-07 16:11:42 +02:00
Leendert de Borst
cdbe6c6e8c Refactor admin account settings links/buttons (#220) 2024-10-07 16:03:11 +02:00
Leendert de Borst
b184273456 Refactor page headers in admin to shared component (#220) 2024-10-07 15:18:08 +02:00
Leendert de Borst
fb7de645e5 Adjust namespace (#220) 2024-10-07 14:39:08 +02:00
Leendert de Borst
35f35b8bbe Refactor page headers in client to shared component (#220) 2024-10-07 14:34:53 +02:00
Leendert de Borst
4c5e312f11 Update welcome.razor margins (#220) 2024-10-07 13:11:11 +02:00
Leendert de Borst
708f6d7b9b Merge pull request #283 from lanedirt/280-optimize-mobile-responsive-version
Mobile response tweaks
2024-10-07 12:43:12 +02:00
Leendert de Borst
5b72e181e6 Merge pull request #282 from lanedirt/276-show-specific-warning-if-wasm-is-not-supported-by-client
Add WASM error detection and show application warning if WASM is not supported by client
2024-10-07 12:42:58 +02:00
Leendert de Borst
4441c543cc Refactor if/else (#276) 2024-10-07 12:29:51 +02:00
Leendert de Borst
933f8650ce Mobile response tweaks (#280) 2024-10-07 12:27:28 +02:00
Leendert de Borst
26c910bf26 Add WASM error detection and show application warning (#276) 2024-10-07 11:45:40 +02:00
Leendert de Borst
037919a548 Update README.md 2024-10-06 15:11:03 +02:00
Leendert de Borst
771527c891 Update README.md 2024-10-06 15:09:13 +02:00
Leendert de Borst
13c8709c19 Merge pull request #278 from lanedirt/275-make-2fa-input-field-number-input
Make 2fa input field a number
2024-10-05 19:53:28 +02:00
Leendert de Borst
043538054e Make 2FA input field a number (#275) 2024-10-05 19:45:46 +02:00
Leendert de Borst
d366dc3b0c Fix note regex bug (#275) 2024-10-05 19:16:45 +02:00
Leendert de Borst
35f1332138 Merge pull request #273 from lanedirt/271-add-newline-support-to-notes-field
Update note widget to support newlines and anchor tags
2024-10-05 15:20:31 +02:00
Leendert de Borst
ac040d90a8 Update note widget to support newlines and anchor tags (#271) 2024-10-04 20:02:06 +02:00
Leendert de Borst
0e50276308 Merge pull request #274 from lanedirt/269-add-support-for-persisting-encryption-key
Revert JsInteropService to have init in properties
2024-10-04 20:01:01 +02:00
Leendert de Borst
1c0041326b Revert JsInteropService to have init in properties (#269) 2024-10-04 20:00:29 +02:00
Leendert de Borst
8d13b1b0e3 Merge pull request #272 from lanedirt/269-add-support-for-persisting-encryption-key
Make webauthn implementation secure
2024-10-04 18:23:25 +02:00
Leendert de Borst
a267d94b3e Fix flaky tests (#269) 2024-10-04 18:11:24 +02:00
Leendert de Borst
4ccbba5b4b Refactor (#269) 2024-10-04 17:23:25 +02:00
Leendert de Borst
daf25fcc12 Updated instructions (#269) 2024-10-04 14:33:41 +02:00
Leendert de Borst
fc8d365c49 Improve webauthn error reporting, add support for Yubikey (#269) 2024-10-04 14:11:48 +02:00
Leendert de Borst
e7e66a6285 Rewrite webauthn implementation to use PRF (#269) 2024-10-04 12:31:37 +02:00
Leendert de Borst
ad26450d8b Refactor webauthn call structure (#269) 2024-10-03 22:17:32 +02:00
Leendert de Borst
fcf0fb8605 Merge pull request #270 from lanedirt/269-add-support-for-persisting-encryption-key
Add support for persisting encryption key
2024-10-03 00:53:41 +02:00
Leendert de Borst
da2efa7e8a Add rough working webauthn version (#269) 2024-10-03 00:53:06 +02:00
Leendert de Borst
4018d38148 Add webauthn scaffolding (#269) 2024-10-02 20:23:21 +02:00
Leendert de Borst
6affa67561 Update Login.razor (#265) 2024-09-30 11:49:27 +02:00
dependabot[bot]
0a543cec42 Bump Microsoft.IdentityModel.JsonWebTokens and Microsoft.IdentityModel.Tokens
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) and [Microsoft.IdentityModel.Tokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet). These dependencies needed to be updated together.

Updates `Microsoft.IdentityModel.JsonWebTokens` from 8.0.1 to 8.1.0
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.0.1...8.1.0)

Updates `Microsoft.IdentityModel.Tokens` from 8.0.1 to 8.1.0
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.0.1...8.1.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: Microsoft.IdentityModel.Tokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-25 07:49:16 +02:00
Leendert de Borst
179faac0a0 Update README.md 2024-09-24 23:10:39 +02:00
Leendert de Borst
4cfacc5012 Merge pull request #259 from lanedirt/233-general-refactoring-and-cleanup
General refactoring, update folder structure, code cleanup
2024-09-24 23:06:11 +02:00
Leendert de Borst
a407a23101 Implement ILogger for all client side console logging calls (#233) 2024-09-24 23:05:25 +02:00
Leendert de Borst
df33d4abd4 Replace console.writeline with ILogger implementation for dbservice (#233) 2024-09-24 18:38:34 +02:00
dependabot[bot]
28a5939f62 Bump Swashbuckle.AspNetCore from 6.7.3 to 6.8.0
Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.7.3 to 6.8.0.
- [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases)
- [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.7.3...v6.8.0)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 08:16:45 +02:00
Leendert de Borst
467b25104e Update dotnet-e2e-client-tests.yml (#233) 2024-09-23 18:50:10 +02:00
Leendert de Borst
8ee3cd0396 Merge main into current branch (#233) 2024-09-23 18:37:54 +02:00
Leendert de Borst
d471a61fbf Restructure tests into their own shards to speed up parallel execution (#233) 2024-09-23 18:35:48 +02:00
dependabot[bot]
df0413038e Bump HtmlAgilityPack from 1.11.65 to 1.11.66
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.65 to 1.11.66.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.65...v1.11.66)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 18:07:04 +02:00
Leendert de Borst
9180d600a6 Separate e2e tests to run in separate actions to speed up execution (#233) 2024-09-23 18:06:43 +02:00
Leendert de Borst
8bea3d9336 Refactor (#233) 2024-09-22 22:08:59 +02:00
Leendert de Borst
1f88d5678b Refactor E2E registration part (#233) 2024-09-22 21:35:46 +02:00
Leendert de Borst
061e72210f Update null! usage to required or default empty value (#233) 2024-09-21 22:19:01 +02:00
Leendert de Borst
ef8fa091b9 Update LoginBase.cs (#233) 2024-09-20 18:35:40 +02:00
Leendert de Borst
21e8171355 Refactor (#233) 2024-09-20 18:07:26 +02:00
Leendert de Borst
5509be5281 Update Dockerfile (#233) 2024-09-20 17:03:10 +02:00
Leendert de Borst
6c7645ea3d Enable treat warning as error for release (#233) 2024-09-20 17:02:51 +02:00
Leendert de Borst
4bd3b5cb29 Refactor project folder structure (#233) 2024-09-20 16:19:06 +02:00
Leendert de Borst
2e08de3546 Move shared projects (#233) 2024-09-20 15:17:38 +02:00
Leendert de Borst
956338f61f Refactor generator projects (#233) 2024-09-20 14:56:14 +02:00
Leendert de Borst
9f87861f88 Add default service URL check to update (#233) 2024-09-20 11:44:27 +02:00
Leendert de Borst
db6357a845 Tweak lock/unlock flow (#233) 2024-09-19 22:21:23 +02:00
Leendert de Borst
adc82278b2 Merge pull request #258 from lanedirt/257-move-user-menu-items-to-mobile-menu-for-mobile-screens 2024-09-19 08:49:48 +02:00
Leendert de Borst
22945f6066 Tweak top menu, add dblock functionality (#257) 2024-09-19 00:14:00 +02:00
Leendert de Borst
9646552e46 Combine user menu to mobile menu (#267) 2024-09-18 23:27:26 +02:00
Leendert de Borst
2ffc6c1f52 Update timeout from 15sec to 20sec to reduce errors in GitHub Actions runs (#254) 2024-09-18 00:12:26 +02:00
Leendert de Borst
9338384649 Set remember me checkbox to enabled by default (#254) 2024-09-18 00:12:26 +02:00
Leendert de Borst
b6cfc03b01 Add revision retention rule to keep last 10 revisions (#254) 2024-09-18 00:12:26 +02:00
Leendert de Borst
997bd8ce44 Add revision number to admin and default migration (#254) 2024-09-18 00:12:26 +02:00
Leendert de Borst
e8b0544735 Merge pull request #252 from lanedirt/186-implement-vault-synchronize-support
Implement vault synchronize/merge support
2024-09-17 22:57:43 +02:00
Leendert de Borst
f1ff5c1d54 Update AttachmentUploader.razor (#186) 2024-09-17 22:56:54 +02:00
Leendert de Borst
90471c362f Refactor (#186) 2024-09-17 22:23:50 +02:00
Leendert de Borst
201f521d60 Add soft delete and cleanup mechanism for merge (#186) 2024-09-17 22:13:12 +02:00
Leendert de Borst
cb25be8962 Add support for overriding Argon2Id settings in client for improved E2E test speed (#186) 2024-09-17 16:57:02 +02:00
Leendert de Borst
a1291b1951 Add E2E test for merge failed situation (#186) 2024-09-17 15:46:21 +02:00
Leendert de Borst
eed66e3c48 Fix db upgrade test (#186) 2024-09-17 14:47:07 +02:00
Leendert de Borst
9544aab2ce Update E2E timeout (#186) 2024-09-17 10:21:21 +02:00
Leendert de Borst
809507d9c3 Mutate UpdatedAt during credential save for all inner objects (#186) 2024-09-17 02:16:53 +02:00
Leendert de Borst
b3e88f9d99 Fix E2E test bug caused by unlock page redirect check (#186) 2024-09-17 02:13:45 +02:00
Leendert de Borst
eda6ad0e44 Add todos (#186) 2024-09-17 01:47:00 +02:00
Leendert de Borst
86eacb0ad8 Add second merge test (#186) 2024-09-17 00:57:37 +02:00
Leendert de Borst
06830baf3d Refactor merge strategy: require client merge before save (#186) 2024-09-17 00:05:15 +02:00
Leendert de Borst
e82faeba40 Add working merge functionality (#186) 2024-09-16 21:09:21 +02:00
Leendert de Borst
f2cf7b123d Add vault merge API endpoint and client side scaffolding (#186) 2024-09-16 20:26:20 +02:00
Leendert de Borst
e499fa9ace Add merge required detection to webapi vault load (#186) 2024-09-16 18:12:11 +02:00
Leendert de Borst
d6e73251d8 Check remember me by default on login (#186) 2024-09-16 16:28:20 +02:00
Leendert de Borst
7ee2984459 Add db sync test (#186) 2024-09-16 16:16:08 +02:00
Leendert de Borst
5b89f163de Add revision number vault retention policy and tests (#186) 2024-09-16 14:11:17 +02:00
Leendert de Borst
291b441d3f Add revision number to vault table (#186) 2024-09-16 13:04:46 +02:00
Leendert de Borst
89751633f1 Add sync support to client database (#186) 2024-09-16 13:04:25 +02:00
Leendert de Borst
50464886f3 Fix delay in navigating to admin log pages (#250) 2024-09-15 16:44:47 +02:00
Leendert de Borst
9d0fc082c0 Refactor (#192) 2024-09-14 21:22:23 +02:00
Leendert de Borst
c85bf5cebd Optimize refresh token logic, add IP address and integrate remember me option (#244) 2024-09-14 21:22:23 +02:00
Leendert de Borst
4f49f343c9 Update release.yml 2024-09-14 21:13:36 +02:00
Leendert de Borst
226b5bfaff Update admin mobile menu content (#244) 2024-09-13 21:09:29 +02:00
dependabot[bot]
73013306d6 Bump Microsoft.Playwright.NUnit from 1.46.0 to 1.47.0
Bumps [Microsoft.Playwright.NUnit](https://github.com/microsoft/playwright-dotnet) from 1.46.0 to 1.47.0.
- [Release notes](https://github.com/microsoft/playwright-dotnet/releases)
- [Commits](https://github.com/microsoft/playwright-dotnet/compare/v1.46.0...v1.47.0)

---
updated-dependencies:
- dependency-name: Microsoft.Playwright.NUnit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-13 16:49:59 +02:00
Leendert de Borst
0b7c641e32 Fix mobile menu open/close (#244) 2024-09-13 16:49:41 +02:00
Leendert de Borst
b7ab70e3de Add PWA basic structure (#202) 2024-09-12 23:05:32 +02:00
Leendert de Borst
f09c27eefa Refactor encryption.cs (#46) 2024-09-11 22:56:36 +02:00
Leendert de Borst
0f4482487b Refactor vault retention manager logic (#46) 2024-09-11 22:56:36 +02:00
Leendert de Borst
edc537316f Change default argon2id settings, persist settings to vault table (#46) 2024-09-11 22:56:36 +02:00
Leendert de Borst
4667ff64e1 Merge pull request #241 from lanedirt/240-improve-playwright-stability
Update Playwright stability
2024-09-10 23:57:57 +02:00
Leendert de Borst
d3d6dc56b8 Remove semaphore as timer with cancellation token prevents race conditions (#240) 2024-09-10 23:48:06 +02:00
Leendert de Borst
b5525f137a Update instructions (#240) 2024-09-10 23:47:21 +02:00
Leendert de Borst
5c619d9553 Fix race condition in E2E tests caused by worker status razor component (#240) 2024-09-10 23:35:10 +02:00
Leendert de Borst
693d419bd9 Update github action workflows (#240) 2024-09-10 22:12:24 +02:00
Leendert de Borst
9bccaba360 Add docs for running github actions locally to aid in debugging (#240) 2024-09-10 20:52:24 +02:00
Leendert de Borst
c4e82205b6 Update Playwright stability (#240) 2024-09-07 01:00:04 +02:00
Leendert de Borst
ef9c9e690f Set parallelism to max 1 for all tests (#125) 2024-09-07 00:14:12 +02:00
Leendert de Borst
a4cc75a3aa Set admin test parallelism to 2 (#125) 2024-09-07 00:14:12 +02:00
Leendert de Borst
633cef3450 Add tweaks to tests for improved stability (#125) 2024-09-07 00:14:12 +02:00
Leendert de Borst
c7af544e25 Refactor logout in tests (#125) 2024-09-07 00:14:12 +02:00
Leendert de Borst
43ae4625dd Improve text and tests (#239) 2024-09-07 00:14:12 +02:00
Leendert de Borst
a53deeeebf Add forgot password screen to client (#125) 2024-09-07 00:14:12 +02:00
Leendert de Borst
f84f063155 Add additional vault metadata (#168) 2024-09-06 14:59:03 +02:00
Leendert de Borst
7f411db4dd Merge pull request #238 from lanedirt/232-add-loading-animation-to-logout-screen
Add loading icon to logout to improve UX
2024-09-06 14:56:33 +02:00
Leendert de Borst
f5ae307fba Add loading icon to logout to improve UX (#232) 2024-09-06 14:56:06 +02:00
Leendert de Borst
f0989aa2d7 Merge pull request #234 from lanedirt/dependabot/nuget/main/Microsoft.NET.Test.Sdk-17.11.1
Bump Microsoft.NET.Test.Sdk from 17.11.0 to 17.11.1
2024-09-06 14:01:53 +02:00
dependabot[bot]
d8ea3fe73c Bump Microsoft.NET.Test.Sdk from 17.11.0 to 17.11.1
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.11.0 to 17.11.1.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.11.0...v17.11.1)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 01:34:00 +00:00
Leendert de Borst
a1c1e86059 Merge pull request #231 from lanedirt/230-auth-log-shows-wrong-ip-address-for-success-entries
Fix bug in auth log IP address
2024-09-02 23:38:43 +02:00
Leendert de Borst
c86a1f84db Fix bug in auth log IP address (#230) 2024-09-02 23:27:42 +02:00
Leendert de Borst
92b5df1cc8 Merge pull request #229 from lanedirt/200-implement-client-password-change-feature
Implement client password change feature
2024-09-02 23:20:37 +02:00
Leendert de Borst
3e7826607f Update DbUpgradeTest after unlock page change (#200) 2024-09-02 23:08:36 +02:00
Leendert de Borst
06f09cdbf1 Add local encryption key validation to unlock page (#200) 2024-09-02 22:51:36 +02:00
Leendert de Borst
81ec09a2ed Fix todos (#200) 2024-09-02 22:24:19 +02:00
Leendert de Borst
7977cd7394 Add admin restore user vault to previous version (#200) 2024-09-02 21:29:18 +02:00
Leendert de Borst
71ad07fad0 Add password change E2E test (#200) 2024-09-02 21:04:33 +02:00
Leendert de Borst
77de70ba82 Update tests (#200) 2024-09-02 20:41:40 +02:00
Leendert de Borst
b97b2163d5 Remove SRP columns from AliasVaultUsers table (#200) 2024-09-02 19:35:57 +02:00
Leendert de Borst
b2aed24d8a Refactored change password to change pass and upload new vault in one atomic webapi operation (#200) 2024-09-02 19:21:18 +02:00
Leendert de Borst
6e6f24417a Move SRP salt and verifier to vaults table (#200) 2024-09-02 17:55:29 +02:00
Leendert de Borst
725efcfa91 Merge pull request #226 from lanedirt/dependabot/nuget/main/NUnit-4.2.2
Bump NUnit from 3.14.0 to 4.2.2
2024-09-02 17:45:34 +02:00
Leendert de Borst
71c326bc55 Add password change functionality (#200) 2024-09-02 14:47:11 +02:00
Leendert de Borst
4b1feca11d Set misc versions back to their correct versions after merge conflict (#226) 2024-09-02 10:48:24 +02:00
Leendert de Borst
02f9571b8b Update NUnit version to 4.2.2. for all test projects (#226) 2024-09-02 10:44:25 +02:00
Leendert de Borst
9e3b08c50d Merge pull request #227 from lanedirt/dependabot/nuget/main/Microsoft.AspNetCore.Components.WebAssembly.DevServer-8.0.8
Bump Microsoft.AspNetCore.Components.WebAssembly.DevServer from 8.0.7 to 8.0.8
2024-09-02 10:34:35 +02:00
Leendert de Borst
84ac36b1e2 Merge pull request #225 from lanedirt/dependabot/nuget/main/Microsoft.AspNetCore.Mvc.Testing-8.0.8
Bump Microsoft.AspNetCore.Mvc.Testing from 8.0.7 to 8.0.8
2024-09-02 10:34:26 +02:00
Leendert de Borst
690547bbf2 Merge pull request #224 from lanedirt/dependabot/nuget/main/Microsoft.Playwright.NUnit-1.46.0
Bump Microsoft.Playwright.NUnit from 1.45.1 to 1.46.0
2024-09-02 10:34:21 +02:00
Leendert de Borst
8d00ee496f Merge pull request #223 from lanedirt/dependabot/nuget/main/Microsoft.EntityFrameworkCore-8.0.8
Bump Microsoft.EntityFrameworkCore from 8.0.7 to 8.0.8
2024-09-02 10:34:14 +02:00
dependabot[bot]
aea8cbf405 Bump Microsoft.AspNetCore.Components.WebAssembly.DevServer
Bumps [Microsoft.AspNetCore.Components.WebAssembly.DevServer](https://github.com/dotnet/aspnetcore) from 8.0.7 to 8.0.8.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.DevServer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 01:44:38 +00:00
dependabot[bot]
0d3a2032a2 Bump NUnit from 3.14.0 to 4.2.2
Bumps [NUnit](https://github.com/nunit/nunit) from 3.14.0 to 4.2.2.
- [Release notes](https://github.com/nunit/nunit/releases)
- [Changelog](https://github.com/nunit/nunit/blob/main/CHANGES.md)
- [Commits](https://github.com/nunit/nunit/compare/v3.14.0...4.2.2)

---
updated-dependencies:
- dependency-name: NUnit
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 01:44:08 +00:00
dependabot[bot]
d619976b10 Bump Microsoft.AspNetCore.Mvc.Testing from 8.0.7 to 8.0.8
Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 8.0.7 to 8.0.8.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Mvc.Testing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 01:43:25 +00:00
dependabot[bot]
72034391fb Bump Microsoft.Playwright.NUnit from 1.45.1 to 1.46.0
Bumps [Microsoft.Playwright.NUnit](https://github.com/microsoft/playwright-dotnet) from 1.45.1 to 1.46.0.
- [Release notes](https://github.com/microsoft/playwright-dotnet/releases)
- [Commits](https://github.com/microsoft/playwright-dotnet/compare/v1.45.1...v1.46.0)

---
updated-dependencies:
- dependency-name: Microsoft.Playwright.NUnit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 01:42:45 +00:00
dependabot[bot]
5cda059a91 Bump Microsoft.EntityFrameworkCore from 8.0.7 to 8.0.8
Bumps [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore) from 8.0.7 to 8.0.8.
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 01:42:04 +00:00
Leendert de Borst
3109135a17 Merge pull request #222 from lanedirt/180-add-log-truncate-option-to-admin-project 2024-09-01 19:58:09 +02:00
Leendert de Borst
eca61933bf Add log truncate buttons to admin (#180) 2024-09-01 17:31:53 +02:00
Leendert de Borst
df72068e5c Merge pull request #213 from lanedirt/dependabot/nuget/main/Microsoft.NET.Test.Sdk-17.11.0
Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.11.0
2024-09-01 15:54:20 +02:00
Leendert de Borst
f093958833 Update Microsoft.NET.Test.Sdk to latest version for all test projects (#213) 2024-09-01 15:47:13 +02:00
Leendert de Borst
d98ac5e61d Merge pull request #219 from lanedirt/dependabot/nuget/main/multi-6114d871cd
Bump Microsoft.AspNetCore.Components.WebAssembly and Microsoft.AspNetCore.Components.Web
2024-09-01 15:46:17 +02:00
Leendert de Borst
7a730ac944 Merge pull request #208 from lanedirt/dependabot/nuget/main/NUnit.Analyzers-4.3.0
Bump NUnit.Analyzers from 3.9.0 to 4.3.0
2024-09-01 15:46:09 +02:00
Leendert de Borst
a99e370b1c Merge pull request #218 from lanedirt/217-ignore-empty-fields-during-encryptiondecryption
Prevent error when attempting to encrypt/decrypt empty string (#217)
2024-09-01 15:42:01 +02:00
dependabot[bot]
94ad6e9ea0 Bump Microsoft.AspNetCore.Components.WebAssembly and Microsoft.AspNetCore.Components.Web
Bumps [Microsoft.AspNetCore.Components.WebAssembly](https://github.com/dotnet/aspnetcore) and [Microsoft.AspNetCore.Components.Web](https://github.com/dotnet/aspnetcore). These dependencies needed to be updated together.

Updates `Microsoft.AspNetCore.Components.WebAssembly` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

Updates `Microsoft.AspNetCore.Components.Web` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.AspNetCore.Components.Web
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:33:12 +00:00
Leendert de Borst
eaec5447f5 Update NUnitAnalyzers in test projects (#208) 2024-09-01 15:33:01 +02:00
Leendert de Borst
c3aee4df8f Merge pull request #211 from lanedirt/dependabot/nuget/main/Swashbuckle.AspNetCore-6.7.3
Bump Swashbuckle.AspNetCore from 6.7.1 to 6.7.3
2024-09-01 15:30:55 +02:00
Leendert de Borst
27b0820906 Merge pull request #210 from lanedirt/dependabot/nuget/main/coverlet.collector-6.0.2
Bump coverlet.collector from 6.0.0 to 6.0.2
2024-09-01 15:30:47 +02:00
Leendert de Borst
1330e78169 Merge pull request #215 from lanedirt/dependabot/nuget/main/multi-7a460b1692
Bump Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore
2024-09-01 15:29:46 +02:00
Leendert de Borst
739c54d821 Merge pull request #214 from lanedirt/dependabot/nuget/main/multi-d0f7e4fcac
Bump Microsoft.AspNetCore.Components.WebAssembly.Authentication, Microsoft.AspNetCore.Authorization and Microsoft.AspNetCore.Components.Web
2024-09-01 15:29:38 +02:00
Leendert de Borst
0e8e5bf2ad Merge pull request #212 from lanedirt/dependabot/nuget/main/Microsoft.AspNetCore.Components.WebAssembly.Server-8.0.8
Bump Microsoft.AspNetCore.Components.WebAssembly.Server from 8.0.7 to 8.0.8
2024-09-01 15:29:25 +02:00
Leendert de Borst
f235e72f01 Merge pull request #207 from lanedirt/dependabot/nuget/main/HtmlAgilityPack-1.11.65
Bump HtmlAgilityPack from 1.11.61 to 1.11.65
2024-09-01 15:28:55 +02:00
Leendert de Borst
9b250bf83f Prevent error when attempting to encrypt/decrypt empty string (#217) 2024-09-01 15:20:49 +02:00
dependabot[bot]
138ffcb7a6 Bump Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore
Bumps [Microsoft.EntityFrameworkCore.Sqlite](https://github.com/dotnet/efcore) and [Microsoft.EntityFrameworkCore](https://github.com/dotnet/efcore). These dependencies needed to be updated together.

Updates `Microsoft.EntityFrameworkCore.Sqlite` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.7...v8.0.8)

Updates `Microsoft.EntityFrameworkCore` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/efcore/releases)
- [Commits](https://github.com/dotnet/efcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.EntityFrameworkCore.Sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.EntityFrameworkCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:04:53 +00:00
dependabot[bot]
7da152a412 Bump Microsoft.AspNetCore.Components.WebAssembly.Authentication, Microsoft.AspNetCore.Authorization and Microsoft.AspNetCore.Components.Web
Bumps [Microsoft.AspNetCore.Components.WebAssembly.Authentication](https://github.com/dotnet/aspnetcore), [Microsoft.AspNetCore.Authorization](https://github.com/dotnet/aspnetcore) and [Microsoft.AspNetCore.Components.Web](https://github.com/dotnet/aspnetcore). These dependencies needed to be updated together.

Updates `Microsoft.AspNetCore.Components.WebAssembly.Authentication` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

Updates `Microsoft.AspNetCore.Authorization` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

Updates `Microsoft.AspNetCore.Components.Web` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.Authentication
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.AspNetCore.Authorization
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.AspNetCore.Components.Web
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:04:32 +00:00
dependabot[bot]
ec4e2d2c80 Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.11.0
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.11.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.11.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:04:07 +00:00
dependabot[bot]
984d8512e9 Bump Microsoft.AspNetCore.Components.WebAssembly.Server
Bumps [Microsoft.AspNetCore.Components.WebAssembly.Server](https://github.com/dotnet/aspnetcore) from 8.0.7 to 8.0.8.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v8.0.7...v8.0.8)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Components.WebAssembly.Server
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:03:23 +00:00
dependabot[bot]
e2e0b81564 Bump Swashbuckle.AspNetCore from 6.7.1 to 6.7.3
Bumps [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 6.7.1 to 6.7.3.
- [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases)
- [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.7.1...v6.7.3)

---
updated-dependencies:
- dependency-name: Swashbuckle.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:03:11 +00:00
dependabot[bot]
770ee60402 Bump coverlet.collector from 6.0.0 to 6.0.2
Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.0 to 6.0.2.
- [Release notes](https://github.com/coverlet-coverage/coverlet/releases)
- [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.0...v6.0.2)

---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:02:45 +00:00
dependabot[bot]
2aa7d1ce60 Bump NUnit.Analyzers from 3.9.0 to 4.3.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 3.9.0 to 4.3.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/3.9.0...4.3.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:01:34 +00:00
dependabot[bot]
578dd9da87 Bump HtmlAgilityPack from 1.11.61 to 1.11.65
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.61 to 1.11.65.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.61...v1.11.65)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-01 13:00:51 +00:00
Leendert de Borst
b129a75255 Merge pull request #206 from lanedirt/203-optimize-github-actions-with-retry-to-prevent-false-negatives
Optimize GitHub actions for stability (#203)
2024-09-01 14:58:45 +02:00
Leendert de Borst
7ec2594d7f Create dependabot.yml (#203) 2024-09-01 14:38:51 +02:00
Leendert de Borst
e1f729f2ed Add retry to GitHub actions, refactor (#203) 2024-09-01 14:38:43 +02:00
Leendert de Borst
e53d1931c5 Merge pull request #204 from lanedirt/185-check-gzipbrotli-compression-for-client-app
Add gzip compression for client app
2024-09-01 14:04:17 +02:00
Leendert de Borst
4373e6fa62 Separate cryptography project to server and client to reduce client WASM size (#185) 2024-09-01 13:52:53 +02:00
Leendert de Borst
5ecdf926b6 Add gzip compression to client nginx (#185) 2024-09-01 13:52:10 +02:00
Leendert de Borst
e47b109f9d Merge pull request #198 from lanedirt/188-add-email-anchor-tags-translation-to-open-in-new-tabs-instead-of-iframe
Fix email anchor tag target=blank conversion
2024-09-01 12:58:10 +02:00
Leendert de Borst
be244b2c68 Refactor (#188) 2024-09-01 12:46:26 +02:00
Leendert de Borst
155a3ccd0b Merge pull request #201 from lanedirt/199-add-auth-log-entry-for-registration-which-also-includes-sign-in-action
Add user registration log entry and include auth log E2E tests
2024-08-31 21:07:10 +02:00
Leendert de Borst
5a16495864 Add user registration log entry and include auth log E2E tests (#199) 2024-08-31 20:50:57 +02:00
Leendert de Borst
b0c74d3ce2 Merge branch 'main' into 188-add-email-anchor-tags-translation-to-open-in-new-tabs-instead-of-iframe 2024-08-31 19:18:59 +02:00
Leendert de Borst
46360e2f4a Merge pull request #197 from lanedirt/171-add-email-delete-option-in-client
Add email delete option in client
2024-08-31 19:18:18 +02:00
Leendert de Borst
f2378f8e7f Merge pull request #195 from lanedirt/184-first-time-loading-animation-does-not-wait-for-app-to-load-correctly
Fix client app loading screen delay
2024-08-31 19:12:58 +02:00
Leendert de Borst
ca6aa40850 Update EmailModal.razor (#171) 2024-08-31 19:09:07 +02:00
Leendert de Borst
b015e4a9d6 Move anchor tag conversion from api to client (#188) 2024-08-31 19:07:53 +02:00
Leendert de Borst
8dc4bcb06f Update E2E test for email delete button (#171) 2024-08-31 17:51:45 +02:00
Leendert de Borst
466c181ad1 Add email delete option to email modal (#184) 2024-08-31 17:26:12 +02:00
Leendert de Borst
cd4dc918cb Update index.template.html loading screen delay (#184) 2024-08-31 16:24:03 +02:00
Leendert de Borst
18cea13ddd Merge pull request #194 from lanedirt/80-add-vault-auth-attempt-logging-bugfix
Update admin menu links
2024-08-31 15:44:12 +02:00
Leendert de Borst
ab5795101f Update admin menu links (#80) 2024-08-31 15:43:55 +02:00
Leendert de Borst
600d7bcbda Merge pull request #193 from lanedirt/80-add-vault-auth-attempt-logging-bugfix
Update admin logs path as /logs folder doesn't work correctly due to …
2024-08-31 15:30:59 +02:00
Leendert de Borst
25b908e311 Update admin logs path as /logs folder doesn't work correctly due to .gitignore (#80) 2024-08-31 15:30:06 +02:00
Leendert de Borst
0269d584aa Merge pull request #191 from lanedirt/80-add-vault-auth-attempt-logging-and-auto-lockout-to-combat-brute-force 2024-08-30 22:38:57 +02:00
Leendert de Borst
072e63e98f Refactor and cleanup (#80) 2024-08-30 22:04:28 +02:00
Leendert de Borst
95949508ba Add E2E tests for account lockout for password, 2fa and recovery (#80) 2024-08-30 21:11:40 +02:00
Leendert de Borst
1564df342a Add admin e2e tests for password and 2fa lockout (#80) 2024-08-30 19:57:05 +02:00
Leendert de Borst
1b2a6029bb Update tests (#80) 2024-08-30 17:47:14 +02:00
Leendert de Borst
c131372e37 Update E2E test config (#80) 2024-08-30 17:38:59 +02:00
Leendert de Borst
b830d90ba4 Update Lockout.razor (#80) 2024-08-30 17:21:14 +02:00
Leendert de Borst
e6feafcb87 Add recent auth logs to client (#80) 2024-08-30 17:19:28 +02:00
Leendert de Borst
52e55e44f2 Add database service builder abstraction (#80) 2024-08-30 16:35:56 +02:00
Leendert de Borst
1945b15e2e Add active sessions component to client (#80) 2024-08-30 16:28:22 +02:00
Leendert de Borst
4f8ab5da28 Add new user sessions endpoint to webapi (#80) 2024-08-30 13:54:31 +02:00
Leendert de Borst
188b1cba94 Update AuthLoggingService.cs (#80) 2024-08-30 13:19:47 +02:00
Leendert de Borst
5da1021088 Add user refresh tokens table to admin user view and revoke option (#80) 2024-08-27 23:25:02 +02:00
Leendert de Borst
21ae755018 Add auth logging table and log methods to admin and api (#80) 2024-08-27 23:05:14 +02:00
Leendert de Borst
ca3e35e066 Merge pull request #182 from lanedirt/70-add-totp-authenticator-support-to-client
Add TOTP authenticator support to client
2024-08-26 16:30:02 +02:00
Leendert de Borst
7d6a5fa947 Update Security.razor (#70)
Update Login.razor (#70)
Code style refactor (#70)
Update JSON models to have required properties (#70)
2024-08-26 16:23:55 +02:00
Leendert de Borst
a9e41fa6b4 Refactor 2fa methods from AuthController to its own controller (#70) 2024-08-26 15:21:50 +02:00
Leendert de Borst
f6f33c2482 Add E2E test for 2-FA recovery codes (#70) 2024-08-26 14:31:43 +02:00
Leendert de Borst
9ef078bd57 Add recovery code support to client 2FA login flow (#70) 2024-08-26 12:36:15 +02:00
Leendert de Borst
329281cd53 Refactor two-factor auth setup in client (#70) 2024-08-24 18:24:24 +02:00
Leendert de Borst
1db9fa5a37 Add persistent 2FA warning to admin app if 2FA is not set (#70) 2024-08-23 19:29:03 +02:00
Leendert de Borst
8ba039ff25 Add admin option to disable 2FA for user (#70) 2024-08-23 19:12:25 +02:00
Leendert de Borst
0df0b2c3ff Add unittests for TotpGenerator logic (#70) 2024-08-23 18:25:51 +02:00
Leendert de Borst
e058990e31 Add E2E test for client two-factor auth setup (#70) 2024-08-23 18:20:54 +02:00
Leendert de Borst
f7865e5d9c Add working basic 2FA login flow to client (#70) 2024-08-23 17:44:28 +02:00
Leendert de Borst
7aeb34ec5f Update install.sh (#70) 2024-08-23 11:08:11 +02:00
Leendert de Borst
1f9400e811 Merge pull request #179 from lanedirt/130-make-dataprotection-work-in-both-local-development-and-when-running-through-docker
Make .NET dataprotection work in both local development and when running through docker
2024-08-21 15:03:37 -07:00
Leendert de Borst
a3e46f28a3 Refactor DataProtection setup to common extension class (#130) 2024-08-21 23:54:18 +02:00
Leendert de Borst
01f026a3d3 Add new required env vars to E2E test factories (#130) 2024-08-21 23:41:29 +02:00
Leendert de Borst
8824db222b Optimize settings to reduce irrelevant warnings in logs (#130) 2024-08-21 23:00:17 +02:00
Leendert de Borst
dce170cee1 Add certificate generation for DataProtection API for both local dev and Docker containers (#130) 2024-08-21 21:42:20 +02:00
Leendert de Borst
ef7a11e27a Update README.md (#130) 2024-08-20 18:06:15 +02:00
Leendert de Borst
75b22cfddf Merge pull request #176 from lanedirt/174-make-emailusername-on-login-case-insensitive
Change email to username for main user authentication
2024-08-19 14:41:41 -07:00
Leendert de Borst
65342a2a8d Change email to username for main user authentication (#174) 2024-08-19 23:33:50 +02:00
Leendert de Borst
18978b94be Merge pull request #173 from lanedirt/164-add-oobe-beginning-screen-if-user-does-not-have-any-credentials-yet
Out-of-box experience UX tweaks
2024-08-16 06:08:30 -07:00
Leendert de Borst
c989573565 Update Vault.razor (#164) 2024-08-16 14:57:21 +02:00
Leendert de Borst
67ce7da21a Refactor (#164) 2024-08-16 14:48:40 +02:00
Leendert de Borst
fb2972695a Update E2E tests (#164) 2024-08-16 14:35:18 +02:00
Leendert de Borst
2f47f81af8 Fix bug in email credential lookup query (#164) 2024-08-16 13:38:24 +02:00
Leendert de Borst
6d6ee8bf3f Add enter on form submit for AddEdit page, refactor service URL placeholder logic (#164) 2024-08-16 13:34:58 +02:00
Leendert de Borst
881eb58a35 Add focus tweaks to Credentials AddEdit page (#164) 2024-08-16 13:27:07 +02:00
Leendert de Borst
80bc7cd223 Add welcome page for new users for OOBE (#164) 2024-08-16 12:25:52 +02:00
Leendert de Borst
87f494fea8 Layout tweaks (#164) 2024-08-16 12:25:28 +02:00
Leendert de Borst
a24e533e4c Tweak settings page layout (#164) 2024-08-16 12:24:52 +02:00
Leendert de Borst
ebb8b27f85 Update DbStatusIndicator.razor (#164) 2024-08-16 12:24:40 +02:00
Leendert de Borst
41c210e75a Add minimum loading screen delay to blazor bootstrap to improve UX (#164) 2024-08-15 21:52:21 +02:00
Leendert de Borst
2a50a455d8 Merge pull request #170 from lanedirt/165-add-styled-wasm-loading-animation
Updated blazor loading animation to AliasVault style
2024-08-13 11:05:31 -07:00
Leendert de Borst
6896c4cd1d Updated blazor loading animation to AliasVault style (#165) 2024-08-13 19:05:15 +02:00
Leendert de Borst
9560572a40 Merge pull request #169 from lanedirt/144-update-client-side-validation-for-all-form-steps
Update client side validation for all form steps
2024-08-12 11:49:15 -07:00
Leendert de Borst
4dffb9c3c0 Change StartsWith overload (#144) 2024-08-12 20:39:12 +02:00
Leendert de Borst
b8cb3c4d78 Add username generate button, fix form validation bugs, tweak UI (#144) 2024-08-12 19:07:39 +02:00
Leendert de Borst
6f54b05d5a Update email style (#144) 2024-08-12 16:15:27 +02:00
Leendert de Borst
d051d69aea Merge pull request #166 from lanedirt/160-rework-credential-view-page-to-show-most-relevant-data-first
Add email page to browse through all received emails
2024-08-12 04:39:21 -07:00
Leendert de Borst
02f0c43cbd Code style refactor (#160) 2024-08-12 13:31:20 +02:00
Leendert de Borst
14cce42091 Add email page to browser through all received emails for all claimed email addresses(#160) 2024-08-12 13:20:20 +02:00
Leendert de Borst
a1c26cec04 Merge pull request #163 from lanedirt/158-add-global-search-bar
Add global search bar
2024-08-09 08:55:22 -07:00
Leendert de Borst
42fc1c018c Add E2E test for global search bar (#158) 2024-08-09 17:47:42 +02:00
Leendert de Borst
f3e740bab3 Add global search bar widget (#158) 2024-08-09 13:51:02 +02:00
Leendert de Borst
bbdf47d6f4 Merge pull request #162 from lanedirt/161-keyboard-shortcuts-stop-working-when-something-else-has-been-typed-before 2024-08-07 22:15:09 -07:00
Leendert de Borst
5faf93d6be Fix CredentialTest, replace wait text after breadcrumb change (#161) 2024-08-07 23:55:47 +02:00
Leendert de Borst
fa1573ee13 Update keyboardShortcuts.js, fix bug (#161) 2024-08-07 23:27:44 +02:00
Leendert de Borst
50f7866a0b Improve GlobalNotificationDisplay system (#161) 2024-08-07 23:25:25 +02:00
Leendert de Borst
7b1a1e893e Merge pull request #159 from lanedirt/142-design-new-client-datamodel-structure-for-credentialsaliases-with-simplified-user-flow
Add quick create new identity popup
2024-08-07 13:39:59 -07:00
Leendert de Borst
40afea3908 Fix parallel E2E tests race condition (#142) 2024-08-07 22:33:58 +02:00
Leendert de Borst
e1ae260fc5 Code style refactor (#142) 2024-08-07 22:28:46 +02:00
Leendert de Borst
c33399b91d Add E2E test for quick create widget (#142) 2024-08-07 22:24:34 +02:00
Leendert de Borst
f46202223a Fix tests (#142) 2024-08-07 22:01:37 +02:00
Leendert de Borst
0867573f2f Load specific JS via isolated modules, refactor CredentialService (#142) 2024-08-07 20:39:39 +02:00
Leendert de Borst
2becb3aa8f Refactor (#142) 2024-08-06 22:04:12 +02:00
Leendert de Borst
dc2f4dd040 Add quick create new identity popup (#142) 2024-08-06 20:29:48 +02:00
Leendert de Borst
2cf3c142da Merge pull request #157 from lanedirt/156-add-e2e-test-for-generating-identity-via-client-gui
Add E2E test for identity generation in client (#156)
2024-08-05 13:53:21 -07:00
Leendert de Borst
a8d84fd38a Update CredentialTest.cs (#156) 2024-08-05 22:43:03 +02:00
Leendert de Borst
4a207763cc Add E2E test for identity generation in client (#156) 2024-08-05 21:18:21 +02:00
Leendert de Borst
b1ef5c33db Merge pull request #155 from lanedirt/108-add-identity-generator-scaffolding-utility-project
Add identity generator utility project for EN and NL identities
2024-08-05 11:35:56 -07:00
Leendert de Borst
578532efdf Code style refactor (#108) 2024-08-05 20:21:48 +02:00
Leendert de Borst
95fb8baaaa Add nonbacktracking option to regexes (#108) 2024-08-05 20:20:28 +02:00
Leendert de Borst
73e432b2dc Refactor identity generation logic (#108) 2024-08-05 17:24:51 +02:00
Leendert de Borst
f43c3171b0 Add local dictionary based identity generation (#108) 2024-08-05 16:34:22 +02:00
Leendert de Borst
364ade9181 Merge pull request #154 from lanedirt/146-add-e2e-test-with-two-users-trying-to-claim-the-same-email
Add e2e test with two users trying to claim the same email
2024-08-05 04:30:19 -07:00
Leendert de Borst
8883c87dfb Fix email conflict bug with multiple tests in same class (#146) 2024-08-05 13:25:15 +02:00
Leendert de Borst
8e35b39197 Update README.md (#146) 2024-08-05 13:14:33 +02:00
Leendert de Borst
79fd941b4e Add E2E test for checking duplicate email claim error (#146) 2024-08-05 13:14:25 +02:00
Leendert de Borst
b317407bfe Merge pull request #153 from lanedirt/145-add-client-settings-page-with-preference-for-default-domain-and-auto-email-refresh
Add client settings page with preference for default domain and auto email refresh
2024-08-05 04:04:50 -07:00
Leendert de Borst
885630b5db Refactor (#145) 2024-08-05 12:57:25 +02:00
Leendert de Borst
cc64f5c877 Add E2E test for client general settings page (#145) 2024-08-05 12:04:54 +02:00
Leendert de Borst
7d358e0c00 Implement general settings on credential page (#145) 2024-08-05 11:38:47 +02:00
Leendert de Borst
eacfee78cc Refactor SettingsService structure so it initializes when the DbService itself is ready (#145) 2024-08-05 11:05:51 +02:00
Leendert de Borst
d4a773fc2c Add settings table and service to client project (#145) 2024-08-05 09:57:33 +02:00
Leendert de Borst
540124cabf Merge pull request #152 from lanedirt/148-improve-email-popup-window-mechanism
Improve email popup window mechanism
2024-08-02 08:11:20 -07:00
Leendert de Borst
6db2b33576 Add IDisposable (#148) 2024-08-02 17:11:08 +02:00
Leendert de Borst
a132bfea65 Refactor EmailModal.razor (#148) 2024-08-02 16:56:46 +02:00
Leendert de Borst
d9f929ec63 Add encryption to email attachments (#148) 2024-08-02 16:41:23 +02:00
Leendert de Borst
f6f00bec3b Make ClickOutsideHandler component work (#148) 2024-08-02 16:19:08 +02:00
Leendert de Borst
798f8623d4 Email UI tweaks WIP (#148) 2024-08-02 11:30:44 +02:00
Leendert de Borst
27174c05ab Merge pull request #149 from lanedirt/137-improve-credential-email-generation-ui
Admin dashboard tweaks
2024-08-01 11:49:45 -07:00
Leendert de Borst
b8b95babe0 Admin dashboard tweaks (#137) 2024-08-01 20:41:45 +02:00
Leendert de Borst
741b514441 Merge pull request #147 from lanedirt/137-improve-credential-email-generation-ui 2024-07-31 14:31:53 -07:00
Leendert de Borst
f8493f2ff6 Update email field UX (#137) 2024-07-31 22:24:06 +02:00
Leendert de Borst
6f15026495 Add auto retry to E2E test for GitHub Actions as its prone to fail sometimes (#137) 2024-07-31 22:16:03 +02:00
Leendert de Borst
b9acaef46b Update RecentEmails.razor (#137) 2024-07-31 22:12:24 +02:00
Leendert de Borst
c0d8b9941d Client UI tweaks to email and password fields (#137) 2024-07-31 22:04:53 +02:00
Leendert de Borst
e44b52d357 Merge pull request #140 from lanedirt/139-smtpserver-process-is-consuming-100-cpu
Fix while loop high CPU usage bug
2024-07-30 14:04:44 -07:00
Leendert de Borst
1b79662113 Fix while loop high CPU usage bug (#139) 2024-07-30 22:55:55 +02:00
Leendert de Borst
eb2eadf14d Merge pull request #138 from lanedirt/117-add-email-ui-to-client-wasm-application-for-local-and-external-email
Add email encryption, add UI to client wasm application for local and external email
2024-07-30 13:21:57 -07:00
Leendert de Borst
175760cae6 Update DatabaseMessageStore.cs (#117) 2024-07-30 22:10:35 +02:00
Leendert de Borst
486dc67f94 Improve smtp server logic (#117) 2024-07-30 22:01:09 +02:00
Leendert de Borst
1609562499 Add test for full encryption/decryption flow (#117) 2024-07-30 18:36:08 +02:00
Leendert de Borst
31429fb5f5 Code style refactor (#117) 2024-07-29 23:32:25 +02:00
Leendert de Borst
ad7e9ea5ba Fix E2E tests for client project (#117) 2024-07-29 23:02:04 +02:00
Leendert de Borst
4c672a0ebe Added working client side decryption of emails (#117) 2024-07-29 22:51:56 +02:00
Leendert de Borst
05a2e3942c Add email view modal for external API (#117) 2024-07-29 17:59:38 +02:00
Leendert de Borst
fabb087874 Add user claims list to admin page (#117) 2024-07-29 16:50:58 +02:00
Leendert de Borst
c266fedd89 Add encryption logic to SmtpServer and integration tests (#117) 2024-07-29 16:39:06 +02:00
Leendert de Borst
e64893c26c Add JSInterop RSA methods, refactor JSInterop on client (#117) 2024-07-29 14:06:11 +02:00
Leendert de Borst
2016117d47 Add PKI tables (#117) 2024-07-29 11:18:38 +02:00
Leendert de Borst
7fd2b9d678 Merge pull request #134 from lanedirt/133-add-cache-busting-to-admin-app
Add cache busting to admin app
2024-07-29 00:25:35 -07:00
Leendert de Borst
1d5c5162e2 Make method static (#133) 2024-07-29 09:25:26 +02:00
Leendert de Borst
6407e1920f Update E2E test with VersionedContentService (#134) 2024-07-28 17:04:27 +02:00
Leendert de Borst
3bdc0f1171 Add cache busting to admin app (#133) 2024-07-28 16:53:40 +02:00
Leendert de Borst
a0f976f075 Merge pull request #132 from lanedirt/126-add-user-vault-statistics-to-admin-app
Add user vault statistics to admin app
2024-07-28 07:37:14 -07:00
Leendert de Borst
35104ce429 Do not run admin and client tests in parallel as it causes issues with the in-memory SQLite db (#126) 2024-07-28 16:19:34 +02:00
Leendert de Borst
ce43c1b2c0 Add filter by servicename (#126) 2024-07-28 15:47:53 +02:00
Leendert de Borst
00cc482342 Add user management to admin (#126) 2024-07-28 15:29:51 +02:00
Leendert de Borst
9e8521fa10 Fix dbcontext refresh in workerstatus blazor (#126) 2024-07-28 12:22:30 +02:00
Leendert de Borst
cdea2106b3 Update WorkerStatus blazor to auto refresh (#126) 2024-07-28 12:11:16 +02:00
Leendert de Borst
7cf03da0ee Merge pull request #131 from lanedirt/113-add-blazor-server-admin-project-for-user-and-smtp-management
Fix bug in install.sh (#113)
2024-07-26 14:36:58 -07:00
Leendert de Borst
cb8f677cdf Fix bug in install.sh (#113) 2024-07-26 23:36:35 +02:00
Leendert de Borst
771d82e35f Merge pull request #123 from lanedirt/113-add-blazor-server-admin-project-for-user-and-smtp-management
Add blazor server admin project for user and smtp management
2024-07-26 14:25:22 -07:00
Leendert de Borst
670dea6924 Improve tests (#113) 2024-07-26 23:14:26 +02:00
Leendert de Borst
d8cfdc2123 Updated log location for all services (#113) 2024-07-26 22:22:19 +02:00
Leendert de Borst
ad8ceff2a8 Add E2E test for API project and logging (#113) 2024-07-26 19:59:26 +02:00
Leendert de Borst
1e93c0786f Add E2E tests for admin project (#113) 2024-07-26 17:34:17 +02:00
Leendert de Borst
152ad6c973 Fix typo (#113) 2024-07-26 14:18:43 +02:00
Leendert de Borst
f51dd0b0cb Add StatusWorker control to admin project (#113) 2024-07-26 14:14:44 +02:00
Leendert de Borst
b06c00283d Update install.sh (#113) 2024-07-26 11:36:14 +02:00
Leendert de Borst
85fbb283c3 Add uninstall.sh script (#113) 2024-07-26 11:24:56 +02:00
Leendert de Borst
4cbedc7034 Code style refactor (#113) 2024-07-26 11:24:39 +02:00
Leendert de Borst
1e9dd71a7a Refactor StatusWorker library (#113) 2024-07-26 02:56:19 +02:00
Leendert de Borst
8d9f5ba302 Stable SmtpServerWorker with statusworker monitoring (#113) 2024-07-26 01:02:21 +02:00
Leendert de Borst
5e18ea163f Fix code style issues (#113) 2024-07-26 00:09:54 +02:00
Leendert de Borst
2f7a5acf42 Update packages, add dynamic service start/stop logic WIP (#113) 2024-07-26 00:07:51 +02:00
Leendert de Borst
99cc429779 Fix logs page CSS (#113) 2024-07-25 19:51:38 +02:00
Leendert de Borst
f0335b485e Add searchable logs page to admin app (#113) 2024-07-24 23:10:33 +02:00
Leendert de Borst
fc8f935092 Update DatabaseSink logic (#113) 2024-07-24 22:11:08 +02:00
Leendert de Borst
d5cf51b5da Add improved logging for SmtpService (#113) 2024-07-24 18:31:23 +02:00
Leendert de Borst
1ae5143fb7 Update install.sh dependencies (#113) 2024-07-23 21:09:11 +02:00
Leendert de Borst
ac284ba71a Update install.sh script (#113) 2024-07-23 21:06:37 +02:00
Leendert de Borst
bf68e380bc Fix admin bugs (#113) 2024-07-22 23:57:37 +02:00
Leendert de Borst
d87800f370 Fix docker build for admin project (#113) 2024-07-22 23:35:43 +02:00
Leendert de Borst
d65db96447 Fix analyzer issues, update docker compose (#113) 2024-07-22 23:04:08 +02:00
Leendert de Borst
b2e344c523 Refactor codestyle issues (#113) 2024-07-22 22:36:16 +02:00
Leendert de Borst
2b9d7d2818 Fix warnings (#113) 2024-07-22 21:34:36 +02:00
Leendert de Borst
d79c2d34a5 Delete old admin project, rename admin2 to admin (#113) 2024-07-22 17:32:12 +02:00
Leendert de Borst
586aafe1f1 Cleanup of old pages, reset 2FA when updating admin password through CLI (#113) 2024-07-22 17:24:56 +02:00
Leendert de Borst
6cb017af1c Add admin db project password seeding logic, extended init.sh (#113) 2024-07-22 17:14:21 +02:00
Leendert de Borst
25462e38bd Fix auth redirect (#123) 2024-07-22 13:16:25 +02:00
Leendert de Borst
4ba6c365a5 Add redirect to login page when already logged in (#113) 2024-07-22 12:52:58 +02:00
Leendert de Borst
aa5d229687 Refactor admin project folder structure (#113) 2024-07-22 11:47:39 +02:00
Leendert de Borst
022370f799 Update manage account page style (#113) 2024-07-22 11:38:58 +02:00
Leendert de Borst
050470453a Admin project refactor (#113) 2024-07-22 11:03:58 +02:00
Leendert de Borst
5a2353fb11 Style login/2FA pages with tailwind CSS (#113) 2024-07-22 00:24:39 +02:00
Leendert de Borst
c73769750c Add pagetitle component to admin, refactoring (#113) 2024-07-21 21:00:53 +02:00
Leendert de Borst
8cda9c06a2 Replace bootstrap with tailwind for admin (#113) 2024-07-21 20:38:57 +02:00
Leendert de Borst
6d16ff234a Update admin scaffolding with full Blazor interactive server routes (#113) 2024-07-21 19:40:08 +02:00
Leendert de Borst
220fbe2be2 Update account login URLs (#113) 2024-07-21 16:36:43 +02:00
Leendert de Borst
64d924d8a4 Add new admin project from scratch with improved identity scaffolding (#113) 2024-07-21 15:14:19 +02:00
Leendert de Borst
da2615096e Update admin project folder structure (#113) 2024-07-21 14:48:27 +02:00
Leendert de Borst
87f2997ce8 Code style refactor (#113) 2024-07-21 00:17:20 +02:00
Leendert de Borst
467943ec49 Make admin and aliasvault user table definitions work together (#113) 2024-07-20 23:20:27 +02:00
Leendert de Borst
902147cbf6 Add admin project, add separate admin and user identity tables (#113) 2024-07-20 12:59:03 +02:00
Leendert de Borst
b165969598 Merge pull request #122 from lanedirt/111-add-e2eunit-test-for-email-smtp-service
Fix SMTP Dockerfile permissions
2024-07-19 12:50:42 -07:00
Leendert de Borst
39ab7558f9 Remove user statement from SMTP Dockerfile as it conflicts with file permissions (#111) 2024-07-19 21:44:17 +02:00
Leendert de Borst
600c4d32ab Merge pull request #120 from lanedirt/111-add-e2eunit-test-for-email-smtp-service
Add SmtpService health check to Github Action after docker build (#111)
2024-07-19 12:01:56 -07:00
Leendert de Borst
00b145bcf9 Fix nc test if/else (#111) 2024-07-19 20:53:14 +02:00
Leendert de Borst
4c15d64ece Change curl to nc to check tcp port (#111) 2024-07-19 20:46:35 +02:00
Leendert de Borst
a639a2581a Update sed command without macOS fix (#111) 2024-07-19 20:37:01 +02:00
Leendert de Borst
51cd53ee9e Change sed command (#111) 2024-07-19 20:32:48 +02:00
Leendert de Borst
7ba94b9315 Change SMTP port from 25 to 2525 for GH Actions only (#111) 2024-07-19 20:23:20 +02:00
Leendert de Borst
28275bb6d9 Update docker-compose-build.yml (#111) 2024-07-19 20:15:26 +02:00
Leendert de Borst
953e45f62e Add SmtpService test to Github Action after docker build (#111) 2024-07-19 17:39:39 +02:00
Leendert de Borst
709514ff3c Merge pull request #119 from lanedirt/111-add-e2eunit-test-for-email-smtp-service
Update docker-compose.yml to include database volume for SmtpService
2024-07-19 08:37:08 -07:00
Leendert de Borst
12940d46d3 Update docker-compose.yml to include database volume for SmtpService (#111) 2024-07-19 17:35:06 +02:00
Leendert de Borst
8f4b6a5d1b Merge pull request #116 from lanedirt/111-add-e2eunit-test-for-email-smtp-service
Add integration test for email smtp service
2024-07-19 08:03:24 -07:00
Leendert de Borst
d46e582c91 Add Assert.Multiple (#111) 2024-07-19 16:50:32 +02:00
Leendert de Borst
7bbf986c09 Change integration test ports so it works with GitHub Actions (#111) 2024-07-19 16:44:28 +02:00
Leendert de Borst
2e6d5c87bc Add SMTP service integration tests (#111) 2024-07-19 16:31:39 +02:00
Leendert de Borst
0e4d0b0f84 Make basic SMTP service integration test work (#111) 2024-07-19 15:06:44 +02:00
Leendert de Borst
533362210b Add IntegrationTests project (#111) 2024-07-19 14:42:43 +02:00
Leendert de Borst
014064376c Add migrate on SmtpServer startup (#111) 2024-07-19 11:08:42 +02:00
Leendert de Borst
b26ddb809c Merge pull request #115 from lanedirt/105-add-email-storage-to-server-database
Make SmtpServer save emails to database
2024-07-19 01:59:04 -07:00
Leendert de Borst
e9a95fcc53 Refactor DatabaseMessageStore.cs structure (#105) 2024-07-19 10:50:20 +02:00
Leendert de Borst
b3ddf94089 Add SMTP service settings to environment variables so it can be exposed via Docker (#105) 2024-07-18 21:36:26 +02:00
Leendert de Borst
2213ab94da Add email table migration, update SmtpServer to save emails to database (#105) 2024-07-18 21:06:18 +02:00
Leendert de Borst
62d95d73e1 Merge pull request #114 from lanedirt/104-add-email-server-scaffolding-to-docker-stack
Add AliasVault.SmtpService scaffolding
2024-07-17 09:27:33 -07:00
Leendert de Borst
22a1fc089e Add TLS port to smtp docker compose (#104) 2024-07-16 23:31:04 +02:00
Leendert de Borst
9f95157c18 Add smtp service to docker compose (#104) 2024-07-16 23:30:25 +02:00
Leendert de Borst
dec69e959d Add AliasVault.SmtpService project, refactor solution structure (#104) 2024-07-16 20:47:00 +02:00
Leendert de Borst
81a52dcc0e Merge pull request #103 from lanedirt/85-update-readme-instructions-with-e2e-encryption-explanation
Update readme instructions
2024-07-12 07:16:00 -07:00
Leendert de Borst
dd3818499c Update README.md 2024-07-12 16:08:06 +02:00
Leendert de Borst
b141196384 Update README.md 2024-07-12 15:35:19 +02:00
Leendert de Borst
2dfe8c64e5 Update README.md (#85) 2024-07-12 15:31:18 +02:00
Leendert de Borst
033a513c92 Merge pull request #99 from lanedirt/98-improve-oobe-user-experience
Rename email prefix to email field and refactor logic (#98)
2024-07-12 04:33:03 -07:00
Leendert de Borst
0bbb504511 Update DbPersistTest.cs (#98) 2024-07-12 13:11:45 +02:00
Leendert de Borst
88fe86c19a Update DbPersistTest.cs (#98) 2024-07-12 12:56:31 +02:00
Leendert de Borst
3ccf239d84 Update DbPersistTest.cs (#98) 2024-07-12 12:50:20 +02:00
Leendert de Borst
c6ef654c87 Layout update (#98) 2024-07-12 12:45:42 +02:00
Leendert de Borst
4ef0373d31 Add global loading indicator to save actions, refactoring (#98) 2024-07-12 11:45:30 +02:00
Leendert de Borst
71f91b8050 Update csproj to conditionally include appsettings.Development.json (#98) 2024-07-12 10:11:02 +02:00
Leendert de Borst
6d8315ac4e Remove appsettings.Development from git (#98) 2024-07-12 09:52:45 +02:00
Leendert de Borst
08e550f46f Add appsettings.json for E2E test local config override (#98) 2024-07-12 09:48:19 +02:00
Leendert de Borst
dfa67a00e3 Rename email prefix to email field and refactor logic (#98) 2024-07-11 22:55:55 +02:00
Leendert de Borst
cdd58773de Merge pull request #97 from lanedirt/95-add-favicon-extract-timeout
Add favicon extract timeout
2024-07-09 13:08:37 -07:00
Leendert de Borst
808ecb865b Merge branch 'main' into 95-add-favicon-extract-timeout 2024-07-09 13:08:24 -07:00
Leendert de Borst
277ff6c012 Move timeout to webapi (#95) 2024-07-09 22:07:44 +02:00
Leendert de Borst
836f7a311e Add timeout to favicon extraction (#95) (#96) 2024-07-09 21:54:49 +02:00
Leendert de Borst
a3b232543f Add timeout to favicon extraction (#95) 2024-07-09 21:54:27 +02:00
Leendert de Borst
6077f8f377 Merge pull request #94 from lanedirt/93-username-field-value-not-shown-in-view-page
Fix username field value
2024-07-09 12:35:48 -07:00
Leendert de Borst
62711f603b Fix username field value (#93) 2024-07-09 21:35:29 +02:00
Leendert de Borst
a59fa22fa6 Merge pull request #91 from lanedirt/74-add-versioning-support-to-local-sqlite-implementation-with-local-upgrade-paths
Add versioning support to local sqlite implementation with local upgrade paths
2024-07-09 12:19:10 -07:00
Leendert de Borst
cc10bcdfb8 Add asserts to test (#74) 2024-07-09 21:11:49 +02:00
Leendert de Borst
481c283c36 Update PlaywrightTest.cs (#74) 2024-07-09 00:50:57 +02:00
Leendert de Borst
947988c2ce Add empty test migration to make the E2E vault upgrade test work (#74) 2024-07-09 00:45:55 +02:00
Leendert de Borst
202df3d9c3 Update DbUpgradeTest.cs (#74) 2024-07-09 00:29:16 +02:00
Leendert de Borst
8f3ad3d171 Add E2E test scaffolding with seed data (#74) 2024-07-09 00:21:15 +02:00
Leendert de Borst
aa771ae1b2 Fix sync redirect (#74) 2024-07-08 21:43:19 +02:00
Leendert de Borst
158a526aee Fix auth revoke token call (#74) 2024-07-08 16:45:05 +02:00
Leendert de Borst
8c4e078490 Fix tests to after adding new new sync page (#74) 2024-07-08 16:40:34 +02:00
Leendert de Borst
98dea2c4bf Improve client DB sync status indicators (#74) 2024-07-08 16:31:24 +02:00
Leendert de Borst
db62eeec22 Add client DB migration screen (#74) 2024-07-08 12:55:12 +02:00
Leendert de Borst
af1e813c48 Rename initial migration to include explicit version number (#74) 2024-07-08 11:54:09 +02:00
Leendert de Borst
d57ac9e743 Update E2E sleep threshold to 100ms to prevent ERR_CONNECTION_REFUSED (#74) 2024-07-08 11:53:47 +02:00
Leendert de Borst
f5e02fb784 Add register warning test (#74) 2024-07-08 11:26:01 +02:00
Leendert de Borst
96a7fbaf3b Add vault decrypt error message (#74) 2024-07-08 11:25:50 +02:00
Leendert de Borst
c749853870 Merge pull request #90 from lanedirt/83-refactor-wasm-project-for-naming-conventions-and-fix-remaining-todos 2024-07-03 14:39:38 -07:00
Leendert de Borst
8c93fceb3e Make E2E test more robust (#83) 2024-07-03 22:41:43 +02:00
Leendert de Borst
fb0ef1c59a Generic refactoring, delete unused files, folder restructuring (#83) 2024-07-03 20:08:18 +02:00
Leendert de Borst
847d97a0e9 Merge pull request #87 from lanedirt/81-add-support-for-attachments-file-upload 2024-07-02 23:39:18 -07:00
Leendert de Borst
8d9c80ef61 Fix multiple attachment add bug (#81) 2024-07-03 00:27:16 +02:00
Leendert de Borst
c22ba0c2cf Add DbPersist E2E test (#81) 2024-07-02 23:59:00 +02:00
Leendert de Borst
5e4654a968 Update DbService.cs (#81) 2024-07-02 23:23:23 +02:00
Leendert de Borst
bbf08d16d4 Update AttachmentViewer.cs (#81) 2024-07-02 23:21:30 +02:00
Leendert de Borst
19c6296a86 Fix tests (#81) 2024-07-02 23:20:03 +02:00
Leendert de Borst
b8ffd39f99 Revert encryption key debug statement (#81) 2024-07-02 23:15:38 +02:00
Leendert de Borst
fe29cb7a2c Merge branch 'main' into 81-add-support-for-attachments-file-upload 2024-07-02 14:13:05 -07:00
Leendert de Borst
4137cc4736 Add attachment uploader/viewer component (#81) 2024-07-02 23:11:34 +02:00
Leendert de Borst
3d23731f0e Merge pull request #86 from lanedirt/84-add-exportimport-database-to-unencrypted-csv
Add CSV import/export and DB loader (#84)
2024-07-02 14:09:45 -07:00
Leendert de Borst
bcbda92601 Update tests (#84) 2024-07-02 21:58:39 +02:00
Leendert de Borst
f0fca573fd Updated vault settings page layout, refactor tests (#84) 2024-07-02 21:22:39 +02:00
Leendert de Borst
4abc674970 Add CSV import/export and DB loader (#84) 2024-07-02 20:49:53 +02:00
Leendert de Borst
43ed35a1be Merge pull request #79 from lanedirt/58-migrate-existing-alias-crud-pages-to-new-local-sqlite-implementation
Refactor CRUD pages to local SQLite model
2024-07-01 15:50:16 -07:00
Leendert de Borst
689ab0b388 Update CredentialService.cs (#58) 2024-07-02 00:46:35 +02:00
Leendert de Borst
a884895fae Restored favicon extraction logic (#58) 2024-07-02 00:05:40 +02:00
Leendert de Borst
a644df1e3c Add notes and birthdate field, add date field validation (#58) 2024-07-01 17:18:42 +02:00
Leendert de Borst
48abe09415 Rename login to credentials, fixed warnings and bugs (#58) 2024-07-01 15:43:32 +02:00
Leendert de Borst
f1bc79a9a4 Refactor (#58) 2024-07-01 13:45:07 +02:00
Leendert de Borst
ce61fc36e1 Refactor SpamOK webapi model to eliminate duplicated code (#58) 2024-07-01 13:02:29 +02:00
Leendert de Borst
cbce527aa1 Delete AliasController.cs old api endpoints (#58) 2024-07-01 12:49:11 +02:00
Leendert de Borst
53ea7c2477 Remove client tables from server db (#58) 2024-07-01 12:46:50 +02:00
Leendert de Borst
aae0846639 Refactor CRUD pages to local SQLite model (#58) 2024-06-30 17:58:01 +02:00
Leendert de Borst
c2648cf2cb Merge pull request #76 from lanedirt/63-abstract-auth-loginunlock-to-share-the-same-implementation-to-reduce-duplicate-code
Abstract Login and Unlock duplicated logic (#63)
2024-06-29 14:23:21 -07:00
Leendert de Borst
68c19957e0 Add webapi call finish content to test pages for improved stability (#63) 2024-06-29 23:12:51 +02:00
Leendert de Borst
6c79503d1f Fix unittests (#63) 2024-06-29 23:00:41 +02:00
Leendert de Borst
c5fc5c0e81 Add dedicated test pages to webapi and wasm for E2E tests (#63) 2024-06-29 22:57:19 +02:00
Leendert de Borst
4170e754ea Add JWT auth tokens E2E tests (#63) 2024-06-29 13:07:06 +02:00
Leendert de Borst
5d44a3aeff Fix missing redirect in login handler (#63) 2024-06-29 11:23:27 +02:00
Leendert de Borst
8243213028 Abstract Login and Unlock duplicated logic (#63) 2024-06-28 23:33:33 +02:00
Leendert de Borst
efc422ac44 Merge pull request #75 from lanedirt/69-implement-vault-changebackup-history-logic-on-server
Implement vault changebackup history logic on server
2024-06-28 14:32:01 -07:00
Leendert de Borst
497430f729 Refactor (#69) 2024-06-28 23:20:20 +02:00
Leendert de Borst
659dc7b55d Refactor DbService client logic (#69) 2024-06-28 20:04:26 +02:00
Leendert de Borst
581fd945c2 Update VaultRetention logic and add tests (#69) 2024-06-28 20:04:06 +02:00
Leendert de Borst
8205fa9d6e Add vault retention logic (#69) 2024-06-28 12:29:45 +02:00
Leendert de Borst
cc4a6db976 Merge pull request #71 from lanedirt/55-add-client-side-database-sync-indicator-to-ui
Add client side database sync indicator to UI
2024-06-28 02:28:24 -07:00
Leendert de Borst
efb7ae009d Remove unused vars (#55) 2024-06-28 10:56:12 +02:00
Leendert de Borst
f29606ea94 Update UI style (#55) 2024-06-28 10:47:38 +02:00
Leendert de Borst
4a586cf117 Add DbStatus indicator to UI (#55) 2024-06-28 10:05:50 +02:00
Leendert de Borst
64688cd2b5 Merge pull request #68 from lanedirt/67-improve-e2e-test-stability
Change port assignment logic (#67)
2024-06-26 15:24:16 -07:00
Leendert de Borst
ea9c6e9aa7 Update Program.cs (#67) 2024-06-27 00:07:33 +02:00
Leendert de Borst
889dc81404 Add host project for in-memory testing of WASM app (#67) 2024-06-26 18:53:47 +02:00
Leendert de Borst
df85846fbe Change port assignment logic (#67) 2024-06-26 08:55:26 +02:00
Leendert de Borst
5a9632f80e Merge pull request #66 from lanedirt/60-unlock-screen-can-be-bypassed-by-clicking-on-logo
Add unlock page redirect test (#60)
2024-06-25 15:02:30 -07:00
Leendert de Borst
9105215dc8 Add unlock page redirect test (#60) 2024-06-25 23:55:39 +02:00
Leendert de Borst
4aa57f95b5 Merge pull request #65 from lanedirt/60-unlock-screen-can-be-bypassed-by-clicking-on-logo
Add encryption key check to generic PageBase (#60)
2024-06-25 14:40:19 -07:00
Leendert de Borst
1112922731 Add assert to UnlockTest (#60) 2024-06-25 23:33:41 +02:00
Leendert de Borst
2bee131ff4 Add E2E test for unlock mechanism (#60) 2024-06-25 23:26:54 +02:00
Leendert de Borst
75933efbdd Refactor isEncryptionKeySet logic to prevent delayed navigation loops (#60) 2024-06-25 23:03:06 +02:00
Leendert de Borst
93623a2f05 Update AliasClientDbService.cs (#60) 2024-06-25 22:35:11 +02:00
Leendert de Borst
4b12518ee4 Fix auth state redirect loop (#60) 2024-06-25 21:56:46 +02:00
Leendert de Borst
2a834eeb38 Add encryption key check to generic PageBase (#60) 2024-06-25 21:40:57 +02:00
Leendert de Borst
25872f08de Merge pull request #64 from lanedirt/59-save-encryption-key-to-session-on-registration-next-to-login
Add missing store calls (#59)
2024-06-24 11:05:29 -07:00
Leendert de Borst
9fc0f4d7da Merge pull request #62 from lanedirt/61-move-aliasclientdb-project-to-correct-folder
Move AliasClientDb project to correct folder (#61)
2024-06-24 10:39:35 -07:00
Leendert de Borst
4e2bf10115 Add missing store calls (#59) 2024-06-24 19:37:48 +02:00
Leendert de Borst
957a9474ec Move AliasClientDb project to correct folder (#61) 2024-06-24 19:32:38 +02:00
Leendert de Borst
bd0d4ad2a4 Merge pull request #56 from lanedirt/44-change-datamodel-to-be-more-dynamic-and-support-client-side-encryption
44 change datamodel to be more dynamic and support client side encryption
2024-06-24 10:09:20 -07:00
Leendert de Borst
88fa8a0c17 Update Dockerfile (#44) 2024-06-24 19:04:54 +02:00
Leendert de Borst
943f16789f Update docker compose (#44) 2024-06-24 18:34:01 +02:00
Leendert de Borst
bb91637db5 Refactoring (#44) 2024-06-24 17:38:58 +02:00
Leendert de Borst
5aef4c58e2 Add working version of client-side SQLite sync via webapi (#44) 2024-06-24 17:15:19 +02:00
Leendert de Borst
554ea91bda Add SQLite in-memory load/save mechanism (#44) 2024-06-24 14:35:45 +02:00
Leendert de Borst
d5a858d78d Refactor EF database projects, added basic client-side SQLite in-memory implementation (#44) 2024-06-24 11:30:37 +02:00
Leendert de Borst
ff265c3a86 Remove AliasVault Blazor server project (#44) 2024-06-24 09:10:20 +02:00
Leendert de Borst
f2fced86b2 Merge pull request #53 from lanedirt/52-fix-ide-warning-configuration-in-rider-regardless-of-user-settings
Move dotnet code style settings to .globalconfig (#52)
2024-06-23 12:44:26 -07:00
Leendert de Borst
83f62e17b2 Update Dockerfile (#52) 2024-06-23 21:35:42 +02:00
Leendert de Borst
0f80217e74 Replace buildtask project with RoslynCodeTaskFactory implementation (#52) 2024-06-23 21:33:56 +02:00
Leendert de Borst
7290ee870c Update buildtasks so it works on both Windows and Mac/Linux (#52) 2024-06-23 20:56:39 +02:00
Leendert de Borst
27c0c9194e Add custom build task for cache busting (#52) 2024-06-23 20:13:56 +02:00
Leendert de Borst
ae652297fa Update Login.razor (#52) 2024-06-23 19:49:11 +02:00
Leendert de Borst
bcdb9efee8 Move dotnet code style settings to .globalconfig (#52) 2024-06-23 19:41:30 +02:00
Leendert de Borst
f224507f91 Merge pull request #51 from lanedirt/49-fix-docker-build-ef-migrations
Update docker-compose.build.yml (#49)
2024-06-23 10:28:38 -07:00
Leendert de Borst
dc9ba64b21 Refactor dockerfiles to remove explicit EF bundle (#49) 2024-06-23 19:22:03 +02:00
Leendert de Borst
c99394416e Update docker-compose-build.yml typo (#49) 2024-06-23 18:31:05 +02:00
Leendert de Borst
ba2abde97d Update RootController.cs (#49) 2024-06-23 18:27:20 +02:00
Leendert de Borst
66b33a8686 Add healthcheck webapi endpoint (#49) 2024-06-23 18:24:33 +02:00
Leendert de Borst
1ebdce5216 Update docker-compose.build.yml (#49) 2024-06-23 18:03:42 +02:00
Leendert de Borst
c2d1ea9895 Merge pull request #50 from lanedirt/49-fix-docker-build-ef-migrations
49 fix docker build ef migrations
2024-06-23 09:00:50 -07:00
Leendert de Borst
75a9278d56 Update .gitignore (#49) 2024-06-23 17:55:53 +02:00
Leendert de Borst
b508354ac6 Remove .env from git (#49) 2024-06-23 17:53:50 +02:00
Leendert de Borst
e1478c055f Merge pull request #48 from lanedirt/43-implement-master-password-for-login-and-basic-encryption
Implement master password for login and basic encryption
2024-06-23 08:28:37 -07:00
Leendert de Borst
c07f0c33bb Update AuthController.cs (#43) 2024-06-23 17:22:17 +02:00
Leendert de Borst
4e2b10eeab Add try/catch to E2E test init (#43) 2024-06-23 17:12:35 +02:00
Leendert de Borst
05e9285752 Code style refactor (#43) 2024-06-23 16:35:45 +02:00
Leendert de Borst
a76a21a935 Update TestDefaults.cs due to Argon2id speed in WASM (#43) 2024-06-21 19:28:20 +02:00
Leendert de Borst
0fd23eab59 Fix code style issues (#43) 2024-06-21 19:27:54 +02:00
Leendert de Borst
1c1d1e1d74 Fix JWT key retrieval (#43) 2024-06-21 17:38:51 +02:00
Leendert de Borst
9412f862eb Make Argon2id dynamic using SRP salt (#43) 2024-06-21 15:52:08 +02:00
Leendert de Borst
9f7ba2eb20 Implement SPR for login flow (#43) 2024-06-21 15:37:51 +02:00
Leendert de Borst
101d1d485a Implement SPR for basic signup flow (#43) 2024-06-21 11:48:27 +02:00
Leendert de Borst
e316836ee5 Make basic SRP flow work (#43) 2024-06-21 10:44:09 +02:00
Leendert de Borst
c6e3c41759 Add SRP auth scaffolding (#43) 2024-06-21 00:52:19 +02:00
790 changed files with 62026 additions and 40557 deletions

View File

@@ -25,11 +25,6 @@ csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
dotnet_diagnostic.SA1011.severity = none
dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1309.severity = none
dotnet_diagnostic.SA1310.severity = warning
dotnet_diagnostic.SX1309.severity = none
# Razor files
[*.razor]
@@ -65,59 +60,3 @@ indent_size = 4
[*.xml]
indent_style = space
indent_size = 4
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
end_of_line = crlf
dotnet_style_coalesce_expression = false:suggestion
dotnet_style_null_propagation = false:suggestion
# IDE0046: Convert to conditional expression
dotnet_diagnostic.IDE0046.severity = silent

1
.env
View File

@@ -1 +0,0 @@
JWT_KEY=YprFMYAzrqY/R/DmDYZI1PS7qTyZYp4g

View File

@@ -1 +1,8 @@
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

53
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
# Enable version updates for NuGet
- package-ecosystem: "nuget"
directory: "/"
target-branch: "main"
open-pull-requests-limit: 10
labels:
- "dependencies"
# Check for updates once a week
schedule:
day: "monday"
time: "09:00"
interval: "weekly"
# Ignore certain dependencies (optional)
# ignore:
# - dependency-name: "SomePackage"
# versions: ["4.x", "5.x"]
# Enable version updates for npm
- package-ecosystem: "npm"
# Look for `package.json` and `lock` files in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
day: "monday"
time: "09:00"
interval: "weekly"
# Enable version updates for Docker
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
day: "monday"
time: "09:00"
interval: "weekly"
# Enable version updates for Composer
- package-ecosystem: "composer"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
# Check for updates once a week
schedule:
day: "monday"
time: "09:00"
interval: "weekly"

6
.github/release.yml vendored
View File

@@ -9,9 +9,9 @@ changelog:
labels:
- dependencies
- bug
- title: 🧩 Dependencies Updates
labels:
- dependencies
- title: 🐞 Bug Fixes
labels:
- bug
- title: 🧩 Dependencies Updates
labels:
- dependencies

View File

@@ -1,3 +1,4 @@
# This workflow will test if building the Docker Compose containers from scratch works.
name: Docker Compose Build
on:
@@ -9,37 +10,91 @@ on:
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 build --verbose
- name: Set up Docker Compose
run: |
# Build the images and start the services
# 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 5
- name: Test if localhost:80 (WASM app) responds
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: |
# Test if the service on localhost:80 responds
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:80)
if [ "$http_code" -ne 200 ]; then
echo "Service did not respond with 200 OK"
exit 1
else
echo "Service responded with 200 OK"
fi
- name: Test if localhost:81 (WebApi) responds
run: |
# Test if the service on localhost:81 responds
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:81)
if [ "$http_code" -ne 200 ] && [ "$http_code" -ne 404 ]; then
echo "Service did not respond with expected 200 OK or 404 Not Found"
exit 1
else
echo "Service responded with $http_code"
fi
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

View 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

View File

@@ -0,0 +1,43 @@
# This workflow will test if running the E2E Admin tests via Playwright CLI works.
name: .NET E2E Admin Tests (Playwright)
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
admin-tests:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet workload install wasm-tools
- name: Build
run: dotnet build
- name: Ensure browsers are installed
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
with:
timeout_minutes: 60
max_attempts: 3
command: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal --filter "Category=AdminTests"
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: admin-test-results
path: TestResults-Admin.xml

View File

@@ -0,0 +1,40 @@
# This workflow will test if running the E2E Client tests via Playwright CLI works.
name: .NET E2E Client Tests (Playwright with Sharding)
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
client-tests:
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet workload install wasm-tools
- name: Build
run: dotnet build
- name: Ensure browsers are installed
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
with:
timeout_minutes: 60
max_attempts: 3
command: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal --filter "FullyQualifiedName~.E2ETests.Tests.Client.Shard${{ matrix.shard }}."

View File

@@ -0,0 +1,43 @@
# This workflow will test if running the E2E Misc tests via Playwright CLI works.
name: .NET E2E Misc Tests (Playwright)
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
misc-tests:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet workload install wasm-tools
- name: Build
run: dotnet build
- name: Ensure browsers are installed
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
with:
timeout_minutes: 60
max_attempts: 3
command: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal --filter "Category!=AdminTests&Category!=ClientTests"
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: misc-test-results
path: TestResults-Misc.xml

View File

@@ -1,7 +1,5 @@
# 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
name: Playwright integration tests
# This workflow will test if running the integration tests works.
name: .NET Integration Tests
on:
push:
@@ -15,13 +13,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet workload install wasm-tools
- name: Build
run: dotnet build
- name: Ensure browsers are installed
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
- name: Run your tests
run: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal
- name: Run integration tests
run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal

View File

@@ -1,7 +1,5 @@
# 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
name: .NET build and run tests
# This workflow will test if running the unit tests works.
name: .NET Unit Tests
on:
push:
@@ -10,19 +8,24 @@ on:
branches: [ "main" ]
jobs:
build:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet workload install wasm-tools
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
- name: Run unittests
run: dotnet test src/Tests/AliasVault.UnitTests --no-build --verbosity normal

View 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 }}

View File

@@ -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,20 +11,31 @@ 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:
java-version: 17
distribution: 'zulu' # Alternative distribution options are available.
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
uses: actions/cache@v3
@@ -31,15 +43,17 @@ jobs:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
restore-keys: ${{ runner.os }}-sonar-scanner
- name: Install SonarCloud scanner
if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
shell: powershell
run: |
New-Item -Path .\.sonar\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |

29
.gitignore vendored
View File

@@ -268,6 +268,7 @@ ServiceFabricBackup/
# SQLite files
*.sqlite
*.sqlite.*
*.sqlite-shm
*.sqlite-wal
@@ -371,6 +372,30 @@ FodyWeavers.xsd
.idea
*.licenseheader
# AliasVault specific
# Codebuddy Rider plugin
.codebuddy
# -------------------
# AliasVault specifics
# -------------------
# index.html is generated by the build process from index.template.html and therefore should be ignored
src/AliasVault.WebApp/wwwroot/index.html
src/AliasVault.Client/wwwroot/index.html
# appsettings.Development.json is generated by the build process from appsettings.Development.template.json and therefore should be ignored
src/AliasVault.Client/wwwroot/appsettings.Development.json
# appsettings.Development.json is added manually if needed, it should not be committed.
src/Tests/AliasVault.E2ETests/appsettings.Development.json
# .env is generated by install.sh and therefore should be ignored
.env
# Draw.io diagram temp files
*.drawio.*
# Certificates
certificates/**/*.crt
certificates/**/*.key
certificates/**/*.pfx
certificates/**/*.pem
certificates/letsencrypt/**

57
.globalconfig Normal file
View File

@@ -0,0 +1,57 @@
dotnet_diagnostic.SA1011.severity = none
dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1309.severity = none
dotnet_diagnostic.SA1310.severity = warning
dotnet_diagnostic.SX1309.severity = none
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_coalesce_expression = false:suggestion
dotnet_style_null_propagation = false:suggestion
# IDE0046: Convert to conditional expression
dotnet_diagnostic.IDE0046.severity = silent

19
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C#: AliasVault.WebApp [http]",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/AliasVault.WebApp/AliasVault.WebApp.csproj",
"launchConfigurationId": "TargetFramework=;http"
},
{
"name": "C#: AliasVault.Api [http]",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/AliasVault.Api/AliasVault.Api.csproj",
"launchConfigurationId": "TargetFramework=;http"
}
]
}

View File

@@ -68,3 +68,34 @@ dotnet tool install --global Microsoft.Playwright.CLI
# Note: make sure the E2E test project has been built at least once so the bin dir exists.
pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install
```
### 7. Create AliasVault.Client appsettings.Development.json
The WASM client app supports a development specific appsettings.json file. This appsettings file is optional but can override various options to make debugging easier.
1. Copy `wwwroot/appsettings.json` to `wwwroot/appsettings.Development.json`
Here is an example file with the various options explained:
```
{
"ApiUrl": "http://localhost:5092",
"PrivateEmailDomains": ["example.tld"],
"SupportEmail": "support@example.tld",
"UseDebugEncryptionKey": "true",
"CryptographyOverrideType" : "Argon2Id",
"CryptographyOverrideSettings" : "{\"DegreeOfParallelism\":1,\"MemorySize\":1024,\"Iterations\":1}"
}
```
- UseDebugEncryptionKey
- This setting will use a static encryption key so that if you login as a user you can refresh the page without needing to unlock the database again. This speeds up development when changing things in the WebApp WASM project. Note: the project needs to be run in "Development" mode for this setting to be used.
- CryptographyOverrideType
- This setting allows overriding the default encryption type (Argon2id) with a different encryption type. This is useful for testing different encryption types without having to change code.
- CryptographyOverrideSettings
- This setting allows overriding the default encryption settings (Argon2id) with different settings. This is useful for testing different encryption settings without having to change code. The default Argon2id settings
are defined in the project as `Utilities/Cryptography/Cryptography.Client/Defaults.cs`. These default settings
are focused on security but NOT performance. Normally for key derivation purposes the slower/heavier the algorithm
the better protection against attackers. For production builds this is what we want, however in case of automated testing or debugging extra performance can be gained by tweaking (lowering) these settings.

15
Dockerfile Normal file
View 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"]

120
README.md
View File

@@ -1,60 +1,110 @@
<div align="center">
<h1>AliasVault</h1>
<h1><img src="https://github.com/user-attachments/assets/933c8b45-a190-4df6-913e-b7c64ad9938b" width="40" /> AliasVault</h1>
[<img src="https://img.shields.io/github/v/release/lanedirt/AliasVault?include_prereleases&logo=github">](https://github.com/lanedirt/OGameX/releases)
<p align="center">
<a href="https://app.aliasvault.net">Live demo 🚀</a> • <a href="https://aliasvault.net?utm_source=gh-readme">Website 🏠</a> • <a href="#installation">Installation 📦</a>
</p>
<h3 align="center">
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/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-build-run-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=e2e tests">](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-integration-tests.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)
[<img src="https://img.shields.io/github/actions/workflow/status/lanedirt/AliasVault/dotnet-e2e-client-tests.yml?label=e2e tests">](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-e2e-client-tests.yml)
[<img src="https://img.shields.io/sonar/coverage/lanedirt_AliasVault?server=https%3A%2F%2Fsonarcloud.io&label=test code coverage">](https://sonarcloud.io/summary/new_code?id=lanedirt_AliasVault)
[<img src="https://img.shields.io/sonar/quality_gate/lanedirt_AliasVault?server=https%3A%2F%2Fsonarcloud.io&label=sonarcloud&logo=sonarcloud">](https://sonarcloud.io/summary/new_code?id=lanedirt_AliasVault)
</div>
> Disclaimer: This repository is currently in an alpha state and is NOT ready for production use. Critical features, such as encryption, are not yet fully implemented. AliasVault is a work in progress and as of this moment serves as a research playground. Users are welcome to explore and use this project, but please be aware that there are no guarantees regarding its security or stability. Use at your own risk!
AliasVault is an open-source password and alias manager built with C# ASP.NET technology. AliasVault can be self-hosted on your own server with Docker, providing a secure and private solution for managing your online identities and passwords.
AliasVault is an open-source password manager that can generate virtual identities complete with virtual email addresses. AliasVault can be self-hosted on your own server with Docker, providing a secure and private solution for managing your online identities and passwords.
### What makes AliasVault unique:
- **Zero-knowledge architecture**: All data is end-to-end encrypted on the client and stored in encrypted state on the server. Your master password never leaves your device and the server never has access to your data.
- **Built-in email server**: AliasVault includes its own email server that allows you to generate virtual email addresses for each alias. Emails sent to these addresses are instantly visible in the AliasVault app.
- **Alias generation**: Generate aliases and assign them to a website, allowing you to use different email addresses and usernames for each website. Keeping your online identities separate and secure, making it harder for bad actors to link your accounts.
- **Open-source**: The source code is available on GitHub and can be self-hosted on your own server.
## Features
- **Password Management:** Securely store and manage your passwords.
- **Virtual Identities:** Generate virtual identities with virtual (working) email addresses that are assigned to one or more passwords.
- **Zero-knowledge architecture:** Ensures that all sensitive data is end-to-end encrypted on the client and stored in encrypted state on the database. The server never has access to your data.
> Note: AliasVault is currently in active development and some features may not yet have been (fully) implemented. If you run into any issues, please create an issue on GitHub.
## Live demo
A live demo of the app is available at the official website at [app.aliasvault.net](https://app.aliasvault.net) (up-to-date with `main` branch). You can create a free account to try it out yourself.
<img width="700" alt="Screenshot of AliasVault" src="docs/img/screenshot.png">
## Installation
### 1. Clone this repository.
Choose one of the following installation methods:
### 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
# 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
```
### 2. Run the init script to set up the .env file and generate a random encryption secret.
This script will create a .env file in the root directory of the project if it does not yet exist and populate it with a random encryption secret.
```bash
# Go to the project directory
$ cd AliasVault
### Option 2: Build from Source
# Make init script executable
$ chmod +x init.sh
# Run the init script
$ ./init.sh
```
### 3. Build and run the app via Docker:
Building from source requires more resources:
- Minimum 2GB RAM (more RAM will speed up build time)
- At least 1 vCPU
- 40GB+ disk space (for dependencies and build artifacts)
- Docker installed
- Git installed
```bash
# Build and run the app via Docker Compose
$ docker compose up -d --build --force-recreate
# Clone the repository
git clone https://github.com/lanedirt/AliasVault.git
cd AliasVault
# Make build script executable and run it. This will create the .env file, build the Docker images from source, and start the AliasVault containers.
chmod +x install.sh
./install.sh build
```
> Note: the container binds to port 80 by default. If you have another service running on port 80, you can change the port in the `docker-compose.yml` file.
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.
#### Note for first time build:
- When running the app for the first time, it may take a few minutes to build the Docker image.
- A SQLite database file will be created in `./database/aliasdb.sqlite`. This file will store all (encrypted) password vaults. It should be kept secure and not shared.
### Post-Installation
After the Docker containers have started the app will be available at http://localhost:80
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.
#### 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
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.
- Your master password never leaves your device.
- Zero-knowledge architecture ensures the server never has access to your unencrypted data
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)
## Tech stack / credits
The following technologies, frameworks and libraries are used in this project:
@@ -63,8 +113,12 @@ The following technologies, frameworks and libraries are used in this project:
- [ASP.NET Core](https://dotnet.microsoft.com/apps/aspnet) - An open-source framework for building modern, cloud-based, internet-connected applications.
- [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) - A lightweight, extensible, open-source and cross-platform version of the popular Entity Framework data access technology.
- [Blazor WASM](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor) - A framework for building interactive web UIs using C# instead of JavaScript. It's a single-page app framework that runs in the browser via WebAssembly.
- [Playwright](https://playwright.dev/) - A Node.js library to automate Chromium, Firefox and WebKit with a single API. Used for end-to-end testing.
- [Docker](https://www.docker.com/) - A platform for building, sharing, and running containerized applications.
- [SQLite](https://www.sqlite.org/index.html) - A C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine.
- [Tailwind CSS](https://tailwindcss.com/) - A utility-first CSS framework for rapidly building custom designs.
- [Flowbite](https://flowbite.com/) - A free and open-source UI component library based on Tailwind CSS.
- [Konscious.Security.Cryptography](https://github.com/kmaragon/Konscious.Security.Cryptography) - A .NET library that implements Argon2id, a memory-hard password hashing algorithm.
- [SRP.net](https://github.com/secure-remote-password/srp.net) - SRP6a Secure Remote Password protocol for secure password authentication.
- [SmtpServer](https://github.com/cosullivan/SmtpServer) - A SMTP server library for .NET that is used for the virtual email address feature.
- [MimeKit](https://github.com/jstedfast/MimeKit) - A .NET MIME creation and parser library used for the virtual email address feature.

95
SECURITY.md Normal file
View File

@@ -0,0 +1,95 @@
# SECURITY.md
This document describes the encryption algorithms used by AliasVault in order to keep its user data secure.
## Overview
AliasVault features a [zero-knowledge architecture](https://en.wikipedia.org/wiki/Zero-knowledge_service) and uses a combination of encryption algorithms to protect the data of its users.
The basic premise is that the master password chosen by the user upon registration forms the basis for all encryption
and decryption operations. This master password is never transmitted over the network and only resides on the client.
All data is encrypted at rest and in transit. This ensures that even if the AliasVault servers are compromised,
the user's data remains secure.
## Encryption algorithms
The following encryption algorithms are used by AliasVault:
- [Argon2id](#argon2id)
- [SRP](#srp)
- [AES-GCM](#aes-gcm)
- [RSA-OAEP](#rsa-oaep)
Below is a detailed explanation of each encryption algorithm.
For more information about how these algorithms are specifically used in AliasVault, see the [Security Architecture](docs/security-architecture.md) document.
### Argon2id
To derive a key from the master password, AliasVault uses the Argon2id key derivation function. Argon2id is a memory-hard
key derivation function which allows for controlling the execution time, memory required and degree of parallelism.
This makes it resilient against brute-force attacks and makes it one of the best choices for deriving keys from passwords.
AliasVault uses Argon2id with the following default parameters:
- Degree of parallelism: 1
- Memory size: 19456 KB
- Iterations: 2
More information about Argon2id can be found on the [Argon2](https://en.wikipedia.org/wiki/Argon2) Wikipedia page.
### SRP
The Secure Remote Password (SRP) protocol is used for authenticating a user with the AliasVault server during login.
The SRP protocol is a password-authenticated key exchange protocol (PAKE). This means that the client and server can
authenticate each other using a password, without sending the password itself over the network.
With the use of SRP the master password never leaves the client. The client sends a verifier to the server,
which is a value derived from the master password. The server uses this verifier to authenticate the client without
having ever seen the actual master password.
For more information see the [SRP protocol](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) information on Wikipedia.
### AES-256-GCM
All user's vault data is fully encrypted on the client using the AES-256-GCM encryption algorithm, which stands for
*Advanced Encryption Standard with 256-bit key in Galois/Counter Mode*. The key for encryption is derived from the
master password by using the Argon2Id algorithm. AliasVault implements AES-GCM with the following specifications:
- Key Size: 256 bits
- Uses the Web Crypto API's SubtleCrypto interface for secure cryptographic operations
- Generates a random 12-byte (96-bit) IV (initialization vector) for each encryption operation
- Performs all encryption/decryption operations entirely in the browser
#### The encryption process works as follows:
- A unique IV is generated for each encryption operation
- The users vault data is encrypted using AES-GCM with the derived key and IV
- The IV is prepended to the ciphertext
More information about AES-GCM can be found on the [AES-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) Wikipedia page.
### RSA-OAEP
To secure email communications, AliasVault uses RSA-OAEP (RSA with Optimal Asymmetric Encryption Padding). This asymmetric
encryption system allows AliasVault to store emails on the server in encrypted state which can only be read by the
intended recipient. AliasVault implements RSA-OAEP with the following specifications:
- Algorithm: RSA-OAEP with SHA-256 hash
- Key Size: 2048-bit modulus
- Key Format: JWK (JSON Web Key)
- Padding: OAEP (Optimal Asymmetric Encryption Padding)
#### Email Security Flow
1. Key Generation: When a user creates a vault, a RSA key pair is generated:
- A private key that remains in the encrypted user's vault and is never transmitted
- A public key that is sent to the server
2. Email Reception Process: When an email arrives at the AliasVault email server:
- The server generates a random 256-bit symmetric encryption key to encrypt the email contents
- The symmetric encryption key is encrypted using the recipient's asymmetric public key
- The encrypted email contents together with the encrypted symmetric encryption key are stored in the server's database
- The original email content is never stored or logged
3. Email Retrieval Process:
- When a user accesses their emails, the encrypted content is retrieved from the server
- The client-side application decrypts the symmetric encryption key using the user's private key that is stored in their vault
- The decrypted symmetric encryption key is used to decrypt the email contents
- Decryption occurs entirely in the browser, maintaining end-to-end encryption
This implementation ensures that:
- Emails are encrypted and secure at rest in the server database
- Only the intended recipient that holds the private key can decrypt and read their emails
- Even if the server is compromised, email contents remain encrypted and unreadable
More information about RSA-OAEP can be found on the [RSA-OAEP](https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding) Wikipedia page.

View File

@@ -3,48 +3,76 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault", "src\AliasVault\AliasVault.csproj", "{BD2050C0-DC26-4777-9514-546525307370}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasDb", "src\AliasDb\AliasDb.csproj", "{64F47C9A-FE69-4793-B469-28BAADEC6706}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasGenerators", "src\AliasGenerators\AliasGenerators.csproj", "{78E84B4E-57D1-491A-8F4E-9879AE49DE0F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FaviconExtractor", "src\Utilities\FaviconExtractor\FaviconExtractor.csproj", "{ED328644-A152-403D-86EB-81201AA07744}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.FaviconExtractor", "src\Utilities\AliasVault.FaviconExtractor\AliasVault.FaviconExtractor.csproj", "{ED328644-A152-403D-86EB-81201AA07744}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.UnitTests", "src\Tests\AliasVault.UnitTests\AliasVault.UnitTests.csproj", "{8E6A418A-B305-465D-857D-49953605C18E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cryptography", "src\Utilities\Cryptography\Cryptography.csproj", "{427EA8E2-EA76-467E-A6BC-201EFE40C0D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.Api", "src\AliasVault.Api\AliasVault.Api.csproj", "{B797C533-260E-4DA2-83B1-0EE4BCFE08DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.WebApp", "src\AliasVault.WebApp\AliasVault.WebApp.csproj", "{25248E01-5A4B-4F95-A63C-BEA01499A1C2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.Client", "src\AliasVault.Client\AliasVault.Client.csproj", "{25248E01-5A4B-4F95-A63C-BEA01499A1C2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.Shared", "src\AliasVault.Shared\AliasVault.Shared.csproj", "{15EFE0D0-F41B-47D7-86B7-8F840335CB82}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.Shared", "src\Shared\AliasVault.Shared\AliasVault.Shared.csproj", "{15EFE0D0-F41B-47D7-86B7-8F840335CB82}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.E2ETests", "src\Tests\AliasVault.E2ETests\AliasVault.E2ETests.csproj", "{AF013D08-1BF6-4E23-87D2-37F614BE7952}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Database", "Database", "{5F7417F6-4388-49CC-9511-ED63C4A6488A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasServerDb", "src\Databases\AliasServerDb\AliasServerDb.csproj", "{1277105D-50CD-4CE0-9C2C-549F46867E54}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasClientDb", "src\Databases\AliasClientDb\AliasClientDb.csproj", "{FE10F294-817F-477E-A24F-8597A15AF0B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.E2ETests.Client.Server", "src\Tests\Server\AliasVault.E2ETests.Client.Server\AliasVault.E2ETests.Client.Server.csproj", "{DD1F496F-CF10-47D1-A57F-5FA256479332}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{607945F3-9896-4544-99EC-F3496CF4D36B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AliasVault.CsvImportExport", "src\Utilities\AliasVault.CsvImportExport\AliasVault.CsvImportExport.csproj", "{A9C9A606-C87E-4298-AB32-09B1884D7487}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{8A477241-B96C-4174-968D-D40CB77F1ECD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.SmtpService", "src\Services\AliasVault.SmtpService\AliasVault.SmtpService.csproj", "{B095A174-E528-4D38-BEC1-D1D38B3B30C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.IntegrationTests", "src\Tests\AliasVault.IntegrationTests\AliasVault.IntegrationTests.csproj", "{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Admin", "src\AliasVault.Admin\AliasVault.Admin.csproj", "{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.InstallCli", "src\Utilities\AliasVault.InstallCli\AliasVault.InstallCli.csproj", "{857BCD0E-753F-437A-AF75-B995B4D9A5FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Logging", "src\Utilities\AliasVault.Logging\AliasVault.Logging.csproj", "{FF0B0E64-1AE2-415C-A404-0EB78010821A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.RazorComponents", "src\Shared\AliasVault.RazorComponents\AliasVault.RazorComponents.csproj", "{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.WorkerStatus", "src\Utilities\AliasVault.WorkerStatus\AliasVault.WorkerStatus.csproj", "{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.TotpGenerator", "src\Utilities\AliasVault.TotpGenerator\AliasVault.TotpGenerator.csproj", "{E8D9C551-67D2-4651-8EDF-4262DF7375CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Auth", "src\Utilities\AliasVault.Auth\AliasVault.Auth.csproj", "{DA175274-0FF7-4436-9266-742F96C2D1ED}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cryptography", "Cryptography", "{BB7E701E-B1C6-453E-800A-E12CE256318D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Cryptography.Server", "src\Utilities\Cryptography\AliasVault.Cryptography.Server\AliasVault.Cryptography.Server.csproj", "{341EC443-0B6B-4E8C-AF46-D6156573CEA5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Cryptography.Client", "src\Utilities\Cryptography\AliasVault.Cryptography.Client\AliasVault.Cryptography.Client.csproj", "{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generators", "Generators", "{03D55CA4-20B3-4FEA-9ADD-3C7B5B10E0FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Generators.Password", "src\Generators\AliasVault.Generators.Password\AliasVault.Generators.Password.csproj", "{47F47A1B-49E0-406A-81C8-31FF2E4C339B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Generators.Identity", "src\Generators\AliasVault.Generators.Identity\AliasVault.Generators.Identity.csproj", "{80E74FBC-4EC8-45FB-B210-473337C484B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{DD359F0A-0180-4F8F-9E48-46213386BA4D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Shared.Core", "src\Shared\AliasVault.Shared.Core\AliasVault.Shared.Core.csproj", "{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BD2050C0-DC26-4777-9514-546525307370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD2050C0-DC26-4777-9514-546525307370}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD2050C0-DC26-4777-9514-546525307370}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD2050C0-DC26-4777-9514-546525307370}.Release|Any CPU.Build.0 = Release|Any CPU
{64F47C9A-FE69-4793-B469-28BAADEC6706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64F47C9A-FE69-4793-B469-28BAADEC6706}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64F47C9A-FE69-4793-B469-28BAADEC6706}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64F47C9A-FE69-4793-B469-28BAADEC6706}.Release|Any CPU.Build.0 = Release|Any CPU
{78E84B4E-57D1-491A-8F4E-9879AE49DE0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78E84B4E-57D1-491A-8F4E-9879AE49DE0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78E84B4E-57D1-491A-8F4E-9879AE49DE0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78E84B4E-57D1-491A-8F4E-9879AE49DE0F}.Release|Any CPU.Build.0 = Release|Any CPU
{ED328644-A152-403D-86EB-81201AA07744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED328644-A152-403D-86EB-81201AA07744}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED328644-A152-403D-86EB-81201AA07744}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -53,10 +81,6 @@ Global
{8E6A418A-B305-465D-857D-49953605C18E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E6A418A-B305-465D-857D-49953605C18E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E6A418A-B305-465D-857D-49953605C18E}.Release|Any CPU.Build.0 = Release|Any CPU
{427EA8E2-EA76-467E-A6BC-201EFE40C0D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{427EA8E2-EA76-467E-A6BC-201EFE40C0D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{427EA8E2-EA76-467E-A6BC-201EFE40C0D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{427EA8E2-EA76-467E-A6BC-201EFE40C0D0}.Release|Any CPU.Build.0 = Release|Any CPU
{B797C533-260E-4DA2-83B1-0EE4BCFE08DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B797C533-260E-4DA2-83B1-0EE4BCFE08DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B797C533-260E-4DA2-83B1-0EE4BCFE08DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -73,6 +97,78 @@ Global
{AF013D08-1BF6-4E23-87D2-37F614BE7952}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF013D08-1BF6-4E23-87D2-37F614BE7952}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF013D08-1BF6-4E23-87D2-37F614BE7952}.Release|Any CPU.Build.0 = Release|Any CPU
{1277105D-50CD-4CE0-9C2C-549F46867E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1277105D-50CD-4CE0-9C2C-549F46867E54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1277105D-50CD-4CE0-9C2C-549F46867E54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1277105D-50CD-4CE0-9C2C-549F46867E54}.Release|Any CPU.Build.0 = Release|Any CPU
{FE10F294-817F-477E-A24F-8597A15AF0B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE10F294-817F-477E-A24F-8597A15AF0B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE10F294-817F-477E-A24F-8597A15AF0B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE10F294-817F-477E-A24F-8597A15AF0B5}.Release|Any CPU.Build.0 = Release|Any CPU
{DD1F496F-CF10-47D1-A57F-5FA256479332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD1F496F-CF10-47D1-A57F-5FA256479332}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD1F496F-CF10-47D1-A57F-5FA256479332}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD1F496F-CF10-47D1-A57F-5FA256479332}.Release|Any CPU.Build.0 = Release|Any CPU
{A9C9A606-C87E-4298-AB32-09B1884D7487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9C9A606-C87E-4298-AB32-09B1884D7487}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9C9A606-C87E-4298-AB32-09B1884D7487}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9C9A606-C87E-4298-AB32-09B1884D7487}.Release|Any CPU.Build.0 = Release|Any CPU
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Release|Any CPU.Build.0 = Release|Any CPU
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Release|Any CPU.Build.0 = Release|Any CPU
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2CAE93E-94A7-4365-8E84-8D48CE8DD53F}.Release|Any CPU.Build.0 = Release|Any CPU
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{857BCD0E-753F-437A-AF75-B995B4D9A5FE}.Release|Any CPU.Build.0 = Release|Any CPU
{FF0B0E64-1AE2-415C-A404-0EB78010821A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF0B0E64-1AE2-415C-A404-0EB78010821A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF0B0E64-1AE2-415C-A404-0EB78010821A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF0B0E64-1AE2-415C-A404-0EB78010821A}.Release|Any CPU.Build.0 = Release|Any CPU
{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Release|Any CPU.Build.0 = Release|Any CPU
{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Release|Any CPU.Build.0 = Release|Any CPU
{E8D9C551-67D2-4651-8EDF-4262DF7375CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8D9C551-67D2-4651-8EDF-4262DF7375CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8D9C551-67D2-4651-8EDF-4262DF7375CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8D9C551-67D2-4651-8EDF-4262DF7375CE}.Release|Any CPU.Build.0 = Release|Any CPU
{DA175274-0FF7-4436-9266-742F96C2D1ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA175274-0FF7-4436-9266-742F96C2D1ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA175274-0FF7-4436-9266-742F96C2D1ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA175274-0FF7-4436-9266-742F96C2D1ED}.Release|Any CPU.Build.0 = Release|Any CPU
{341EC443-0B6B-4E8C-AF46-D6156573CEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{341EC443-0B6B-4E8C-AF46-D6156573CEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{341EC443-0B6B-4E8C-AF46-D6156573CEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{341EC443-0B6B-4E8C-AF46-D6156573CEA5}.Release|Any CPU.Build.0 = Release|Any CPU
{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E}.Release|Any CPU.Build.0 = Release|Any CPU
{47F47A1B-49E0-406A-81C8-31FF2E4C339B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47F47A1B-49E0-406A-81C8-31FF2E4C339B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47F47A1B-49E0-406A-81C8-31FF2E4C339B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47F47A1B-49E0-406A-81C8-31FF2E4C339B}.Release|Any CPU.Build.0 = Release|Any CPU
{80E74FBC-4EC8-45FB-B210-473337C484B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{80E74FBC-4EC8-45FB-B210-473337C484B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{80E74FBC-4EC8-45FB-B210-473337C484B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{80E74FBC-4EC8-45FB-B210-473337C484B5}.Release|Any CPU.Build.0 = Release|Any CPU
{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -80,7 +176,29 @@ Global
GlobalSection(NestedProjects) = preSolution
{ED328644-A152-403D-86EB-81201AA07744} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{8E6A418A-B305-465D-857D-49953605C18E} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
{427EA8E2-EA76-467E-A6BC-201EFE40C0D0} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{AF013D08-1BF6-4E23-87D2-37F614BE7952} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
{1277105D-50CD-4CE0-9C2C-549F46867E54} = {5F7417F6-4388-49CC-9511-ED63C4A6488A}
{FE10F294-817F-477E-A24F-8597A15AF0B5} = {5F7417F6-4388-49CC-9511-ED63C4A6488A}
{DD1F496F-CF10-47D1-A57F-5FA256479332} = {607945F3-9896-4544-99EC-F3496CF4D36B}
{607945F3-9896-4544-99EC-F3496CF4D36B} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
{A9C9A606-C87E-4298-AB32-09B1884D7487} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{B095A174-E528-4D38-BEC1-D1D38B3B30C0} = {8A477241-B96C-4174-968D-D40CB77F1ECD}
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
{857BCD0E-753F-437A-AF75-B995B4D9A5FE} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{FF0B0E64-1AE2-415C-A404-0EB78010821A} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{E8D9C551-67D2-4651-8EDF-4262DF7375CE} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{DA175274-0FF7-4436-9266-742F96C2D1ED} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{BB7E701E-B1C6-453E-800A-E12CE256318D} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
{341EC443-0B6B-4E8C-AF46-D6156573CEA5} = {BB7E701E-B1C6-453E-800A-E12CE256318D}
{542C7B7D-C2B4-4AE3-9B2C-C62FCF4DFF8E} = {BB7E701E-B1C6-453E-800A-E12CE256318D}
{47F47A1B-49E0-406A-81C8-31FF2E4C339B} = {03D55CA4-20B3-4FEA-9ADD-3C7B5B10E0FE}
{80E74FBC-4EC8-45FB-B210-473337C484B5} = {03D55CA4-20B3-4FEA-9ADD-3C7B5B10E0FE}
{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC} = {DD359F0A-0180-4F8F-9E48-46213386BA4D}
{15EFE0D0-F41B-47D7-86B7-8F840335CB82} = {DD359F0A-0180-4F8F-9E48-46213386BA4D}
{40CA41BF-9E67-4D0A-A3F8-38B94992E4CA} = {DD359F0A-0180-4F8F-9E48-46213386BA4D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FEE82475-C009-4762-8113-A6563D9DC49E}
EndGlobalSection
EndGlobal

6
certificates/README.md Normal file
View File

@@ -0,0 +1,6 @@
# Certificates directory structure
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

View 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
View 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

View 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;'"

View File

@@ -1,35 +1,68 @@
services:
wasm:
image: aliasvault
build:
context: .
dockerfile: src/AliasVault.WebApp/Dockerfile
reverse-proxy:
image: ghcr.io/lanedirt/aliasvault-reverse-proxy:latest
ports:
- "80:8080"
restart: always
environment:
- API_URL=http://localhost:81
server:
image: aliasvault-server
build:
context: .
dockerfile: src/AliasVault/Dockerfile
ports:
- "82:8082"
- "80:80"
- "443:443"
volumes:
- ./database:/database
- ./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: 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:
- ./database:/database
- ./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: ghcr.io/lanedirt/aliasvault-smtp:latest
ports:
- "25:25"
- "587:587"
volumes:
- ./database:/database:rw
- ./logs:/logs:rw
env_file:
- .env
restart: always
networks:
aliasvault:
name: aliasvault_default

5
docs/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Documentation
This is the documentation for the AliasVault project.
## Description
TODO: Work in progress.

View File

@@ -0,0 +1,12 @@
The webauthn implementation in order to quick unlock the vault requires the use of a FIDO2 authenticator.
This can be either the built-in browser authenticator or an external authenticator like a Yubikey.
At the time of writing (2024-10-04), only some browsers support the required PRF extension. In order to make it work in Chrome, you need to enable the PRF extension in the browser settings.
## Chrome
1. Open the Chrome browser and navigate to `chrome://flags/#enable-experimental-web-platform-features`.
2. Enable the `Experimental Web Platform features` flag.
3. Restart the browser.
4. Now it should be possible to use the built-in chrome password manager to unlock the vault.

View File

@@ -0,0 +1,18 @@
To configure SQLite for use with WebAssembly follow these steps:
1. Add NuGet package
```
dotnet add package SQLitePCLRaw.bundle_e_sqlite3
```
2. Modify .csproj and add the following:
```xml
<PropertyGroup>
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>
```
3. Make sure the "wasm-tools" workload is installed on the local machine in order to build the project:
```
dotnet workload install wasm-tools
```

View File

@@ -0,0 +1,86 @@
# Run GitHub Actions Locally
This guide will help you set up and run GitHub Actions locally on Linux, which can be useful for debugging and testing your workflows without pushing changes to the repository.
## Prerequisites
- Linux (Ubuntu or RHEL-based distributions)
- [Docker](https://www.docker.com/) installed and running
## Setup Instructions
### 1. Install GitHub CLI
First, install the GitHub CLI using Homebrew:
```bash
sudo dnf install 'dnf-command(config-manager)'
sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
sudo dnf install gh --repo gh-cli
```
### 2. Install Nektos/Act
Next, install the Nektos/Act extension for GitHub CLI:
```bash
gh extension install https://github.com/nektos/gh-act
```
## Basic usage
To run GitHub Actions locally, navigate to the root of your Git project and execute:
```bash
act
```
This command will pull the necessary Docker containers and execute the GitHub Actions defined in your repository.
### Understanding the `-P` Option
By default, `act` uses a simple Docker container that is small in size. However, official GitHub runners are much larger (10GB or even 100GB+). When certain commands or environments are needed, you should specify the full runner image using the `-P` option.
The `-P` option allows you to map the platform to a specific Docker image. This is particularly useful when you need to replicate the environment of the official GitHub runners more closely.
Syntax:
```bash
act -P ubuntu-latest=catthehacker/ubuntu:full-latest
```
This command tells `act` to use the `catthehacker/ubuntu:full-latest` Docker image for the `ubuntu-latest` platform, which is a more complete representation of the GitHub-hosted runner environment.
## Debugging E2E Tests for AliasVault
To run and debug the E2E tests for AliasVault using a more complete runner image, use the following command:
```bash
act -W .github/workflows/dotnet-e2e-tests.yml -P ubuntu-latest=catthehacker/ubuntu:full-latest
```
This command does the following:
- `-W .github/workflows/dotnet-e2e-tests.yml`: Specifies the workflow file to run
- `-P ubuntu-latest=catthehacker/ubuntu:full-latest`: Uses a more complete Ubuntu image that better replicates the GitHub-hosted runner environment
Running this command will execute the E2E tests locally, allowing you to debug and test your workflow without pushing changes to the repository.
```bash
docker image prune -a -f && docker system prune -a -f
```
## Troubleshooting
### Handling Disk Space Errors
If you encounter disk space errors, you can free up space by pruning Docker images and system resources:
### Misc
If you encounter any issues while running GitHub Actions locally, consider the following:
1. Ensure Docker is running and has sufficient resources allocated.
2. Check that your workflow file is correctly formatted and placed in the `.github/workflows/` directory.
3. Verify that all required secrets and environment variables are properly set.
4. If you're using specific tools or commands that are available in GitHub-hosted runners but not in the default `act` image, make sure to use the `-P` option with an appropriate image as shown in the E2E tests example.
For more detailed information and advanced usage, refer to the [Nektos/Act GitHub repository](https://github.com/nektos/act).

View File

@@ -0,0 +1,12 @@
To upgrade the AliasClientDb EF model, follow these steps:
1. Make changes to the AliasClientDb EF model in the `AliasClientDb` project.
2. Create a new migration by running the following command in the `AliasClientDb` project:
```bash
# Important: make sure the migration name is prefixed by the Semver version number of the release.
# For example, if the release version is 1.0.0, the migration name should be `1.0.0-<migration-name>`.
dotnet ef migrations add "1.0.0-<migration-name>"
```
4. On the next login of a user, they will be prompted (required) to upgrade their database schema to the latest version.
Make sure to manually test this.

30
docs/diagrams/README.md Normal file
View File

@@ -0,0 +1,30 @@
# Diagrams
This folder contains architecture and flow diagrams for AliasVault in various formats.
## Draw.io Diagrams (.drawio)
Files with `.drawio` extension are created with Draw.io (also known as diagrams.net), an open-source diagramming tool.
### How to Open/Edit Draw.io Files
1. Web Interface (Cloud)
- Visit [diagrams.net](https://app.diagrams.net/)
- Open source code available at [github.com/jgraph/drawio](https://github.com/jgraph/drawio)
2. Desktop Applications (Offline)
- Available for Windows, macOS, and Linux
- Download from [github.com/jgraph/drawio-desktop/releases](https://github.com/jgraph/drawio-desktop/releases)
- Open source code available at [github.com/jgraph/drawio-desktop](https://github.com/jgraph/drawio-desktop)
3. VS Code Extension
- Install the [Draw.io Integration](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio) extension
- Edit diagrams directly within VS Code
- Source code at [github.com/hediet/vscode-drawio](https://github.com/hediet/vscode-drawio)
## Mermaid Diagrams (.mmd)
Files with `.mmd` extension are [Mermaid](https://mermaid.js.org/) format diagrams. These are text-based diagram definitions that can be rendered by various tools.
### Editors & Tools for Mermaid
- [Mermaid Live Editor](https://github.com/mermaid-js/mermaid-live-editor) - Web-based editor with live preview
- [VS Code Mermaid Extension](https://github.com/mermaid-js/vscode-mermaid) - Preview and edit Mermaid diagrams directly in VS Code
- [Obsidian Mermaid Plugin](https://github.com/jobindj/obsidian-mermaid) - If you use Obsidian for documentation
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#mermaid) and [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) both render Mermaid diagrams natively in markdown

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1024 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,452 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.17 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.17">
<diagram name="Page-1" id="ykhTdbPCDOXpVAqZYsCj">
<mxGraphModel dx="1775" dy="1249" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="6F2B19X3ZkVbRV3rCgbW-1" value="Legend" style="shape=table;startSize=30;container=1;collapsible=0;childLayout=tableLayout;fontSize=16;align=left;verticalAlign=top;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;fontStyle=1;spacingLeft=6;spacing=0;resizable=0;" vertex="1" parent="1">
<mxGeometry x="275" y="1058" width="723" height="60" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-1">
<mxGeometry y="30" width="723" height="30" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-3" value="Cryptographic Operations" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#432D57;overflow=hidden;fillColor=#76608a;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;align=left;fontColor=#ffffff;spacingLeft=10;spacingRight=4;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-2">
<mxGeometry width="163" height="30" as="geometry">
<mxRectangle width="163" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-203" value="Storage Elements" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#006EAF;overflow=hidden;fillColor=#1ba1e2;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;align=left;fontColor=#ffffff;spacingLeft=10;spacingRight=4;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-2">
<mxGeometry x="163" width="120" height="30" as="geometry">
<mxRectangle width="120" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-208" value="&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;Keys and Sensitive Data&lt;/span&gt;" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#B09500;overflow=hidden;fillColor=#e3c800;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;align=left;fontColor=#000000;spacingLeft=10;spacingRight=4;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-2">
<mxGeometry x="283" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-213" value="Authentication Steps" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#2D7600;overflow=hidden;fillColor=#60a917;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;align=left;fontColor=#ffffff;spacingLeft=10;spacingRight=4;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-2">
<mxGeometry x="453" width="150" height="30" as="geometry">
<mxRectangle width="150" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-218" value="Process step" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#6D1F00;overflow=hidden;fillColor=#a0522d;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;align=left;fontColor=#ffffff;spacingLeft=10;spacingRight=4;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-2">
<mxGeometry x="603" width="120" height="30" as="geometry">
<mxRectangle width="120" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-15" value="Client (WebAssembly)" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="40" y="52" width="1340" height="470" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-63" value="Vault operations" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="526.84" y="82" width="160" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-32" value="Server (REST API)" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="40" y="652" width="1340" height="390" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-43" value="" style="group;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" connectable="0" parent="1">
<mxGeometry x="70" y="682.65" width="410" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-33" value="Authentication flow" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry width="410" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-34" value="SRP server verification" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="150" y="40" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-35" value="2FA (Optional)" style="text;html=1;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="16" y="120" width="378" height="90" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-81" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-43" source="6F2B19X3ZkVbRV3rCgbW-36" target="6F2B19X3ZkVbRV3rCgbW-37">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-36" value="Google Authenticator or compatible" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="34" y="155" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-82" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-43" source="6F2B19X3ZkVbRV3rCgbW-37" target="6F2B19X3ZkVbRV3rCgbW-38">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-37" value="Time-based OTP" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="154" y="155" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-38" value="Verify OTP code" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="274" y="155" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-39" value="Issue JWT token" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-43">
<mxGeometry x="150" y="239.35" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-40" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-43" source="6F2B19X3ZkVbRV3rCgbW-34" target="6F2B19X3ZkVbRV3rCgbW-35">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-256" y="440" as="sourcePoint" />
<mxPoint x="-206" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-41" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-43" source="6F2B19X3ZkVbRV3rCgbW-35" target="6F2B19X3ZkVbRV3rCgbW-39">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="215" y="100" as="sourcePoint" />
<mxPoint x="215" y="130" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-44" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-26" target="6F2B19X3ZkVbRV3rCgbW-66">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="369" y="640" as="sourcePoint" />
<mxPoint x="419" y="590" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-45" value="SRP handshake" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-44">
<mxGeometry x="-0.1654" y="1" relative="1" as="geometry">
<mxPoint x="8" y="15" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-27" value="AES256-GCM" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="554" y="222" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-26" value="SRP client" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="164" y="453" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-60" value="Key derivation and server authentication" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="95" y="82" width="250" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-16" value="Master Password &lt;br&gt;(not persisted)" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="145" y="172" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-20" value="Argon2Id" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="165" y="247" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-22" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-16" target="6F2B19X3ZkVbRV3rCgbW-20">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="405" y="282" as="sourcePoint" />
<mxPoint x="455" y="232" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-23" value="Derived Key &lt;br&gt;(stored in app memory)" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="144" y="320" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-24" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-20" target="6F2B19X3ZkVbRV3rCgbW-23">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="405" y="282" as="sourcePoint" />
<mxPoint x="455" y="232" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-28" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;curved=1;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-23" target="6F2B19X3ZkVbRV3rCgbW-26">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="420" y="552" as="sourcePoint" />
<mxPoint x="470" y="502" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-30" value="Used for authentication&lt;br&gt;with server" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-28">
<mxGeometry x="-0.1756" y="2" relative="1" as="geometry">
<mxPoint x="-2" y="24" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-67" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-66" target="6F2B19X3ZkVbRV3rCgbW-34">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-66" value="SRP salt/verifier" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="144" y="559" width="150" height="48" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-72" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-68" target="6F2B19X3ZkVbRV3rCgbW-27">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="609" y="392" />
<mxPoint x="609" y="392" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-180" value="Decrypt with&lt;br&gt;derived key" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-72">
<mxGeometry x="0.6371" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-68" value="Retrieve encrypted vault" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="1">
<mxGeometry x="549" y="330" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-47" value="Server storage" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;container=0;" vertex="1" parent="1">
<mxGeometry x="540" y="682.65" width="140" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-70" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-56" target="6F2B19X3ZkVbRV3rCgbW-68">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-181" value="Retrieve vault &lt;br&gt;from server" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-70">
<mxGeometry x="0.2454" y="1" relative="1" as="geometry">
<mxPoint y="-26" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-56" value="Encrypted Vault(s)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;container=0;" vertex="1" parent="1">
<mxGeometry x="556" y="712.65" width="110" height="80" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-76" value="Claiming new email address" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="720" y="82" width="250" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-83" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-77" target="6F2B19X3ZkVbRV3rCgbW-79">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-100" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-77" target="6F2B19X3ZkVbRV3rCgbW-78">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-77" value="RSA/OAEP Key Generation" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="790" y="122" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-78" value="Public key" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="852" y="202" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-79" value="Private key" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="742" y="202" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-86" value="" style="group;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" connectable="0" parent="1">
<mxGeometry x="870" y="687" width="490" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-87" value="Email system" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry width="490" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-113" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-86" source="6F2B19X3ZkVbRV3rCgbW-109" target="6F2B19X3ZkVbRV3rCgbW-112">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-114" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-113">
<mxGeometry x="-0.1453" y="2" relative="1" as="geometry">
<mxPoint x="2" y="-12" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-116" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-86" source="6F2B19X3ZkVbRV3rCgbW-109" target="6F2B19X3ZkVbRV3rCgbW-115">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-117" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-116">
<mxGeometry x="0.3626" y="3" relative="1" as="geometry">
<mxPoint x="18" y="-2" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-109" value="Valid Email Claim?" style="rhombus;whiteSpace=wrap;html=1;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry x="298.780487804878" y="225" width="95.60975609756098" height="80" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-112" value="Reject email" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry x="380" y="155.65" width="103.41" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-120" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-86" source="6F2B19X3ZkVbRV3rCgbW-115" target="6F2B19X3ZkVbRV3rCgbW-121">
<mxGeometry relative="1" as="geometry">
<mxPoint x="89.6341463414634" y="201.30000000000007" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-125" value="Encrypt email contents &lt;br&gt;with symmetric key &lt;div&gt;&lt;br/&gt;&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-120">
<mxGeometry x="-0.4172" y="1" relative="1" as="geometry">
<mxPoint x="1" y="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-133" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-86" source="6F2B19X3ZkVbRV3rCgbW-115" target="6F2B19X3ZkVbRV3rCgbW-124">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-115" value="Random generated&lt;br&gt;symmetric key" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry x="239.9982926829268" y="145.65" width="131.46341463414635" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-121" value="AES256-GCM" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry x="95.60975609756098" y="35.650000000000006" width="119.51219512195121" height="35.65" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-137" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="6F2B19X3ZkVbRV3rCgbW-86" source="6F2B19X3ZkVbRV3rCgbW-124" target="6F2B19X3ZkVbRV3rCgbW-121">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-124" value="Encrypt symmetric key with public key" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="6F2B19X3ZkVbRV3rCgbW-86">
<mxGeometry x="70.00121951219514" y="105.65" width="131.46341463414635" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-101" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-78" target="6F2B19X3ZkVbRV3rCgbW-99">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="892" y="292" />
<mxPoint x="750" y="292" />
<mxPoint x="750" y="882" />
<mxPoint x="666" y="882" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-102" value="Save in server &lt;br&gt;public key store" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-101">
<mxGeometry x="0.4464" y="-2" relative="1" as="geometry">
<mxPoint x="9" y="-242" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-103" value="Register new email&lt;br&gt;claim" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="1">
<mxGeometry x="781" y="332" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-140" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-99" target="6F2B19X3ZkVbRV3rCgbW-124">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-141" value="Retrieve public key&lt;div&gt;associated with email claim&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-140">
<mxGeometry x="0.4692" relative="1" as="geometry">
<mxPoint x="17" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-99" value="Public key store" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="556" y="872.65" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-138" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-105" target="6F2B19X3ZkVbRV3rCgbW-109">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-153" value="Retrieve registered email &lt;br&gt;address claims" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-138">
<mxGeometry x="0.2388" y="-5" relative="1" as="geometry">
<mxPoint x="-10" y="-2" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-105" value="Email claim store" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="555" y="932.65" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-158" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-107" target="6F2B19X3ZkVbRV3rCgbW-109">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-107" value="External email received from internet" style="shape=message;html=1;html=1;outlineConnect=0;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="1186.3899999999999" y="1058" width="60" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-106" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="830" y="372" as="sourcePoint" />
<mxPoint x="665" y="942" as="targetPoint" />
<Array as="points">
<mxPoint x="830" y="372" />
<mxPoint x="830" y="942" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-152" value="Register email claim&lt;br&gt;on server" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-106">
<mxGeometry x="-0.3735" relative="1" as="geometry">
<mxPoint x="11" y="-153" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-123" value="Encrypted email(s)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="556" y="812.65" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-134" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-121" target="6F2B19X3ZkVbRV3rCgbW-123">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1025" y="703" />
<mxPoint x="850" y="703" />
<mxPoint x="850" y="843" />
<mxPoint x="666" y="843" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-135" value="Store encrypted email" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-134">
<mxGeometry x="0.6857" relative="1" as="geometry">
<mxPoint x="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-136" value="Email retrieval and decryption" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="996.3900000000001" y="82" width="250" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-145" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-143" target="6F2B19X3ZkVbRV3rCgbW-144">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-146" value="Decrypt email using private key&lt;br&gt;&amp;nbsp;stored in vault" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-145">
<mxGeometry x="0.1105" y="2" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-143" value="Retrieve encrypted email" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="1">
<mxGeometry x="1061.39" y="122" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-148" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-144" target="6F2B19X3ZkVbRV3rCgbW-162">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1121.39" y="322" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-144" value="AES256-GCM" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#76608a;fontColor=#ffffff;strokeColor=#432D57;" vertex="1" parent="1">
<mxGeometry x="1066.39" y="232" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-150" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-123">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1060" y="142" as="targetPoint" />
<Array as="points">
<mxPoint x="672" y="823" />
<mxPoint x="920" y="822" />
<mxPoint x="920" y="482" />
<mxPoint x="1040" y="482" />
<mxPoint x="1040" y="142" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-151" value="Retrieve encrypted email" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-150">
<mxGeometry x="-0.6389" y="-1" relative="1" as="geometry">
<mxPoint x="-117" y="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-159" value="" style="shape=image;aspect=fixed;image=data:image/svg+xml,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MDAgNTAwIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MDAgNTAwIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJtNDU5Ljg3IDI5NC45NWMwLjAxNjIwNSA1LjQwMDUgMC4wMzI0MSAxMC44MDEtMC4zNTAyMiAxNi44NzMtMS4xMTEgNi4zMzkyLTEuMTk0MSAxMi4xNzMtMi42MzUxIDE3LjY0OS0xMC45MjIgNDEuNTA4LTM2LjczMSA2OS40ODEtNzcuMzUxIDgzLjQwOC03LjIxNTcgMi40NzM5LTE0Ljk3MiAzLjM3MDItMjIuNDc5IDQuOTk1LTIzLjYyOSAwLjA0MjIwNS00Ny4yNTcgMC4xMTQ1My03MC44ODYgMC4xMjAyNy00Ni43NjIgMC4wMTEzMjItOTMuNTIzLTAuMDE0MTYtMTQwLjk1LTAuNDM0MTEtOC41OS0yLjAwMjQtMTYuNzY2LTIuODM1Mi0yNC4zOTgtNS4zMzI2LTIxLjU5NS03LjA2NjYtMzkuNTIzLTE5LjY1Ni01My43MDgtMzcuNTUyLTEwLjIyNy0xMi45MDMtMTcuNTc5LTI3LjE3LTIxLjI4LTQzLjIyMS0xLjQ3NS02LjM5NjctMi40NzExLTEyLjkwNC0zLjY4NTItMTkuMzYxLTAuMDUxODQ5LTUuNzQ3LTAuMTAzNy0xMS40OTQgMC4yNjkxNS0xNy44ODYgNC4xNTktNDIuOTczIDI3LjY4LTcxLjYzOCA2My41NjItOTIuMTUzIDAtMC43MDc2MS0wLjAwMTk2MS0xLjY5ODggMy4xMmUtNCAtMi42OSAwLjAyMjQ4NC05LjgyOTMtMS4zMDcxLTE5Ljg5NCAwLjM1NjY0LTI5LjQzOCAzLjIzOTEtMTguNTc5IDExLjA4LTM1LjI3MiAyMy43NjMtNDkuNzczIDEyLjA5OC0xMy44MzIgMjYuNDU3LTIzLjk4OSA0My42MDktMzAuMDI5IDcuODEzLTIuNzUxMiAxNi4xNC00LjA0MTcgMjQuMjM0LTUuOTk0OCA3LjM5Mi0wLjAyNTczNCAxNC43ODQtMC4wNTE0NiAyMi44MzUgMC4zMjI1MyA0LjE5NTkgMC45NTM5MiA3Ljc5NDYgMS4yNTM4IDExLjI1OCAyLjEwNTMgMTcuMTYgNC4yMTkyIDMyLjI4NyAxMi4xNzYgNDUuNDY5IDI0LjEwNCAyLjI1NTggMi4wNDExIDQuMzcyIDYuNjI0MSA5LjYyMSAzLjg2OCAxNi44MzktOC44NDE5IDM0LjcxOC0xMS41OTcgNTMuNjAzLTguNTk0IDE2Ljc5MSAyLjY2OTkgMzEuNjAyIDkuNDMwOCA0NC4yMzYgMjAuNjM2IDExLjUzMSAxMC4yMjcgMTkuODQgMjIuODQxIDI1LjM5MyAzNy4yMzYgNi4zNDM2IDE2LjQ0NSAxMC4zODkgMzMuMTYzIDYuMDc5OCA0OS4zODkgNy45NTg3IDguOTMyMSAxNS44MDcgMTYuNzA0IDIyLjQyMSAyNS40MTQgOS4xNjIgMTIuMDY1IDE1LjMzIDI1Ljc0NiAxOC4xNDQgNDAuNzc2IDAuOTcwNDYgNS4xODQ4IDEuOTExMSAxMC4zNzUgMi44NjU0IDE1LjU2M20tNzEuNTk3IDcxLjAxMmM1LjU2MTUtNS4yMjg0IDEyLjAwMi05Ljc5ODYgMTYuNTA4LTE1LjgxNyAxMC40NzQtMTMuOTkyIDE0LjMzMy0yOS45MTYgMTEuMjg4LTQ3LjQ0Ni0yLjI0OTYtMTIuOTUtOC4xOTczLTI0LjA3Ni0xNy4yNDMtMzMuMDYzLTEyLjc0Ni0xMi42NjMtMjguODY1LTE4LjYxNC00Ni43ODYtMTguNTY5LTY5LjkxMiAwLjE3NzEyLTEzOS44MiAwLjU2ODMxLTIwOS43NCAwLjk2MTc2LTE1LjkyMiAwLjA4OTU5OS0yOS4xNjggNy40MjA5LTM5LjY4NSAxOC4yOTYtMTQuNDUgMTQuOTQ0LTIwLjQwOCAzMy4zNDMtMTYuNjU1IDU0LjM2OCAyLjI3NjMgMTIuNzU0IDguMjE2NyAyMy43NDggMTcuMTU4IDMyLjY2IDEzLjI5OSAxMy4yNTUgMzAuMDk3IDE4LjY1MyA0OC43MjggMTguNjUxIDU5LjMyMS0wLjAwNTE4OCAxMTguNjQgMC4wNDIzNTggMTc3Ljk2LTAuMDQ2NjAxIDkuNTkxMi0wLjAxNDM3NCAxOS4xODEtMC44NjU4OCAyOC43NzMtMC44ODg1NSAxMC42NDktMC4wMjUxNDYgMTkuOTc4LTMuODI1IDI5LjY4Ny05LjEwNzR6IiBmaWxsPSIjRUVDMTcwIi8+CjxwYXRoIGQ9Im0xNjIuNzcgMjkzYzE1LjY1NCA0LjM4ODMgMjAuNjI3IDIyLjk2NyAxMC4zMDQgMzQuOTgtNS4zMTA0IDYuMTc5NS0xNC44MTcgOC4zMjA4LTI0LjI3OCA1LjA0NzItNy4wNzIzLTIuNDQ3MS0xMi4zMzItMTAuMzYyLTEyLjg3Ni0xNy45MzMtMS4wNDUxLTE0LjU0MiAxMS4wODktMjMuMTc2IDIxLjcwNS0yMy4wNDYgMS41Nzk0IDAuMDE5Mjg3IDMuMTUxNyAwLjYxNTY2IDUuMTQ2MSAwLjk1MTg0eiIgZmlsbD0iI0VFQzE3MCIvPgo8cGF0aCBkPSJtMjI3LjE4IDI5My42NGM3Ljg0OTkgMi4zOTczIDExLjkzOCA4LjIxNDMgMTMuNTI0IDE1LjA3NyAxLjg1OTEgOC4wNDM5LTAuNDQ4MTcgMTUuNzA2LTcuMTU4OCAyMS4xMjEtNi43NjMzIDUuNDU3Mi0xNC40MTcgNi44Nzk0LTIyLjU3OCAzLjE0ODMtOC4yOTcyLTMuNzkzMy0xMi44MzYtMTAuODQ5LTEyLjczNi0xOS40MzggMC4xNjg3LTE0LjQ5NyAxNC4xMy0yNS4zNjggMjguOTQ4LTE5LjkwOHoiIGZpbGw9IiNFRUMxNzAiLz4KPHBhdGggZD0ibTI2MS41NyAzMTkuMDdjLTIuNDk1LTE0LjQxOCA0LjY4NTMtMjIuNjAzIDE0LjU5Ni0yNi4xMDggOS44OTQ1LTMuNDk5NSAyMy4xODEgMy40MzAzIDI2LjI2NyAxMy43NzkgNC42NTA0IDE1LjU5MS03LjE2NTEgMjkuMDY0LTIxLjY2NSAyOC4xNjEtOC41MjU0LTAuNTMwODgtMTcuMjAyLTYuNTA5NC0xOS4xOTgtMTUuODMxeiIgZmlsbD0iI0VFQzE3MCIvPgo8cGF0aCBkPSJtMzM2LjkxIDMzMy40MWMtOS4wMTc1LTQuMjQ5MS0xNS4zMzctMTQuMzQ5LTEzLjgyOS0yMS42ODIgMy4wODI1LTE0Ljk4OSAxMy4zNDEtMjAuMzA0IDIzLjAxOC0xOS41ODUgMTAuNjUzIDAuNzkxNDEgMTcuOTMgNy40MDcgMTkuNzY1IDE3LjU0NyAxLjk1ODggMTAuODI0LTQuMTE3MSAxOS45MzktMTMuNDk0IDIzLjcwMy01LjI3MiAyLjExNjItMTAuMDkxIDEuNTA4Ni0xNS40NiAwLjAxNzg4M3oiIGZpbGw9IiNFRUMxNzAiLz4KPC9zdmc+Cg==;" vertex="1" parent="1">
<mxGeometry x="40" y="1060" width="58" height="58" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-160" value="AliasVault" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=24;fontStyle=1" vertex="1" parent="1">
<mxGeometry x="100" y="1064" width="130" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-161" value="Security architecture 0.6.0" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="98" y="1088" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-162" value="Decrypted email" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="1056.39" y="320" width="130" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-58" value="JWT token for &lt;br&gt;authenticating&lt;br&gt;with REST API" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="364" y="559" width="155" height="48" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-165" value="Client local storage" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;container=0;" vertex="1" parent="1">
<mxGeometry x="370" y="82" width="140" height="320" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-166" value="Decrypted vault&lt;br&gt;(app memory)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;container=0;" vertex="1" parent="1">
<mxGeometry x="380" y="112" width="110" height="80" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-167" value="JWT Token&lt;br&gt;(browser local storage)" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="383" y="312" width="116" height="50" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-169" value="Derived Key&lt;br&gt;(app&lt;span style=&quot;background-color: initial;&quot;&gt;&amp;nbsp;memory)&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" vertex="1" parent="1">
<mxGeometry x="383" y="242" width="116" height="50" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-84" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-79" target="6F2B19X3ZkVbRV3rCgbW-166">
<mxGeometry relative="1" as="geometry">
<mxPoint x="630" y="222" as="targetPoint" />
<Array as="points">
<mxPoint x="752" y="152" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-85" value="Stored in vault" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-84">
<mxGeometry x="0.3345" y="3" relative="1" as="geometry">
<mxPoint x="7" y="-3" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-170" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" target="6F2B19X3ZkVbRV3rCgbW-169">
<mxGeometry relative="1" as="geometry">
<mxPoint x="280" y="342" as="sourcePoint" />
<Array as="points">
<mxPoint x="320" y="342" />
<mxPoint x="320" y="267" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-173" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-58" target="6F2B19X3ZkVbRV3rCgbW-167">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-174" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-39" target="6F2B19X3ZkVbRV3rCgbW-58">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="500" y="942" />
<mxPoint x="500" y="632" />
<mxPoint x="442" y="632" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-176" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-175" target="6F2B19X3ZkVbRV3rCgbW-16">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-175" value="User login" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a0522d;fontColor=#ffffff;strokeColor=#6D1F00;" vertex="1" parent="1">
<mxGeometry x="162" y="112" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-178" value="&lt;span style=&quot;&quot;&gt;JWT token protected API endpoints&lt;/span&gt;" style="shape=corner;whiteSpace=wrap;html=1;fontColor=default;fillColor=#e3c800;strokeColor=#B09500;" vertex="1" parent="1">
<mxGeometry x="567" y="552" width="803" height="80" as="geometry" />
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-179" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="6F2B19X3ZkVbRV3rCgbW-27" target="6F2B19X3ZkVbRV3rCgbW-166">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="609" y="212" />
<mxPoint x="435" y="212" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="6F2B19X3ZkVbRV3rCgbW-182" value="Store vault &lt;br&gt;in local WASM memory" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6F2B19X3ZkVbRV3rCgbW-179">
<mxGeometry x="-0.4412" y="1" relative="1" as="geometry">
<mxPoint x="28" y="-21" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,118 @@
%%{ init: { 'flowchart': { 'curve': 'basis' } } }%%
graph TB
%% Legend
subgraph Legend["Legend"]
L_CRYPTO["Cryptographic Operations"]
L_STORAGE["Storage Elements"]
L_KEY["Keys and Sensitive Data"]
L_PROCESS["Process Steps"]
L_AUTH["Authentication Steps"]
L_FLOW["Email Processing Flow"]
end
subgraph Client["Client (Local Only Operations)"]
direction TB
MP[/"Master Password\n(never leaves client)"/]
subgraph KD["1. Key Derivation"]
A2["Argon2id"]
DK[/"Derived Key"/]
MP --> A2
A2 --> DK
DK --> |"used for vault\nencryption/decryption"| AES
DK --> |"used for authentication"| SRP_C
end
subgraph VE["3. Vault Operations"]
AES["AES256-GCM"]
VAULT["Encrypted Vault Contents"]
AES --> |"encrypt/decrypt"| VAULT
end
subgraph KP["4. Email Key Management"]
RSA["RSA/OAEP Key Generation"]
PRK[/"Private Key\n(stored in vault)"/]
PBK[/"Public Key"/]
RSA --> |"generates pair"| PRK
RSA --> |"generates pair"| PBK
end
subgraph ED["5. Email Decryption"]
PRK --> |"decrypt symmetric key"| SK[/"Symmetric Key\n(AES256)"/]
SK --> |"decrypt email"| DC["Decrypted Email"]
end
end
subgraph Server["Server"]
direction TB
subgraph AUTH["Authentication Flow"]
SRP_S["SRP Verification"]
subgraph FA["2FA (Optional)"]
TOTP["Time-based OTP"]
GA["Google Authenticator\nor Compatible App"]
VERIFY["Verify OTP Code"]
GA --> |"generate code"| TOTP
TOTP --> |"user enters"| VERIFY
end
JWT["Issue JWT Token"]
SRP_S --> FA
FA --> |"if 2FA enabled"| JWT
SRP_S --> |"if 2FA disabled"| JWT
end
subgraph VS["Vault Storage"]
EV["Encrypted Vault Data"]
end
subgraph ES["Email System"]
EC["Email Claims"]
PKS["Public Key Store"]
subgraph EP["Email Processing"]
CHECK{"Valid\nEmail Claim?"}
REJECT["Reject Email"]
ESK[/"Generate Random\nSymmetric Key"/]
EE["Encrypt Email\nContent"]
ESP["Encrypt Symmetric Key\nwith Public Key"]
EST["Store Encrypted Email\n& Encrypted Sym Key"]
CHECK --> |"No"| REJECT
CHECK --> |"Yes"| ESK
ESK --> EE
ESK --> ESP
EE --> EST
ESP --> EST
end
end
end
%% Client-Server Interactions
SRP_C["SRP Client"] <--> |"SRP Authentication"| SRP_S
AES <--> |"encrypted vault transfer"| EV
PBK --> |"register"| PKS
EST --> |"retrieve encrypted\nemail & sym key"| ED
%% Styling
classDef process fill:#ddd,stroke:#333,stroke-width:2px
classDef storage fill:#b7e3fc,stroke:#333,stroke-width:2px
classDef key fill:#fef08a,stroke:#333,stroke-width:2px
classDef crypto fill:#e9d5ff,stroke:#333,stroke-width:2px
classDef auth fill:#86efac,stroke:#333,stroke-width:2px
classDef flow fill:#fca5a5,stroke:#333,stroke-width:2px
%% Apply styles to legend
class L_CRYPTO crypto
class L_STORAGE storage
class L_KEY key
class L_PROCESS process
class L_AUTH auth
class L_FLOW flow
%% Apply styles to elements
class A2,SRP_C,SRP_S,RSA,AES crypto
class EV,EST storage
class MP,DK,PRK,PBK,SK,ESK key
class KD,VE,KP,ED process
class SRP_S,FA,JWT,TOTP,VERIFY auth
class CHECK,EP flow

BIN
docs/img/screenshot.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

View File

@@ -0,0 +1,133 @@
# Manual Setup Instructions for AliasVault
This README provides step-by-step instructions for manually setting up AliasVault without using the `install.sh` script. Follow these steps if you prefer to execute all statements yourself.
## Prerequisites
- Docker and Docker Compose installed on your system
- OpenSSL for generating random passwords
## Steps
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
```
3. **Set HOSTNAME**
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:
```bash
JWT_KEY=your_generated_key_here
```
5. **Generate and set DATA_PROTECTION_CERT_PASS**
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
```
Or to disable email:
```bash
PRIVATE_EMAIL_DOMAINS=DISABLED.TLD
```
7. **Set SUPPORT_EMAIL (Optional)**
Add a support email address if desired:
```bash
SUPPORT_EMAIL=support@yourdomain.com
```
8. **Generate admin password**
Build the Docker image for password hashing:
```bash
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile .
```
Generate the password hash:
```bash
docker run --rm installcli "your_preferred_admin_password_here"
```
Add the password hash and generation timestamp to the .env file:
```bash
ADMIN_PASSWORD_HASH=<output_from_previous_command>
ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z
```
9. **Build and start Docker containers**
Build the Docker Compose stack:
```bash
docker compose build
```
Start the Docker Compose stack:
```bash
docker compose up -d
```
10. **Access AliasVault**
AliasVault should now be running. You can access it at:
- Admin Panel: https://localhost/admin
- Username: admin
- Password: [Use the password you set in step 8]
- Client Website: https://localhost/
- Create your own account from here
## Important Notes
- 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
If you encounter any issues during the setup:
1. Check the Docker logs:
```bash
docker compose logs
```
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.

View File

@@ -0,0 +1,75 @@
# Security Architecture
AliasVault implements a zero-knowledge architecture where sensitive user data and passwords never leave the client device in unencrypted form. Below is a detailed explanation of how the system secures user data and communications.
## Diagram
The security architecture diagram below illustrates all encryption and authentication processes used in AliasVault to secure user data and communications.
<picture>
<source media="(prefers-color-scheme: dark)" srcset="diagrams/security-architecture/aliasvault-security-architecture-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="diagrams/security-architecture/aliasvault-security-architecture-light.svg">
<img alt="AliasVault Security Architecture Diagram" src="diagrams/security-architecture/aliasvault-security-architecture-light.svg">
</picture>
You can also view the diagram in a browser-friendly HTML format: [AliasVault Security Architecture](diagrams/security-architecture/aliasvault-security-architecture.html)
## Key Components and Process Flow
### 1. Key Derivation
- When a user enters their master password, it remains strictly on the client device
- The master password is processed through Argon2id (a memory-hard key derivation function) locally
- The derived key serves two purposes:
- Authentication with the server through the SRP protocol
- Local encryption/decryption of vault contents using AES-256-GCM
### 2. Authentication Process
1. SRP (Secure Remote Password) Authentication
- Enables secure password-based authentication without transmitting the password
- Client and server perform a cryptographic handshake to verify identity
2. Two-Factor Authentication (Optional)
- If enabled, requires an additional verification step after successful SRP authentication
- Uses Time-based One-Time Password (TOTP) protocol
- Compatible with standard authenticator apps (e.g., Google Authenticator)
- Server only issues the final JWT access token after successful 2FA verification
### 3. Vault Operations
- All vault contents are encrypted/decrypted locally using AES-256-GCM
- The encryption key is derived from the user's master password
- Only encrypted data is ever transmitted to or stored on the server
- The server never has access to the unencrypted vault contents
### 4. Email System Security
#### Key Generation and Storage
1. RSA key pair is generated locally on the client
2. Private key is stored in the encrypted vault
3. Public key is sent to the server and associated with email claim(s)
#### Email Reception Process
1. When an email is received, the server:
- Verifies if the recipient has a valid email claim
- If no valid claim exists, the email is rejected
- If valid, generates a random 256-bit symmetric key
- Encrypts the email content using this symmetric key
- Encrypts the symmetric key using the recipient's public key
- Stores both the encrypted email and encrypted symmetric key
#### Email Retrieval Process
1. Client retrieves encrypted email and encrypted symmetric key from server
2. Client uses private key from vault to decrypt the symmetric key
3. Client uses decrypted symmetric key to decrypt the email contents
4. All decryption occurs locally on the client device
> Note: The use of a symmetric key for email content encryption and asymmetric encryption for the symmetric key (hybrid encryption) is implemented due to RSA's limitations on encryption string length and for better performance.
## Security Benefits
- Zero-knowledge architecture ensures user data privacy
- Master password never leaves the client device
- All sensitive operations (key derivation, encryption/decryption) happen locally
- Server stores only encrypted data
- Multi-layer encryption for emails provides secure communication
- Optional 2FA adds an additional security layer
- Use of established cryptographic standards (Argon2id, AES-256-GCM, RSA/OAEP)
This security architecture ensures that even if the server is compromised, user data remains secure as all sensitive operations and keys remain strictly on the client side.

33
entrypoint.sh Normal file
View 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;"

78
init.sh
View File

@@ -1,78 +0,0 @@
#!/bin/sh
# Define colors for CLI output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Define the path to the .env and .env.example files
ENV_FILE=".env"
ENV_EXAMPLE_FILE=".env.example"
# Function to generate a new 32-character JWT key
generate_jwt_key() {
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 | head -c 32
}
# Function to create .env file from .env.example if it doesn't exist
create_env_file() {
if [ ! -f "$ENV_FILE" ]; then
if [ -f "$ENV_EXAMPLE_FILE" ]; then
cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
printf "${GREEN}> .env file created from .env.example.${NC}\n"
else
touch "$ENV_FILE"
printf "${YELLOW}> .env file created as empty because .env.example was not found.${NC}\n"
fi
else
printf "${CYAN}> .env file already exists.${NC}\n"
fi
}
# Function to check and populate the .env file with JWT_KEY
populate_jwt_key() {
if ! grep -q "^JWT_KEY=" "$ENV_FILE" || [ -z "$(grep "^JWT_KEY=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
printf "${YELLOW}JWT_KEY not found or empty in $ENV_FILE. Generating a new JWT key...${NC}\n"
JWT_KEY=$(generate_jwt_key)
if grep -q "^JWT_KEY=" "$ENV_FILE"; then
awk -v key="$JWT_KEY" '/^JWT_KEY=/ {$0="JWT_KEY="key} 1' "$ENV_FILE" > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE"
else
printf "JWT_KEY=${JWT_KEY}" >> "$ENV_FILE\n"
fi
printf "${GREEN}> JWT_KEY has been added to $ENV_FILE.${NC}\n"
else
printf "${CYAN}> JWT_KEY already exists and has a value in $ENV_FILE.${NC}\n"
fi
}
# Function to print the CLI logo
print_logo() {
printf "${MAGENTA}\n"
printf "=========================================================\n"
printf " _ _ __ __ _ _ \n"
printf " /\ | (_) \ \ / / | | | \n"
printf " / \ | |_ __ _ __\ \ / /_ _ _ _| | |_\n"
printf " / /\ \ | | |/ _ / __\ \/ / _ | | | | | __|\n"
printf " / ____ \| | | (_| \__ \\ / (_| | |_| | | |_ \n"
printf " /_/ \_\_|_|\__,_|___/ \/ \__,_|\__,_|_|\__|\n"
printf "\n"
printf "=========================================================\n"
printf "${NC}\n"
}
# Run the functions and print status
print_logo
printf "${BLUE}Initializing AliasVault...${NC}\n"
create_env_file
populate_jwt_key
printf "${BLUE}Initialization complete.${NC}\n"
printf "\n"
printf "To build the images and start the containers, run the following command:\n"
printf "\n"
printf "${CYAN}$ docker compose up -d --build --force-recreate${NC}\n"
printf "\n"
printf "\n"

808
install.sh Executable file
View File

@@ -0,0 +1,808 @@
#!/bin/bash
# Repository information used for downloading files and images from GitHub
REPO_OWNER="lanedirt"
REPO_NAME="AliasVault"
REPO_BRANCH="main"
GITHUB_RAW_URL="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REPO_BRANCH}"
GITHUB_CONTAINER_REGISTRY="ghcr.io/$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')/$(echo "$REPO_NAME" | tr '[:upper:]' '[:lower:]')"
# Required files and directories
REQUIRED_DIRS=(
"certificates/ssl"
"certificates/app"
"certificates/letsencrypt"
"certificates/letsencrypt/www"
"database"
"logs"
"logs/msbuild"
)
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
# File paths
ENV_FILE=".env"
ENV_EXAMPLE_FILE=".env.example"
# Function to show usage
show_usage() {
print_logo
printf "Usage: $0 [COMMAND] [OPTIONS]\n"
printf "\n"
printf "Commands:\n"
printf " install Install AliasVault by pulling pre-built images from GitHub Container Registry (default)\n"
printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n"
printf " reset-password Reset admin password\n"
printf " uninstall Uninstall AliasVault\n"
printf " configure-ssl Configure SSL certificates (Let's Encrypt or self-signed)\n"
printf "\n"
printf "Options:\n"
printf " --verbose Show detailed output\n"
printf " -y, --yes Automatic yes to prompts (for uninstall)\n"
printf " --help Show this help message\n"
}
# Function to parse command line arguments
parse_args() {
COMMAND="" # Remove default command
VERBOSE=false
FORCE_YES=false
# Show usage if no arguments provided
if [ $# -eq 0 ]; then
show_usage
exit 0
fi
while [[ $# -gt 0 ]]; do
case $1 in
install|i)
COMMAND="install"
shift
;;
build|b)
COMMAND="build"
shift
;;
uninstall|u)
COMMAND="uninstall"
shift
;;
reset-password|reset-admin-password|rp)
COMMAND="reset-password"
shift
;;
configure-ssl|ssl)
COMMAND="configure-ssl"
shift
;;
--verbose)
VERBOSE=true
shift
;;
-y|--yes)
FORCE_YES=true
shift
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
}
# Main function
main() {
parse_args "$@"
# Check if command is empty (should not happen with updated parse_args)
if [ -z "$COMMAND" ]; then
show_usage
exit 1
fi
print_logo
case $COMMAND in
"install")
handle_install
;;
"build")
handle_build
;;
"uninstall")
handle_uninstall
;;
"reset-password")
generate_admin_password
if [ $? -eq 0 ]; then
recreate_docker_containers
print_password_reset_message
fi
;;
"configure-ssl")
handle_ssl_configuration
;;
esac
}
# Function to create required directories
create_directories() {
printf "${CYAN}> Checking workspace...${NC}\n"
local dirs_needed=false
for dir in "${REQUIRED_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
if [ "$dirs_needed" = false ]; then
printf " ${CYAN}> Creating required directories...${NC}\n"
dirs_needed=true
fi
mkdir -p "$dir"
chmod -R 755 "$dir"
if [ $? -ne 0 ]; then
printf " ${RED}> Failed to create directory: $dir${NC}\n"
exit 1
fi
fi
done
if [ "$dirs_needed" = true ]; then
printf " ${GREEN}> Directories created successfully.${NC}\n"
else
printf " ${GREEN}> All required directories already exist.${NC}\n"
fi
}
# Function to initialize workspace
initialize_workspace() {
create_directories
handle_docker_compose
}
# Function to handle docker-compose.yml
handle_docker_compose() {
printf "${CYAN}> Checking docker-compose.yml...${NC}\n"
if [ -f "docker-compose.yml" ]; then
printf " ${GREEN}> docker-compose.yml already exists.${NC}\n"
return 0
fi
printf " ${CYAN}> Downloading docker-compose.yml...${NC}"
if curl -sSf "${GITHUB_RAW_URL}/docker-compose.yml" -o "docker-compose.yml" > /dev/null 2>&1; then
printf "\n ${GREEN}> docker-compose.yml downloaded successfully.${NC}\n"
return 0
else
printf "\n ${YELLOW}> Failed to download docker-compose.yml, please check your internet connection and try again. Alternatively, you can download it manually from https://github.com/${REPO_OWNER}/${REPO_NAME}/blob/main/docker-compose.yml and place it in the root directory of AliasVault.${NC}\n"
exit 1
fi
}
# Function to print the logo
print_logo() {
printf "${MAGENTA}"
printf " _ _ _ __ __ _ _ \n"
printf " / \ | (_) __ _ ___ \ \ / /_ _ _ _| | |_\n"
printf " / _ \ | | |/ _\` / __| \ \/\/ / _\` | | | | | __|\n"
printf " / ___ \| | | (_| \__ \ \ / (_| | |_| | | |_ \n"
printf "/_/ \_\_|_|\__,_|___/ \/ \__,_|\__,_|_|\__|\n"
printf "${NC}\n"
}
# Function to create .env file
create_env_file() {
printf "${CYAN}> Checking .env file...${NC}\n"
if [ ! -f "$ENV_FILE" ]; then
if [ -f "$ENV_EXAMPLE_FILE" ]; then
cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
printf " ${GREEN}> New.env file created from .env.example.${NC}\n"
else
touch "$ENV_FILE"
printf " ${YELLOW}> New blank .env file created.${NC}\n"
fi
else
printf " ${GREEN}> .env file already exists.${NC}\n"
fi
}
# Environment setup functions
populate_hostname() {
printf "${CYAN}> Checking HOSTNAME...${NC}\n"
if ! grep -q "^HOSTNAME=" "$ENV_FILE" || [ -z "$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
DEFAULT_HOSTNAME="localhost"
read -p "Enter the hostname where AliasVault will be hosted (press Enter for default: $DEFAULT_HOSTNAME): " USER_HOSTNAME
HOSTNAME=${USER_HOSTNAME:-$DEFAULT_HOSTNAME}
update_env_var "HOSTNAME" "$HOSTNAME"
else
HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
printf " ${GREEN}> HOSTNAME already exists.${NC}\n"
fi
}
populate_jwt_key() {
printf "${CYAN}> Checking JWT_KEY...${NC}\n"
if ! grep -q "^JWT_KEY=" "$ENV_FILE" || [ -z "$(grep "^JWT_KEY=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
JWT_KEY=$(openssl rand -base64 32)
update_env_var "JWT_KEY" "$JWT_KEY"
else
printf " ${GREEN}> JWT_KEY already exists.${NC}\n"
fi
}
populate_data_protection_cert_pass() {
printf "${CYAN}> Checking DATA_PROTECTION_CERT_PASS...${NC}\n"
if ! grep -q "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" || [ -z "$(grep "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
CERT_PASS=$(openssl rand -base64 32)
update_env_var "DATA_PROTECTION_CERT_PASS" "$CERT_PASS"
else
printf " ${GREEN}> DATA_PROTECTION_CERT_PASS already exists.${NC}\n"
fi
}
set_private_email_domains() {
printf "${CYAN}> Checking PRIVATE_EMAIL_DOMAINS...${NC}\n"
if ! grep -q "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" || [ -z "$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
printf "Please enter the domains that should be allowed to receive email, separated by commas (press Enter to disable email support): "
read -r private_email_domains
private_email_domains=${private_email_domains:-"DISABLED.TLD"}
update_env_var "PRIVATE_EMAIL_DOMAINS" "$private_email_domains"
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
printf " ${RED}SMTP is disabled.${NC}\n"
fi
else
private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)
if [ "$private_email_domains" = "DISABLED.TLD" ]; then
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists.${NC} ${RED}Private email domains are disabled.${NC}\n"
else
printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists.${NC}\n"
fi
fi
}
set_smtp_tls_enabled() {
printf "${CYAN}> Checking SMTP_TLS_ENABLED...${NC}\n"
if ! grep -q "^SMTP_TLS_ENABLED=" "$ENV_FILE"; then
update_env_var "SMTP_TLS_ENABLED" "false"
else
printf " ${GREEN}> SMTP_TLS_ENABLED already exists.${NC}\n"
fi
}
set_support_email() {
printf "${CYAN}> Checking SUPPORT_EMAIL...${NC}\n"
if ! grep -q "^SUPPORT_EMAIL=" "$ENV_FILE"; then
read -p "Enter support email address (optional, press Enter to skip): " SUPPORT_EMAIL
update_env_var "SUPPORT_EMAIL" "$SUPPORT_EMAIL"
else
printf " ${GREEN}> SUPPORT_EMAIL already exists.${NC}\n"
fi
}
# Function to generate admin password
generate_admin_password() {
printf "${CYAN}> Generating admin password...${NC}\n"
PASSWORD=$(openssl rand -base64 12)
if ! docker pull ${GITHUB_CONTAINER_REGISTRY}-installcli:latest > /dev/null 2>&1; then
printf "${YELLOW}> Pre-built image not found, building locally...${NC}"
if [ "$VERBOSE" = true ]; then
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile .
else
(
docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile . > install_build_output.log 2>&1 &
BUILD_PID=$!
while kill -0 $BUILD_PID 2>/dev/null; do
printf "."
sleep 1
done
printf "\n"
wait $BUILD_PID
BUILD_EXIT_CODE=$?
if [ $BUILD_EXIT_CODE -ne 0 ]; then
printf "\n${RED}> Error building Docker image. Check install_build_output.log for details.${NC}\n"
exit $BUILD_EXIT_CODE
fi
)
fi
HASH=$(docker run --rm installcli "$PASSWORD")
if [ -z "$HASH" ]; then
printf "${RED}> Error: Failed to generate password hash${NC}\n"
exit 1
fi
else
HASH=$(docker run --rm ${GITHUB_CONTAINER_REGISTRY}-installcli:latest "$PASSWORD")
if [ -z "$HASH" ]; then
printf "${RED}> Error: Failed to generate password hash${NC}\n"
exit 1
fi
fi
if [ -n "$HASH" ]; then
update_env_var "ADMIN_PASSWORD_HASH" "$HASH"
update_env_var "ADMIN_PASSWORD_GENERATED" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
printf " ==> New admin password: $PASSWORD\n"
fi
}
# Helper function to update environment variables
update_env_var() {
local key=$1
local value=$2
if [ -f "$ENV_FILE" ]; then
sed -i.bak "/^${key}=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
fi
echo "$key=$value" >> "$ENV_FILE"
printf " ${GREEN}> $key has been set in $ENV_FILE.${NC}\n"
}
# Function to print success message
print_success_message() {
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
printf "\n"
printf "${GREEN}AliasVault is successfully installed!${NC}\n"
printf "\n"
printf "${CYAN}To configure the server, login to the admin panel:${NC}\n"
printf "\n"
if [ -n "$PASSWORD" ]; then
printf "Admin Panel: https://${HOSTNAME}/admin\n"
printf "Username: admin\n"
printf "Password: $PASSWORD\n"
printf "\n"
printf "${YELLOW}(!) Caution: Make sure to backup the above credentials in a safe place, they won't be shown again!${NC}\n"
else
printf "Admin Panel: https://${HOSTNAME}/admin\n"
printf "Username: admin\n"
printf "Password: (Previously set. Use ./install.sh reset-password to generate new one.)\n"
fi
printf "\n"
printf "${CYAN}===========================${NC}\n"
printf "\n"
printf "${CYAN}In order to start using AliasVault, log into the client website:${NC}\n"
printf "\n"
printf "Client Website: https://${HOSTNAME}/\n"
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
}
# Function to recreate (restart) Docker containers
recreate_docker_containers() {
printf "${CYAN}> Recreating Docker containers...${NC}\n"
if [ "$VERBOSE" = true ]; then
docker compose up -d --force-recreate
else
docker compose up -d --force-recreate > /dev/null 2>&1
fi
printf "${GREEN}> Docker containers recreated.${NC}\n"
}
# Function to print password reset success message
print_password_reset_message() {
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
printf "\n"
printf "${GREEN}The admin password is successfully reset, see the output above. You can now login to the admin panel using this new password.${NC}\n"
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
printf "\n"
}
# Function to get docker compose command with appropriate config files
get_docker_compose_command() {
local base_command="docker compose -f docker-compose.yml"
# Check if using build configuration
if [ "$1" = "build" ]; then
base_command="$base_command -f docker-compose.build.yml"
fi
# Check if Let's Encrypt is enabled
if grep -q "^LETSENCRYPT_ENABLED=true" "$ENV_FILE" 2>/dev/null; then
base_command="$base_command -f docker-compose.letsencrypt.yml"
fi
echo "$base_command"
}
# Function to handle installation
handle_install() {
printf "${YELLOW}+++ Installing AliasVault +++${NC}\n"
printf "\n"
# Initialize workspace which makes sure all required directories and files exist
initialize_workspace
# Initialize environment
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
# Only generate admin password if not already set
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
fi
# Pull images from GitHub Container Registry
printf "\n${YELLOW}+++ Pulling Docker images +++${NC}\n"
printf "\n"
images=(
"${GITHUB_CONTAINER_REGISTRY}-reverse-proxy:latest"
"${GITHUB_CONTAINER_REGISTRY}-api:latest"
"${GITHUB_CONTAINER_REGISTRY}-client:latest"
"${GITHUB_CONTAINER_REGISTRY}-admin:latest"
"${GITHUB_CONTAINER_REGISTRY}-smtp:latest"
)
for image in "${images[@]}"; do
printf "${CYAN}> Pulling $image...${NC}\n"
if [ "$VERBOSE" = true ]; then
docker pull $image || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
else
docker pull $image > /dev/null 2>&1 || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; }
fi
done
# Start containers
printf "\n${YELLOW}+++ Starting services +++${NC}\n"
printf "\n"
if [ "$VERBOSE" = true ]; then
$(get_docker_compose_command) up -d || { printf "${RED}> Failed to start Docker containers${NC}\n"; exit 1; }
else
$(get_docker_compose_command) up -d > /dev/null 2>&1 || { printf "${RED}> Failed to start Docker containers${NC}\n"; exit 1; }
fi
# Only show success message if we made it here without errors
print_success_message
}
# Function to handle build
handle_build() {
printf "${YELLOW}+++ Building AliasVault from source +++${NC}\n"
printf "\n"
# Check for required build files
if [ ! -f "docker-compose.build.yml" ] || [ ! -d "src" ]; then
printf "${RED}Error: Required files for building from source are missing.${NC}\n"
printf "\n"
printf "To build AliasVault from source, you need:\n"
printf "1. docker-compose.build.yml file\n"
printf "2. src/ directory with the complete source code\n"
printf "\n"
printf "Please clone the complete repository using:\n"
printf "git clone https://github.com/${REPO_OWNER}/${REPO_NAME}.git\n"
printf "\n"
printf "Alternatively, you can use '/install' to pull pre-built images.\n"
exit 1
fi
# Initialize environment with proper error handling
create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }
populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }
populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; }
populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; }
set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; }
set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; }
set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; }
# Only generate admin password if not already set
if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
fi
printf "\n${YELLOW}+++ Building and starting services +++${NC}\n"
printf "\n"
printf "${CYAN}> Building Docker Compose stack...${NC}"
if [ "$VERBOSE" = true ]; then
$(get_docker_compose_command "build") build || {
printf "\n${RED}> Failed to build Docker Compose stack${NC}\n"
exit 1
}
else
(
$(get_docker_compose_command "build") build > install_compose_build_output.log 2>&1 &
BUILD_PID=$!
while kill -0 $BUILD_PID 2>/dev/null; do
printf "."
sleep 1
done
wait $BUILD_PID
BUILD_EXIT_CODE=$?
if [ $BUILD_EXIT_CODE -ne 0 ]; then
printf "\n${RED}> Failed to build Docker Compose stack. Check install_compose_build_output.log for details.${NC}\n"
exit 1
fi
)
fi
printf "\n${GREEN}> Docker Compose stack built successfully.${NC}\n"
printf "${CYAN}> Starting Docker Compose stack...${NC}\n"
if [ "$VERBOSE" = true ]; then
$(get_docker_compose_command "build") up -d || {
printf "${RED}> Failed to start Docker Compose stack${NC}\n"
exit 1
}
else
$(get_docker_compose_command "build") up -d > /dev/null 2>&1 || {
printf "${RED}> Failed to start Docker Compose stack${NC}\n"
exit 1
}
fi
printf "${GREEN}> Docker Compose stack started successfully.${NC}\n"
# Only show success message if we made it here without errors
print_success_message
}
# Function to handle uninstall
handle_uninstall() {
printf "${YELLOW}+++ Uninstalling AliasVault +++${NC}\n"
printf "\n"
# Check if -y flag was passed
if [ "$FORCE_YES" != "true" ]; then
# Ask for confirmation before proceeding
read -p "Are you sure you want to uninstall AliasVault? This will remove all containers and images. [y/N]: " REPLY
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
printf "${YELLOW}> Uninstall cancelled.${NC}\n"
exit 0
fi
fi
printf "${CYAN}> Stopping and removing Docker containers...${NC}\n"
if [ "$VERBOSE" = true ]; then
docker compose -f docker-compose.yml down -v || {
printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
exit 1
}
else
docker compose -f docker-compose.yml down -v > /dev/null 2>&1 || {
printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
exit 1
}
fi
printf "${GREEN}> Docker containers stopped and removed.${NC}\n"
printf "${CYAN}> Removing Docker images...${NC}\n"
if [ "$VERBOSE" = true ]; then
docker compose -f docker-compose.yml down --rmi all || {
printf "${RED}> Failed to remove Docker images${NC}\n"
exit 1
}
else
docker compose -f docker-compose.yml down --rmi all > /dev/null 2>&1 || {
printf "${RED}> Failed to remove Docker images${NC}\n"
exit 1
}
fi
printf "${GREEN}> Docker images removed.${NC}\n"
printf "${CYAN}> Pruning Docker system...${NC}\n"
if [ "$VERBOSE" = true ]; then
docker system prune -af || {
printf "${RED}> Failed to prune Docker system${NC}\n"
exit 1
}
else
docker system prune -af > /dev/null 2>&1 || {
printf "${RED}> Failed to prune Docker system${NC}\n"
exit 1
}
fi
printf "${GREEN}> Docker system pruned.${NC}\n"
# Only show success message if we made it here without errors
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
printf "\n"
printf "${GREEN}AliasVault has been successfully uninstalled!${NC}\n"
printf "\n"
printf "All Docker containers and images related to AliasVault have been removed.\n"
printf "The current directory, including logs and .env files, has been left intact.\n"
printf "\n"
printf "If you wish to remove the remaining files, you can do so manually.\n"
printf "\n"
printf "Thank you for using AliasVault!\n"
printf "\n"
printf "${MAGENTA}=========================================================${NC}\n"
}
# Function to handle SSL configuration
handle_ssl_configuration() {
printf "${YELLOW}+++ SSL Certificate Configuration +++${NC}\n"
printf "\n"
# Check if AliasVault is installed
if [ ! -f "docker-compose.yml" ]; then
printf "${RED}Error: AliasVault must be installed first.${NC}\n"
exit 1
fi
# Get the current hostname and SSL config from .env
CURRENT_HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
LETSENCRYPT_ENABLED=$(grep "^LETSENCRYPT_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)
printf "${CYAN}About SSL Certificates:${NC}\n"
printf "A default installation of AliasVault comes with a self-signed SSL certificate.\n"
printf "While self-signed certificates provide encryption, they will show security warnings in browsers.\n"
printf "\n"
printf "AliasVault also supports generating valid SSL certificates via Let's Encrypt.\n"
printf "Let's Encrypt certificates are trusted by browsers and will not show security warnings.\n"
printf "However, Let's Encrypt requires that:\n"
printf " - AliasVault is reachable from the internet via port 80/443\n"
printf " - You have configured a valid domain name (not localhost)\n"
printf "\n"
printf "Let's Encrypt certificates will be automatically renewed before expiry.\n"
printf "\n"
printf "${CYAN}Current Configuration:${NC}\n"
if [ "$LETSENCRYPT_ENABLED" = "true" ]; then
printf "Currently using: ${GREEN}Let's Encrypt certificates${NC}\n"
else
printf "Currently using: ${YELLOW}Self-signed certificates${NC}\n"
fi
printf "Current hostname: ${CYAN}${CURRENT_HOSTNAME}${NC}\n"
printf "\n"
printf "SSL Options:\n"
printf "1) Activate and/or request new Let's Encrypt certificate (recommended for production)\n"
printf "2) Activate and/or generate new self-signed certificate\n"
printf "3) Cancel\n"
printf "\n"
read -p "Select an option [1-3]: " ssl_option
case $ssl_option in
1)
configure_letsencrypt
;;
2)
generate_self_signed_cert
;;
3)
printf "${YELLOW}SSL configuration cancelled.${NC}\n"
exit 0
;;
*)
printf "${RED}Invalid option selected.${NC}\n"
exit 1
;;
esac
}
# Function to configure Let's Encrypt
configure_letsencrypt() {
printf "${CYAN}> Configuring Let's Encrypt SSL certificate...${NC}\n"
# Check if hostname is localhost
if [ "$CURRENT_HOSTNAME" = "localhost" ]; then
printf "${RED}Error: Let's Encrypt certificates cannot be issued for 'localhost'.${NC}\n"
printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
exit 1
fi
# Check if hostname is a valid domain
if ! [[ "$CURRENT_HOSTNAME" =~ \.[a-zA-Z]{2,}$ ]]; then
printf "${RED}Error: Invalid hostname '${CURRENT_HOSTNAME}'.${NC}\n"
printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
exit 1
fi
# Verify DNS is properly configured
printf "\n${YELLOW}Important: Before proceeding, ensure that:${NC}\n"
printf "1. AliasVault is currently running and accessible at ${CYAN}https://${CURRENT_HOSTNAME}${NC}\n"
printf "2. Your domain (${CYAN}${CURRENT_HOSTNAME}${NC}) is externally resolvable to this server's IP address\n"
printf "3. Ports 80 and 443 are open and accessible from the internet\n"
printf "\n"
read -p "Have you completed these steps? [y/N]: " REPLY
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
printf "${YELLOW}> Let's Encrypt configuration cancelled.${NC}\n"
exit 0
fi
# Get contact email for Let's Encrypt
SUPPORT_EMAIL=$(grep "^SUPPORT_EMAIL=" "$ENV_FILE" | cut -d '=' -f2)
LETSENCRYPT_EMAIL=""
while true; do
printf "\nPlease enter a valid email address that will be used for Let's Encrypt certificate notifications:\n"
read -p "Email: " LETSENCRYPT_EMAIL
if [[ "$LETSENCRYPT_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
printf "Confirm using ${CYAN}${LETSENCRYPT_EMAIL}${NC} for Let's Encrypt notifications? [y/N] "
read REPLY
if [[ $REPLY =~ ^[Yy]$ ]]; then
break
fi
else
printf "${RED}Invalid email format. Please try again.${NC}\n"
fi
done
# Create certbot directories
printf "${CYAN}> Creating Let's Encrypt directories...${NC}\n"
mkdir -p ./certificates/letsencrypt/www
# Request certificate using a temporary certbot container
printf "${CYAN}> Requesting Let's Encrypt certificate...${NC}\n"
docker run --rm \
--network aliasvault_default \
-v ./certificates/letsencrypt:/etc/letsencrypt:rw \
-v ./certificates/letsencrypt/www:/var/www/certbot:rw \
certbot/certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
--email "$LETSENCRYPT_EMAIL" \
--agree-tos \
--no-eff-email \
--non-interactive \
--domains ${CURRENT_HOSTNAME} \
--force-renewal
if [ $? -ne 0 ]; then
printf "${RED}Failed to obtain Let's Encrypt certificate.${NC}\n"
exit 1
fi
# Fix permissions on Let's Encrypt directories and files
sudo chmod -R 755 ./certificates/letsencrypt
# Ensure private keys remain secure
sudo find ./certificates/letsencrypt -type f -name "privkey*.pem" -exec chmod 600 {} \;
sudo find ./certificates/letsencrypt -type f -name "fullchain*.pem" -exec chmod 644 {} \;
# Update .env to indicate Let's Encrypt is enabled
update_env_var "LETSENCRYPT_ENABLED" "true"
# Restart only the reverse proxy with new configuration so it loads the new certificate
printf "${CYAN}> Restarting reverse proxy with Let's Encrypt configuration...${NC}\n"
$(get_docker_compose_command) up -d reverse-proxy
printf "${GREEN}> Let's Encrypt SSL certificate has been configured successfully!${NC}\n"
}
# Function to generate self-signed certificate
generate_self_signed_cert() {
printf "${CYAN}> Generating new self-signed certificate...${NC}\n"
# Disable Let's Encrypt
update_env_var "LETSENCRYPT_ENABLED" "false"
# Stop existing containers
printf "${CYAN}> Stopping existing containers...${NC}\n"
docker compose down
# Remove existing certificates
rm -f ./certificates/ssl/cert.pem ./certificates/ssl/key.pem
# Remove Let's Encrypt directories
rm -rf ./certificates/letsencrypt
# Start containers (which will generate new self-signed certs)
printf "${CYAN}> Restarting services...${NC}\n"
docker compose up -d
printf "${GREEN}> New self-signed certificate has been generated successfully!${NC}\n"
}
main "$@"

100
nginx.conf Normal file
View 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;
}
}
}

View File

@@ -1,144 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="AliasDbContext.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 AliasDb;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
/// <summary>
/// The AliasDbContext class.
/// </summary>
public class AliasDbContext : IdentityDbContext
{
/// <summary>
/// Initializes a new instance of the <see cref="AliasDbContext"/> class.
/// </summary>
public AliasDbContext()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AliasDbContext"/> class.
/// </summary>
/// <param name="options">DbContextOptions.</param>
public AliasDbContext(DbContextOptions<AliasDbContext> options)
: base(options)
{
}
/// <summary>
/// Gets or sets the Identities DbSet.
/// </summary>
public DbSet<Identity> Identities { get; set; }
/// <summary>
/// Gets or sets the Logins DbSet.
/// </summary>
public DbSet<Login> Logins { get; set; }
/// <summary>
/// Gets or sets the Passwords DbSet.
/// </summary>
public DbSet<Password> Passwords { get; set; }
/// <summary>
/// Gets or sets the Services DbSet.
/// </summary>
public DbSet<Service> Services { get; set; }
/// <summary>
/// Gets or sets the AspNetUserRefreshTokens DbSet.
/// </summary>
public DbSet<AspNetUserRefreshToken> AspNetUserRefreshTokens { get; set; }
/// <summary>
/// The OnModelCreating method.
/// </summary>
/// <param name="builder">ModelBuilder instance.</param>
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entity in builder.Model.GetEntityTypes())
{
foreach (var property in entity.GetProperties())
{
// NOTE: This is a workaround for SQLite. Add conditional check if SQLite is used.
// NOTE: SQL server doesn't need this override.
// SQLite does not support varchar(max) so we use TEXT.
if (property.ClrType == typeof(string) && property.GetMaxLength() == null)
{
property.SetColumnType("TEXT");
}
}
}
// Configure Identity - Login relationship
builder.Entity<Login>()
.HasOne(l => l.Identity)
.WithMany()
.HasForeignKey(l => l.IdentityId)
.OnDelete(DeleteBehavior.Cascade);
// Configure the Login - UserId entity
builder.Entity<Login>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UserId)
.IsRequired();
// Configure Login - Service relationship
builder.Entity<Login>()
.HasOne(l => l.Service)
.WithMany()
.HasForeignKey(l => l.ServiceId)
.OnDelete(DeleteBehavior.Cascade);
// Configure Login - Password relationship
builder.Entity<Login>()
.HasMany(l => l.Passwords)
.WithOne(p => p.Login)
.HasForeignKey(p => p.LoginId)
.OnDelete(DeleteBehavior.Cascade);
// Configure Identity - DefaultPassword relationship
builder.Entity<Identity>()
.HasOne(i => i.DefaultPassword)
.WithMany()
.HasForeignKey(i => i.DefaultPasswordId)
.OnDelete(DeleteBehavior.SetNull);
// Configure the User - AspNetUserRefreshToken entity
builder.Entity<AspNetUserRefreshToken>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UserId)
.IsRequired();
}
/// <summary>
/// Sets up the connection string if it is not already configured.
/// </summary>
/// <param name="optionsBuilder">DbContextOptionsBuilder instance.</param>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// If the options are not already configured, use the appsettings.json file.
if (!optionsBuilder.IsConfigured)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
optionsBuilder
.UseSqlite(configuration.GetConnectionString("AliasDbContext"))
.UseLazyLoadingProxies();
}
}
}

View File

@@ -1,135 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="Identity.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 AliasDb;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
/// <summary>
/// The identity entity.
/// </summary>
public class Identity
{
/// <summary>
/// Gets or sets the identity primary key.
/// </summary>
[Key]
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the gender.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? Gender { get; set; }
/// <summary>
/// Gets or sets the first name.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? FirstName { get; set; } = null!;
/// <summary>
/// Gets or sets the last name.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? LastName { get; set; } = null!;
/// <summary>
/// Gets or sets the nickname.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? NickName { get; set; }
/// <summary>
/// Gets or sets the birth date.
/// </summary>
public DateTime BirthDate { get; set; }
/// <summary>
/// Gets or sets the address street.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? AddressStreet { get; set; }
/// <summary>
/// Gets or sets the address city.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? AddressCity { get; set; }
/// <summary>
/// Gets or sets the address state.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? AddressState { get; set; }
/// <summary>
/// Gets or sets the address zip code.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? AddressZipCode { get; set; }
/// <summary>
/// Gets or sets the address country.
/// </summary>
[StringLength(255)]
[Column(TypeName = "VARCHAR")]
public string? AddressCountry { get; set; }
/// <summary>
/// Gets or sets the hobbies in CSV format, can contain multiple values separated by ";".
/// </summary>
[StringLength(255)]
public string? Hobbies { get; set; }
/// <summary>
/// Gets or sets the generated email prefix.
/// </summary>
[StringLength(255)]
public string? EmailPrefix { get; set; }
/// <summary>
/// Gets or sets the random generated mobile phone number.
/// </summary>
[StringLength(255)]
public string? PhoneMobile { get; set; }
/// <summary>
/// Gets or sets the generated IBAN bank account number.
/// </summary>
[StringLength(255)]
public string? BankAccountIBAN { get; set; }
/// <summary>
/// Gets or sets the created timestamp.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets the updated timestamp.
/// </summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// Gets or sets the login foreign key.
/// </summary>
public Guid? DefaultPasswordId { get; set; }
/// <summary>
/// Gets or sets the login navigation property.
/// </summary>
[ForeignKey("DefaultPasswordId")]
public virtual Password? DefaultPassword { get; set; }
}

View File

@@ -1,39 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="FigIdentityGenerator.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 AliasGenerators.Identity.Implementations;
using System.Text.Json;
/// <summary>
/// Identity generator which generates random identities using the identiteitgenerator.nl semi-public API.
/// </summary>
public class FigIdentityGenerator : IIdentityGenerator
{
private static readonly HttpClient HttpClient = new();
private static readonly string Url = "https://api.identiteitgenerator.nl/generate/identity";
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNameCaseInsensitive = true,
};
/// <inheritdoc/>
public async Task<Identity.Models.Identity> GenerateRandomIdentityAsync()
{
var response = await HttpClient.GetAsync(Url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var identity = JsonSerializer.Deserialize<Identity.Models.Identity>(json, JsonSerializerOptions);
if (identity is null)
{
throw new InvalidOperationException("Failed to deserialize the identity from FIG WebApi.");
}
return identity;
}
}

View File

@@ -1,27 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="StaticIdentityGenerator.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 AliasGenerators.Identity.Implementations;
using AliasGenerators.Identity;
/// <summary>
/// Static identity generator which implements IIdentityGenerator but always returns
/// the same static identity for testing purposes.
/// </summary>
public class StaticIdentityGenerator : IIdentityGenerator
{
/// <inheritdoc/>
public async Task<Identity.Models.Identity> GenerateRandomIdentityAsync()
{
await Task.Yield(); // Add an await statement to make the method truly asynchronous.
return new Identity.Models.Identity
{
FirstName = "John",
LastName = "Doe",
};
}
}

View File

@@ -1,38 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="Address.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 AliasGenerators.Identity.Models;
/// <summary>
/// Address model.
/// </summary>
public class Address
{
/// <summary>
/// Gets or sets the street.
/// </summary>
public string Street { get; set; } = null!;
/// <summary>
/// Gets or sets the city.
/// </summary>
public string City { get; set; } = null!;
/// <summary>
/// Gets or sets the state.
/// </summary>
public string State { get; set; } = null!;
/// <summary>
/// Gets or sets the zip code.
/// </summary>
public string ZipCode { get; set; } = null!;
/// <summary>
/// Gets or sets the country.
/// </summary>
public string Country { get; set; } = null!;
}

View File

@@ -1,88 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="Identity.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 AliasGenerators.Identity.Models;
/// <summary>
/// Identity model.
/// </summary>
public class Identity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// Gets or sets the gender.
/// </summary>
public int Gender { get; set; }
/// <summary>
/// Gets or sets the first name.
/// </summary>
public string FirstName { get; set; } = null!;
/// <summary>
/// Gets or sets the last name.
/// </summary>
public string LastName { get; set; } = null!;
/// <summary>
/// Gets or sets the nickname. This is also used as the username.
/// </summary>
public string NickName { get; set; } = null!;
/// <summary>
/// Gets or sets the birth date.
/// </summary>
public DateTime BirthDate { get; set; }
/// <summary>
/// Gets or sets the address.
/// </summary>
public Address Address { get; set; } = null!;
/// <summary>
/// Gets or sets the job.
/// </summary>
public Job Job { get; set; } = null!;
/// <summary>
/// Gets or sets the hobbies.
/// </summary>
public List<string> Hobbies { get; set; } = null!;
/// <summary>
/// Gets or sets the email address prefix.
/// </summary>
public string EmailPrefix { get; set; } = null!;
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; } = null!;
/// <summary>
/// Gets or sets the phone mobile.
/// </summary>
public string PhoneMobile { get; set; } = null!;
/// <summary>
/// Gets or sets the bank account IBAN.
/// </summary>
public string BankAccountIBAN { get; set; } = null!;
/// <summary>
/// Gets or sets the profile photo in base64 format.
/// </summary>
public string ProfilePhotoBase64 { get; set; } = null!;
/// <summary>
/// Gets or sets the profile photo prompt.
/// </summary>
public string ProfilePhotoPrompt { get; set; } = null!;
}

View File

@@ -1,38 +0,0 @@
//-----------------------------------------------------------------------
// <copyright file="Job.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 AliasGenerators.Identity.Models;
/// <summary>
/// Job model.
/// </summary>
public class Job
{
/// <summary>
/// Gets or sets the title.
/// </summary>
public string Title { get; set; } = null!;
/// <summary>
/// Gets or sets the company.
/// </summary>
public string Company { get; set; } = null!;
/// <summary>
/// Gets or sets the salary.
/// </summary>
public string Salary { get; set; } = null!;
/// <summary>
/// Gets or sets the calculated salary.
/// </summary>
public decimal SalaryCalculated { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; } = null!;
}

View File

@@ -0,0 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<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\net9.0\AliasVault.Admin.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\net9.0\AliasVault.Admin.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<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>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Content Include="..\..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Databases\AliasServerDb\AliasServerDb.csproj" />
<ProjectReference Include="..\Shared\AliasVault.RazorComponents\AliasVault.RazorComponents.csproj" />
<ProjectReference Include="..\Shared\AliasVault.Shared.Core\AliasVault.Shared.Core.csproj" />
<ProjectReference Include="..\Utilities\AliasVault.Auth\AliasVault.Auth.csproj" />
<ProjectReference Include="..\Utilities\AliasVault.Logging\AliasVault.Logging.csproj" />
<ProjectReference Include="..\Utilities\Cryptography\AliasVault.Cryptography.Server\AliasVault.Cryptography.Server.csproj" />
</ItemGroup>
</Project>

View File

@@ -12,30 +12,36 @@
/// <summary>
/// Gets or sets the ID of the input field.
/// </summary>
[Parameter] public string Id { get; set; } = null!;
[Parameter]
public required string Id { get; set; }
/// <summary>
/// Gets or sets the value of the input field.
/// </summary>
[Parameter] public string Value { get; set; } = null!;
[Parameter]
public required string Value { get; set; }
/// <summary>
/// Gets or sets the event callback that is triggered when the value changes.
/// </summary>
[Parameter] public EventCallback<string?> ValueChanged { get; set; }
[Parameter]
public required EventCallback<string?> ValueChanged { get; set; }
/// <summary>
/// Gets or sets the expression that identifies the value property.
/// </summary>
[Parameter] public Expression<Func<string>> ValueExpression { get; set; } = null!;
[Parameter]
public required Expression<Func<string>> ValueExpression { get; set; }
/// <summary>
/// Gets or sets the placeholder text for the input field.
/// </summary>
[Parameter] public string Placeholder { get; set; } = null!;
[Parameter]
public required string Placeholder { get; set; }
/// <summary>
/// Gets or sets additional attributes for the input field.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object?>? AdditionalAttributes { get; set; } = new();
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object?>? AdditionalAttributes { get; set; } = new();
}

View File

@@ -0,0 +1,10 @@
@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>

View File

@@ -0,0 +1,36 @@
@inherits LayoutComponentBase
@using AliasVault.Admin.Auth.Components
@implements IDisposable
@inject NavigationManager NavigationManager
<div class="flex flex-col items-center justify-center px-6 pt-8 mx-auto md:h-screen pt:mt-0 dark:bg-gray-900">
<Logo />
<div class="w-full max-w-xl p-6 space-y-4 sm:p-8 bg-white rounded-lg shadow dark:bg-gray-800">
@Body
</div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
/// <inheritdoc />
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
/// <inheritdoc />
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
StateHasChanged();
}
}

View File

@@ -0,0 +1,18 @@
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@@ -0,0 +1,78 @@
//-----------------------------------------------------------------------
// <copyright file="AuthBase.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.Admin.Auth.Pages;
using AliasServerDb;
using AliasVault.Admin.Main.Components.Alerts;
using AliasVault.Admin.Services;
using AliasVault.Auth;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
/// <summary>
/// Base auth page that all pages that are part of the auth (non-logged in part of website) should inherit from.
/// All pages that inherit from this class will require the user to be logged out. If user is logged in they
/// are automatically redirected to index page.
/// </summary>
public class AuthBase : OwningComponentBase
{
/// <summary>
/// Gets or sets the logger.
/// </summary>
[Inject]
protected ILogger<Login> Logger { get; set; } = null!;
/// <summary>
/// Gets or sets the navigation service.
/// </summary>
[Inject]
protected NavigationService NavigationService { get; set; } = null!;
/// <summary>
/// Gets or sets the sign in manager.
/// </summary>
[Inject]
protected SignInManager<AdminUser> SignInManager { get; set; } = null!;
/// <summary>
/// Gets or sets the user manager.
/// </summary>
[Inject]
protected UserManager<AdminUser> UserManager { get; set; } = null!;
/// <summary>
/// Gets or sets the authentication state provider.
/// </summary>
[Inject]
protected AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!;
/// <summary>
/// Gets or sets the auth logging service.
/// </summary>
[Inject]
protected AuthLoggingService AuthLoggingService { get; set; } = null!;
/// <summary>
/// Gets or sets object which holds server validation errors to show in the UI.
/// </summary>
protected ServerValidationErrors ServerValidationErrors { get; set; } = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
// Redirect to home if the user is already authenticated
if (SignInManager.IsSignedIn(user))
{
NavigationService.RedirectTo("./");
}
}
}

View File

@@ -0,0 +1,8 @@
@page "/user/forgot-password"
<LayoutPageTitle>Forgot your password?</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
Forgot your password?
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">If you have forgotten your password, contact the server admin or consult the AliasVault documentation on how to reset your password.</p>

View File

@@ -0,0 +1,8 @@
@page "/user/lockout"
<LayoutPageTitle>Locked out</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
Locked out
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">You have entered an incorrect password too many times and your account has now been locked out. You can try again in 30 minutes.</p>

View File

@@ -0,0 +1,112 @@
@page "/user/login"
@using AliasVault.Shared.Models.Enums
<LayoutPageTitle>Log in</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
Sign in to AliasVault Admin
</h2>
<ServerValidationErrors @ref="ServerValidationErrors" />
<EditForm Model="Input" FormName="LoginForm" OnValidSubmit="LoginUser" class="mt-8 space-y-6">
<DataAnnotationsValidator/>
<div>
<label asp-for="Input.UserName" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your username</label>
<InputTextField id="username" @bind-Value="Input.UserName" placeholder="username" />
<ValidationMessage For="() => Input.UserName"/>
</div>
<div>
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
<InputTextField id="password" @bind-Value="Input.Password" type="password" placeholder="••••••••" />
<ValidationMessage For="() => Input.Password"/>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="remember" aria-describedby="remember" name="remember" type="checkbox" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
</div>
<div class="ml-3 text-sm">
<label for="remember" class="font-medium text-gray-900 dark:text-white">Remember me</label>
</div>
<a href="user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">Lost Password?</a>
</div>
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Login to your account</button>
</EditForm>
@code {
[CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm] private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (HttpMethods.IsGet(HttpContext.Request.Method))
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
}
}
/// <summary>
/// Logs in the user.
/// </summary>
protected async Task LoginUser()
{
ServerValidationErrors.Clear();
var user = await UserManager.FindByNameAsync(Input.UserName);
if (user == null)
{
await AuthLoggingService.LogAuthEventFailAsync(Input.UserName, AuthEventType.Login, AuthFailureReason.InvalidUsername);
ServerValidationErrors.AddError("Error: Invalid login attempt.");
return;
}
var result = await SignInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
await AuthLoggingService.LogAuthEventSuccessAsync(Input.UserName, AuthEventType.Login);
Logger.LogInformation("User logged in.");
NavigationService.RedirectTo(ReturnUrl ?? "/");
}
else if (result.RequiresTwoFactor)
{
await AuthLoggingService.LogAuthEventSuccessAsync(Input.UserName, AuthEventType.Login);
NavigationService.RedirectTo(
"user/loginWith2fa",
new Dictionary<string, object?> { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe });
}
else if (result.IsLockedOut)
{
await AuthLoggingService.LogAuthEventFailAsync(Input.UserName, AuthEventType.Login, AuthFailureReason.AccountLocked);
Logger.LogWarning("User account locked out.");
NavigationService.RedirectTo("user/lockout");
}
else
{
await AuthLoggingService.LogAuthEventFailAsync(Input.UserName, AuthEventType.Login, AuthFailureReason.InvalidPassword);
ServerValidationErrors.AddError("Error: Invalid login attempt.");
}
}
private sealed class InputModel
{
[Required] public string UserName { get; set; } = "";
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = "";
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; } = true;
}
}

View File

@@ -0,0 +1,105 @@
@page "/user/loginWith2fa"
@using AliasVault.Shared.Models.Enums
<LayoutPageTitle>Two-factor authentication</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Two-factor authentication
</h2>
<ServerValidationErrors @ref="ServerValidationErrors" />
<p class="text-gray-700 dark:text-gray-300 mb-6">Your login is protected with an authenticator app. Enter your authenticator code below.</p>
<div class="w-full max-w-md">
<EditForm Model="Input" FormName="login-with-2fa" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-6">
<input type="hidden" name="ReturnUrl" value="@ReturnUrl"/>
<input type="hidden" name="RememberMe" value="@RememberMe"/>
<DataAnnotationsValidator/>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
<div>
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Authenticator code</label>
<InputText @bind-Value="Input.TwoFactorCode" id="two-factor-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off"/>
<ValidationMessage For="() => Input.TwoFactorCode" class="text-red-600 dark:text-red-400 text-sm mt-1"/>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<InputCheckbox @bind-Value="Input.RememberMachine" id="remember-machine" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"/>
</div>
<div class="ml-3 text-sm">
<label for="remember-machine" class="font-medium text-gray-900 dark:text-white">Remember this machine</label>
</div>
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Log in</button>
</EditForm>
</div>
<p class="mt-6 text-sm text-gray-700 dark:text-gray-300">
Don't have access to your authenticator device? You can
<a href="user/loginWithRecoveryCode?ReturnUrl=@ReturnUrl" class="text-primary-600 hover:underline dark:text-primary-500">log in with a recovery code</a>.
</p>
@code {
private AdminUser user = default!;
[SupplyParameterFromForm]
private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
[SupplyParameterFromQuery]
private bool RememberMe { get; set; }
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// Ensure the user has gone through the username & password screen first
user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ??
throw new InvalidOperationException("Unable to load two-factor authentication user.");
}
/// <summary>
/// Submits the form.
/// </summary>
private async Task OnValidSubmitAsync()
{
ServerValidationErrors.Clear();
var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine);
var userId = await UserManager.GetUserIdAsync(user);
if (result.Succeeded)
{
await AuthLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.TwoFactorAuthentication);
Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId);
NavigationService.RedirectTo(ReturnUrl);
}
else if (result.IsLockedOut)
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.AccountLocked);
Logger.LogWarning("User with ID '{UserId}' account locked out.", userId);
NavigationService.RedirectTo("user/lockout");
}
else
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.InvalidTwoFactorCode);
Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId);
ServerValidationErrors.AddError("Error: Invalid authenticator code.");
}
}
private sealed class InputModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Authenticator code")]
public string? TwoFactorCode { get; set; }
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
}
}

View File

@@ -0,0 +1,83 @@
@page "/user/loginWithRecoveryCode"
@using AliasVault.Shared.Models.Enums
<LayoutPageTitle>Recovery code verification</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Recovery code verification
</h2>
<ServerValidationErrors @ref="ServerValidationErrors" />
<p class="text-gray-700 dark:text-gray-300 mb-6">
You have requested to log in with a recovery code. A recovery code is a one-time code that can be used to log in to your account.
Note that if you don't manually disable 2FA after login, you will be asked for an authenticator code again at the next login.
</p>
<div class="w-full max-w-md">
<EditForm Model="Input" FormName="login-with-recovery-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-6">
<DataAnnotationsValidator/>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
<div>
<label for="recovery-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Recovery Code</label>
<InputText @bind-Value="Input.RecoveryCode" id="recovery-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off" placeholder="Enter your recovery code"/>
<ValidationMessage For="() => Input.RecoveryCode" class="text-red-600 dark:text-red-400 text-sm mt-1"/>
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Log in</button>
</EditForm>
</div>
@code {
private AdminUser user = default!;
[SupplyParameterFromForm] private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
// Ensure the user has gone through the username & password screen first
user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ??
throw new InvalidOperationException("Unable to load two-factor authentication user.");
}
/// <summary>
/// Submits the form.
/// </summary>
private async Task OnValidSubmitAsync()
{
ServerValidationErrors.Clear();
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var userId = await UserManager.GetUserIdAsync(user);
if (result.Succeeded)
{
await AuthLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.TwoFactorAuthentication);
Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId);
NavigationService.RedirectTo(ReturnUrl);
}
else if (result.IsLockedOut)
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.AccountLocked);
Logger.LogWarning("User account locked out.");
NavigationService.RedirectTo("user/lockout");
}
else
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.InvalidRecoveryCode);
Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId);
ServerValidationErrors.AddError("Error: Invalid recovery code entered.");
}
}
private sealed class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; } = "";
}
}

View File

@@ -0,0 +1,40 @@
@page "/user/logout"
@using AliasVault.Shared.Models.Enums
@inject UserService UserService
@inject GlobalNotificationService GlobalNotificationService
@code {
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
// Sign out the user.
// NOTE: the try/catch below is a workaround for the issue that the sign out does not work when
// the server session is already started.
try
{
var username = UserService.User().UserName;
try
{
await SignInManager.SignOutAsync();
GlobalNotificationService.ClearMessages();
await AuthLoggingService.LogAuthEventSuccessAsync(username!, AuthEventType.Logout);
// Redirect to the home page with hard refresh.
NavigationService.RedirectTo("/", true);
}
catch
{
// Hard refresh current page if sign out fails. When an interactive server session is already started
// the sign out will fail because it tries to mutate cookies which is only possible when the server
// session is not started yet.
await AuthLoggingService.LogAuthEventSuccessAsync(username!, AuthEventType.Logout);
NavigationService.RedirectTo(NavigationService.Uri, true);
}
}
catch
{
// Redirect to the home page with hard refresh.
NavigationService.RedirectTo("./", true);
}
}
}

View File

@@ -0,0 +1,11 @@
@inherits AuthBase
@using System.ComponentModel.DataAnnotations
@using AliasVault.Admin.Auth.Components
@using AliasVault.Admin.Auth.Layout
@using AliasVault.Admin.Main.Components.Alerts
@using AliasVault.Admin.Main.Components.Layout
@using AliasVault.Admin.Main.Layout
@using AliasVault.Admin.Services
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Identity
@layout AuthLayout

View File

@@ -0,0 +1,67 @@
//-----------------------------------------------------------------------
// <copyright file="RevalidatingAuthenticationStateProvider.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.Admin.Auth.Providers;
using System.Security.Claims;
using AliasServerDb;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
/// <summary>
/// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user
/// every 30 minutes an interactive circuit is connected.
/// </summary>
/// <param name="loggerFactory">ILoggerFactory instance.</param>
/// <param name="scopeFactory">IServiceScopeFactory instance.</param>
/// <param name="options">IOptions instance.</param>
internal sealed class RevalidatingAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> options)
: RevalidatingServerAuthenticationStateProvider(loggerFactory)
{
/// <summary>
/// Gets the revalidation interval.
/// </summary>
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
/// <summary>
/// Validate the authentication state.
/// </summary>
/// <param name="authenticationState">AuthenticationState instance.</param>
/// <param name="cancellationToken">CancellationToken.</param>
/// <returns>Boolean indicating whether the currently logged on user is still valid.</returns>
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
await using var scope = scopeFactory.CreateAsyncScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<AdminUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
private async Task<bool> ValidateSecurityStampAsync(UserManager<AdminUser> userManager, ClaimsPrincipal principal)
{
var user = await userManager.GetUserAsync(principal);
if (user is null)
{
return false;
}
if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
}
}

View File

@@ -1,5 +1,5 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@@ -7,7 +7,6 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AliasVault
@using AliasVault.Components
@using AliasVault.Components.Shared
@using AliasVault.Components.Pages.Aliases
@using AliasVault.Admin
@using AliasVault.Admin.Main
@using AliasServerDb

View File

@@ -0,0 +1,26 @@
//-----------------------------------------------------------------------
// <copyright file="Config.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Admin;
/// <summary>
/// Configuration class for the Admin project with values loaded from environment variables.
/// </summary>
public class Config
{
/// <summary>
/// Gets or sets the admin password hash which is generated by install.sh and will be set
/// as the default password for the admin user.
/// </summary>
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
/// password hash generated by install.sh should replace the current password hash if user already exists.
/// </summary>
public DateTime LastPasswordChanged { get; set; } = DateTime.MinValue;
}

View File

@@ -0,0 +1,26 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 3002
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/AliasVault.Admin/AliasVault.Admin.csproj", "src/AliasVault.Admin/"]
RUN dotnet restore "src/AliasVault.Admin/AliasVault.Admin.csproj"
COPY . .
WORKDIR "/src/src/AliasVault.Admin"
RUN dotnet build "AliasVault.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "AliasVault.Admin.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS=http://+:3002
ENV ASPNETCORE_PATHBASE=/admin
ENTRYPOINT ["dotnet", "AliasVault.Admin.dll"]

View File

@@ -0,0 +1,31 @@
@inject VersionedContentService VersionService
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
<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"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<HeadOutlet @rendermode="RenderModeForPage"/>
</head>
<body class="bg-gray-50 dark:bg-gray-900">
<Routes @rendermode="RenderModeForPage"/>
<script src="@VersionService.GetVersionedPath("lib/qrcode.min.js")"></script>
<script src="@VersionService.GetVersionedPath("js/dark-mode.js")"></script>
<script src="@VersionService.GetVersionedPath("js/utilities.js")"></script>
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@code {
[CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/user")
? null
: InteractiveServer;
}

View File

@@ -0,0 +1,102 @@
@implements IDisposable
@inject NavigationManager NavigationManager
@if (Messages.Count == 0)
{
return;
}
<div class="messages-container grid px-4 pt-6 lg:gap-4 dark:bg-gray-900">
@foreach (var message in Messages)
{
if (message.Key == "success")
{
<AlertMessageSuccess Message="@message.Value" />
}
}
@foreach (var message in Messages)
{
if (message.Key == "info")
{
<AlertMessageInfo Message="@message.Value" />
}
}
@foreach (var message in Messages)
{
if (message.Key == "warning")
{
<AlertMessageWarning Message="@message.Value" />
}
}
@foreach (var message in Messages)
{
if (message.Key == "error")
{
<AlertMessageError Message="@message.Value" />
}
}
</div>
<style>
.messages-container > :last-child {
margin-bottom: 0 !important;
}
</style>
@code {
private List<KeyValuePair<string, string>> Messages { get; set; } = new();
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
RefreshAddMessages();
GlobalNotificationService.OnChange += RefreshAddMessages;
NavigationManager.LocationChanged += HandleLocationChanged;
}
}
/// <inheritdoc />
public void Dispose()
{
GlobalNotificationService.OnChange -= RefreshAddMessages;
NavigationManager.LocationChanged -= HandleLocationChanged;
}
/// <summary>
/// Refreshes the messages on navigation to another page.
/// </summary>
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
{
RefreshAddMessages();
InvokeAsync(StateHasChanged);
}
/// <summary>
/// Refreshes the messages by adding any new messages from the PortalMessageService.
/// </summary>
private void RefreshAddMessages()
{
// We retrieve any additional messages from the GlobalNotificationService that we do not yet have.
var newMessages = GlobalNotificationService.GetMessagesForDisplay();
foreach (var message in newMessages)
{
if (!Messages.Exists(m => m.Key == message.Key && m.Value == message.Value))
{
Messages.Add(message);
}
}
// Remove messages that are no longer in the GlobalNotificationService and have already been displayed.
var messagesToRemove = Messages.Where(m => !newMessages.Exists(nm => nm.Key == m.Key && nm.Value == m.Value)).ToList();
foreach (var message in messagesToRemove)
{
Messages.Remove(message);
}
StateHasChanged();
}
}

View File

@@ -0,0 +1,29 @@
@if (_errors.Any())
{
@foreach (var error in _errors)
{
<AlertMessageError Message="@error" />
}
}
@code {
private readonly List<string> _errors = [];
/// <summary>
/// Adds a server validation error.
/// </summary>
public void AddError(string error)
{
_errors.Add(error);
StateHasChanged();
}
/// <summary>
/// Clears the server validation errors.
/// </summary>
public void Clear()
{
_errors.Clear();
StateHasChanged();
}
}

View File

@@ -0,0 +1,9 @@
<PageTitle>@ChildContent - AliasVault Admin</PageTitle>
@code {
/// <summary>
/// Child content.
/// </summary>
[Parameter]
public RenderFragment ChildContent { get; set; } = default!;
}

View File

@@ -1,12 +1,15 @@
<div class="loading" style="display:@(IsVisible ? "block" : "none");">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
@if (IsVisible)
{
<div class="loading z-50">
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
</div>
}
@code {
private bool IsVisible { get; set; }

View File

@@ -0,0 +1,22 @@
<div role="status" class="px-2" title="@Title">
<svg aria-hidden="true" class="inline w-7 h-7 text-gray-200 @(Spinning ? "animate-spin fill-primary-600" : "") dark:text-gray-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor" />
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill" />
</svg>
<span class="sr-only">Loading...</span>
</div>
@code {
/// <summary>
/// Optional title of the loading indicator.
/// </summary>
[Parameter]
public string Title { get; set; } = string.Empty;
/// <summary>
/// Set spinning to false to stop the animation.
/// </summary>
[Parameter]
public bool Spinning { get; set; } = true;
}

View File

@@ -0,0 +1,17 @@
@inject NavigationManager NavigationManager
@code {
/// <inheritdoc />
protected override void OnInitialized()
{
var returnUrl = NavigationManager.Uri;
if (string.IsNullOrWhiteSpace(returnUrl) || returnUrl == "/")
{
NavigationManager.NavigateTo($"user/login", forceLoad: true);
}
else
{
NavigationManager.NavigateTo($"user/login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true);
}
}
}

View File

@@ -0,0 +1,209 @@
@using AliasVault.WorkerStatus.Database
@inherits MainBase
<button @onclick="SmtpClick"
class="@GetSmtpButtonClasses() mx-3 inline-flex items-center justify-center rounded-xl px-8 py-2 text-white"
disabled="@(!IsHeartbeatValid())"
title="@GetButtonTooltip()">
<span>SmtpService</span>
@if (SmtpPending)
{
<svg class="animate-spin ml-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
}
</button>
@code {
private List<WorkerServiceStatus> ServiceStatus = [];
private bool InitInProgress;
private bool SmtpStatus;
private bool SmtpPending;
private DateTime LastHeartbeat;
/// <summary>
/// The interval in milliseconds for refreshing the service status.
/// </summary>
private readonly int AutoRefreshInterval = 5000;
/// <summary>
/// CancellationTokenSource for the timer.
/// </summary>
private CancellationTokenSource? _timerCancellationTokenSource;
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
_timerCancellationTokenSource = new CancellationTokenSource();
_ = RunPeriodicRefreshAsync(_timerCancellationTokenSource.Token);
}
}
/// <summary>
/// Refreshes the service status periodically while waiting for specified amount of ms in between.
/// </summary>
private async Task RunPeriodicRefreshAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await InitPage();
await Task.Delay(AutoRefreshInterval, cancellationToken);
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_timerCancellationTokenSource?.Cancel();
_timerCancellationTokenSource?.Dispose();
}
}
/// <summary>
/// Gets the CSS classes for the SMTP button based on its current state.
/// </summary>
/// <returns>A string containing the CSS classes for the button.</returns>
private string GetSmtpButtonClasses()
{
string buttonClass = "cursor-pointer ";
if (!IsHeartbeatValid())
{
buttonClass += "bg-gray-600";
}
else if (SmtpStatus)
{
buttonClass += "bg-green-600";
}
else
{
buttonClass += "bg-red-600";
}
return buttonClass;
}
/// <summary>
/// Gets the tooltip text for the SMTP button.
/// </summary>
/// <returns>A string containing the tooltip text.</returns>
private string GetButtonTooltip()
{
return IsHeartbeatValid() ? "" : "Heartbeat offline";
}
/// <summary>
/// Checks if the heartbeat is valid (within the last 5 minutes).
/// </summary>
/// <returns>True if the heartbeat is valid, false otherwise.</returns>
private bool IsHeartbeatValid()
{
return DateTime.Now <= LastHeartbeat.AddMinutes(5);
}
/// <summary>
/// Handles the click event for the SMTP button.
/// </summary>
private async void SmtpClick()
{
if (!IsHeartbeatValid())
{
return;
}
SmtpPending = true;
StateHasChanged();
SmtpStatus = !SmtpStatus;
await UpdateSmtpStatus(SmtpStatus);
SmtpPending = false;
StateHasChanged();
}
/// <summary>
/// Initializes the page by fetching service statuses and updating the SMTP status.
/// </summary>
private async Task InitPage()
{
if (InitInProgress || SmtpPending)
{
return;
}
try
{
InitInProgress = true;
var dbContext = await DbContextFactory.CreateDbContextAsync();
ServiceStatus = await dbContext.WorkerServiceStatuses.ToListAsync();
var smtpEntry = ServiceStatus.Find(x => x.ServiceName == "AliasVault.SmtpService");
if (smtpEntry != null)
{
LastHeartbeat = smtpEntry.Heartbeat;
SmtpStatus = IsHeartbeatValid() && smtpEntry.CurrentStatus == "Started";
}
await InvokeAsync(StateHasChanged);
}
finally
{
InitInProgress = false;
}
}
/// <summary>
/// Update the service statuses.
/// </summary>
public async Task<bool> UpdateServiceStatus(string serviceName, bool newStatus)
{
// Refresh the DbContext to ensure we get the latest data.
var dbContext = await DbContextFactory.CreateDbContextAsync();
var entry = await dbContext.WorkerServiceStatuses.Where(x => x.ServiceName == serviceName).FirstOrDefaultAsync();
if (entry != null)
{
string newDesiredStatus = newStatus ? "Started" : "Stopped";
entry.DesiredStatus = newDesiredStatus;
await dbContext.SaveChangesAsync();
// Wait for service to have updated its status.
var timeout = DateTime.Now.AddSeconds(30);
while (true)
{
if (DateTime.Now > timeout)
{
// Timeout
return false;
}
dbContext = await DbContextFactory.CreateDbContextAsync();
var check = await dbContext.WorkerServiceStatuses.Where(x => x.ServiceName == serviceName).FirstAsync();
if (check.CurrentStatus == newDesiredStatus)
{
// Done
return true;
}
await Task.Delay(1000);
}
}
return false;
}
/// <summary>
/// Update the SMTP service status.
/// </summary>
public async Task<bool> UpdateSmtpStatus(bool newStatus)
{
return await UpdateServiceStatus("AliasVault.SmtpService", newStatus);
}
}

View File

@@ -0,0 +1,10 @@
@using AliasVault.Shared.Core
<footer class="md:flex md:items-center md:justify-between px-4 2xl:px-0 py-6 md:py-10">
<p class="text-sm text-center text-gray-500 mb-4 lg:mb-0">
© 2024 <span>@AppInfo.ApplicationName v@(AppInfo.GetFullVersion())</span>. All rights reserved.
</p>
<ul class="flex flex-wrap items-center justify-center">
<li><a href="https://github.com/lanedirt/AliasVault/blob/main/LICENSE.md" class="mr-4 text-sm font-normal text-gray-500 hover:underline md:mr-6 dark:text-gray-400">License</a></li>
<li><a href="https://github.com/lanedirt/AliasVault" target="_blank" class="text-sm font-normal text-gray-500 hover:underline dark:text-gray-400">GitHub</a></li>
</ul>
</footer>

View File

@@ -0,0 +1,61 @@
@inherits LayoutComponentBase
@implements IDisposable
@inject NavigationManager NavigationManager
@inject GlobalLoadingService GlobalLoadingService
<FullScreenLoadingIndicator @ref="LoadingIndicator" />
<ConfirmModal />
<TopMenu />
<div class="flex pt-16 overflow-hidden bg-gray-50 dark:bg-gray-900">
<div id="main-content" class="relative w-full max-w-screen-2xl mx-auto h-full overflow-y-auto bg-gray-50 dark:bg-gray-900">
<main>
<GlobalNotificationDisplay />
@Body
</main>
<Footer></Footer>
</div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@code {
private FullScreenLoadingIndicator LoadingIndicator = new();
/// <inheritdoc />
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
GlobalLoadingService.OnChange -= OnChange;
}
/// <inheritdoc />
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
GlobalLoadingService.OnChange += OnChange;
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
StateHasChanged();
}
private void OnChange()
{
if (GlobalLoadingService.IsLoading)
{
LoadingIndicator.Show();
}
else
{
LoadingIndicator.Hide();
}
StateHasChanged();
}
}

View File

@@ -0,0 +1,18 @@
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@@ -1,31 +1,35 @@
@inherits PageBase
@inherits MainBase
@implements IDisposable
<header>
<nav class="fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700 py-3 px-4">
<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">
<img src="/icon-trimmed.png" class="mr-3 h-8" alt="AliasVault Logo">
<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>
</a>
<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="/" class="block rounded 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">
Home
<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="/aliases" 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">
Aliases
<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">
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">
Auth logs
</NavLink>
</ul>
</div>
</div>
<div class="flex justify-between items-center lg:order-2">
<div class="mr-3 -mb-1 hidden sm:block">
<span></span>
</div>
<div class="flex justify-end items-center lg:order-2">
<Services />
<button id="theme-toggle" data-tooltip-target="tooltip-toggle" type="button" class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
<svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
@@ -48,12 +52,12 @@
</div>
<ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="userMenuDropdownButton">
<li>
<a href="#" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white">Account settings</a>
<a href="account/manage" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white">Account settings</a>
</li>
</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>
@@ -71,13 +75,28 @@
<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="/aliases" 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">
Aliases
<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">
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">
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">
Auth logs
</NavLink>
</li>
</ul>
@@ -90,12 +109,31 @@
private bool isMobileMenuOpen = false;
private string _username { get; set; } = "";
/// <summary>
/// Close the menu.
/// </summary>
[JSInvokable]
public void CloseMenu()
{
isMenuOpen = false;
isMobileMenuOpen = false;
StateHasChanged();
}
/// <summary>
/// Dispose method.
/// </summary>
public void Dispose()
{
NavigationService.LocationChanged -= LocationChanged;
}
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_username = await GetUsernameAsync();
NavigationManager.LocationChanged += LocationChanged;
_username = GetUsername();
NavigationService.LocationChanged += LocationChanged;
}
/// <inheritdoc />
@@ -110,7 +148,7 @@
}
}
void LocationChanged(object? sender, LocationChangedEventArgs e)
private void LocationChanged(object? sender, LocationChangedEventArgs e)
{
isMenuOpen = false;
isMobileMenuOpen = false;
@@ -126,17 +164,4 @@
{
isMobileMenuOpen = !isMobileMenuOpen;
}
[JSInvokable]
public void CloseMenu()
{
isMenuOpen = false;
isMobileMenuOpen = false;
StateHasChanged();
}
public void Dispose()
{
NavigationManager.LocationChanged -= LocationChanged;
}
}

View File

@@ -0,0 +1,54 @@
//-----------------------------------------------------------------------
// <copyright file="UserViewModel.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.Admin.Main.Models;
/// <summary>
/// User view model.
/// </summary>
public class UserViewModel
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the CreatedAt timestamp.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets the user name.
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether the user has two-factor authentication enabled.
/// </summary>
public bool TwoFactorEnabled { get; set; }
/// <summary>
/// Gets or sets the vault count.
/// </summary>
public int VaultCount { get; set; }
/// <summary>
/// Gets or sets the email claim count.
/// </summary>
public int EmailClaimCount { get; set; }
/// <summary>
/// Gets or sets the total vault storage that this user takes up in kilobytes.
/// </summary>
public int VaultStorageInKb { get; set; }
/// <summary>
/// Gets or sets the last time the vault was updated.
/// </summary>
public DateTime LastVaultUpdate { get; set; }
}

View File

@@ -0,0 +1,86 @@
@page "/account/manage/change-password"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject ILogger<ChangePassword> Logger
<LayoutPageTitle>Change password</LayoutPageTitle>
<div class="max-w-2xl mx-auto">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Change password</h3>
<EditForm Model="Input" FormName="change-password" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-6">
<DataAnnotationsValidator/>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
<div>
<label for="old-password" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Old password</label>
<InputText type="password" @bind-Value="Input.OldPassword" id="old-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password."/>
<ValidationMessage For="() => Input.OldPassword" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
</div>
<div>
<label for="new-password" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">New password</label>
<InputText type="password" @bind-Value="Input.NewPassword" id="new-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password."/>
<ValidationMessage For="() => Input.NewPassword" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
</div>
<div>
<label for="confirm-password" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Confirm password</label>
<InputText type="password" @bind-Value="Input.ConfirmPassword" id="confirm-password" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
<ValidationMessage For="() => Input.ConfirmPassword" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
</div>
<div>
<SubmitButton>Update password</SubmitButton>
</div>
</EditForm>
</div>
@code {
[CascadingParameter] private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm] private InputModel Input { get; set; } = new();
private async Task OnValidSubmitAsync()
{
var changePasswordResult = await UserManager.ChangePasswordAsync(UserService.User(), Input.OldPassword, Input.NewPassword);
var user = UserService.User();
user.LastPasswordChanged = DateTime.UtcNow;
await UserService.UpdateUserAsync(user);
// Clear the password fields
Input.OldPassword = "";
Input.NewPassword = "";
Input.ConfirmPassword = "";
if (!changePasswordResult.Succeeded)
{
GlobalNotificationService.AddErrorMessage($"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}", true);
return;
}
Logger.LogInformation("User changed their password successfully.");
GlobalNotificationService.AddSuccessMessage("Your password has been changed.");
NavigationService.RedirectToCurrentPage();
}
private sealed class InputModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; } = "";
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; } = "";
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; } = "";
}
}

View File

@@ -0,0 +1,25 @@
<h3 class="text-lg font-medium">Recovery codes</h3>
<div class="bg-primary-100 border-l-4 border-primary-500 text-primary-700 p-4" role="alert">
<p class="font-semibold">
Put these codes in a safe place.
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
</div>
<div class="grid grid-cols-1">
@foreach (var recoveryCode in RecoveryCodes)
{
<div>
<code class="block p-2 bg-primary-200 rounded">@recoveryCode</code>
</div>
}
</div>
@code {
/// <summary>
/// The recovery codes to show.
/// </summary>
[Parameter]
public string[] RecoveryCodes { get; set; } = [];
}

View File

@@ -0,0 +1,56 @@
@page "/account/manage/disable-2fa"
@using AliasVault.Shared.Models.Enums
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject ILogger<Disable2fa> Logger
<LayoutPageTitle>Disable two-factor authentication (2FA)</LayoutPageTitle>
<h3 class="text-xl font-bold mb-4">Disable two-factor authentication (2FA)</h3>
<div class="bg-primary-100 border-l-4 border-primary-500 text-primary-700 p-4 mb-4" role="alert">
<p class="font-bold mb-2">
This action only disables 2FA.
</p>
<p>
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a href="account/manage/reset-authenticator" class="text-primary-600 hover:text-primary-800 underline">reset your authenticator keys.</a>
</p>
</div>
<div>
<form @formname="disable-2fa" @onsubmit="OnSubmitAsync" method="post">
<AntiforgeryToken/>
<button class="bg-primary-600 hover:bg-primary-700 text-white font-bold py-2 px-4 rounded" type="submit">Disable 2FA</button>
</form>
</div>
@code {
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
if (!await UserManager.GetTwoFactorEnabledAsync(UserService.User()))
{
throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled.");
}
}
private async Task OnSubmitAsync()
{
var disable2FaResult = await UserManager.SetTwoFactorEnabledAsync(UserService.User(), false);
if (!disable2FaResult.Succeeded)
{
await AuthLoggingService.LogAuthEventFailAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthDisable, AuthFailureReason.Unknown);
throw new InvalidOperationException("Unexpected error occurred disabling 2FA.");
}
await AuthLoggingService.LogAuthEventSuccessAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthDisable);
Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", UserService.User().Id);
// Reload current page.
NavigationService.RedirectTo("account/manage/2fa");
}
}

View File

@@ -0,0 +1,166 @@
@page "/account/manage/enable-authenticator"
@using System.ComponentModel.DataAnnotations
@using System.Globalization
@using System.Text
@using System.Text.Encodings.Web
@using AliasVault.Shared.Models.Enums
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject UrlEncoder UrlEncoder
@inject ILogger<EnableAuthenticator> Logger
<LayoutPageTitle>Configure authenticator app</LayoutPageTitle>
@if (RecoveryCodes is not null)
{
<ShowRecoveryCodes RecoveryCodes="RecoveryCodes.ToArray()"/>
}
else
{
<div class="max-w-2xl mx-auto">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Configure authenticator app</h3>
<div class="space-y-6">
<p class="text-gray-700 dark:text-gray-300">To use an authenticator app go through the following steps:</p>
<ol class="list-decimal space-y-4">
<li>
<p class="text-gray-700 dark:text-gray-300">
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825072" class="text-blue-600 hover:underline dark:text-blue-400">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073" class="text-blue-600 hover:underline dark:text-blue-400">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en" class="text-blue-600 hover:underline dark:text-blue-400">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8" class="text-blue-600 hover:underline dark:text-blue-400">iOS</a>.
</p>
</li>
<li>
<p class="text-gray-700 dark:text-gray-300">Scan the QR Code or enter this key <kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">@SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div id="authenticator-uri" data-url="@AuthenticatorUri" class="mt-4"></div>
</li>
<li>
<p class="text-gray-700 dark:text-gray-300">
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="mt-4">
<EditForm Model="Input" FormName="send-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-4">
<DataAnnotationsValidator/>
<div>
<label for="code" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Verification Code</label>
<InputText @bind-Value="Input.Code" id="code" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="off" placeholder="Please enter the code."/>
<ValidationMessage For="() => Input.Code" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
</div>
<div>
<SubmitButton>Verify</SubmitButton>
</div>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
</EditForm>
</div>
</li>
</ol>
</div>
</div>
}
@code {
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
private string? SharedKey { get; set; }
private string? AuthenticatorUri { get; set; }
private IEnumerable<string>? RecoveryCodes { get; set; }
[SupplyParameterFromForm] private InputModel Input { get; set; } = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadSharedKeyAndQrCodeUriAsync(UserService.User());
await JsInvokeService.RetryInvokeAsync("generateQrCode", TimeSpan.Zero, 5, "authenticator-uri");
}
private async Task OnValidSubmitAsync()
{
// Strip spaces and hyphens
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2FaTokenValid = await UserManager.VerifyTwoFactorTokenAsync(
UserService.User(), UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2FaTokenValid)
{
GlobalNotificationService.AddErrorMessage("Error: Verification code is invalid.");
return;
}
await UserManager.SetTwoFactorEnabledAsync(UserService.User(), true);
await AuthLoggingService.LogAuthEventSuccessAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthEnable);
Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", UserService.User().Id);
GlobalNotificationService.AddSuccessMessage("Your authenticator app has been verified.");
if (await UserManager.CountRecoveryCodesAsync(UserService.User()) == 0)
{
RecoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(UserService.User(), 10);
}
else
{
// Navigate back to the two factor authentication page.
NavigationService.RedirectTo("account/manage/2fa", forceLoad: true);
}
}
private async ValueTask LoadSharedKeyAndQrCodeUriAsync(AdminUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await UserManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
}
SharedKey = FormatKey(unformattedKey!);
var username = await UserManager.GetUserNameAsync(user);
AuthenticatorUri = GenerateQrCodeUri(username!, unformattedKey!);
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
private string GenerateQrCodeUri(string username, string unformattedKey)
{
return string.Format(
CultureInfo.InvariantCulture,
AuthenticatorUriFormat,
UrlEncoder.Encode("AliasVault Admin"),
UrlEncoder.Encode(username),
unformattedKey);
}
private sealed class InputModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; } = "";
}
}

View File

@@ -0,0 +1,61 @@
@page "/account/manage/generate-recovery-codes"
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject ILogger<GenerateRecoveryCodes> Logger
<LayoutPageTitle>Generate two-factor authentication (2FA) recovery codes</LayoutPageTitle>
@if (recoveryCodes is not null)
{
<ShowRecoveryCodes RecoveryCodes="recoveryCodes.ToArray()"/>
}
else
{
<h3 class="text-xl font-bold mb-4">Generate two-factor authentication (2FA) recovery codes</h3>
<div class="bg-primary-100 border-l-4 border-primary-500 text-primary-700 p-4 mb-4" role="alert">
<p class="mb-2">
<svg class="inline w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
</svg>
<strong>Put these codes in a safe place.</strong>
</p>
<p class="mb-2">
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
<p>
Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a href="account/manage/reset-authenticator" class="text-primary-600 hover:text-primary-800 underline">reset your authenticator keys.</a>
</p>
</div>
<div>
<button class="bg-primary-600 hover:bg-primary-700 text-white font-bold py-2 px-4 rounded" @onclick="GenerateCodes" type="submit">Generate Recovery Codes</button>
</div>
}
@code {
private IEnumerable<string>? recoveryCodes;
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(UserService.User());
if (!isTwoFactorEnabled)
{
throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled.");
}
}
private async Task GenerateCodes()
{
var userId = await UserManager.GetUserIdAsync(UserService.User());
recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(UserService.User(), 10);
GlobalNotificationService.AddSuccessMessage("You have generated new recovery codes.");
Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
}
}

View File

@@ -0,0 +1,67 @@
@page "/account/manage"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
<LayoutPageTitle>Profile</LayoutPageTitle>
<div class="max-w-2xl mx-auto">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Profile</h3>
<EditForm Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" class="space-y-6">
<DataAnnotationsValidator/>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
<div>
<label for="username" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Username</label>
<input type="text" value="@username" id="username" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 bg-gray-100 cursor-not-allowed dark:bg-gray-700 dark:border-gray-600 dark:text-gray-400" placeholder="Please choose your username." disabled/>
</div>
<div>
<label for="phone-number" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Phone number</label>
<InputText @bind-Value="Input.PhoneNumber" id="phone-number" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" placeholder="Please enter your phone number."/>
<ValidationMessage For="() => Input.PhoneNumber" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
</div>
<div>
<SubmitButton>Save</SubmitButton>
</div>
</EditForm>
</div>
@code {
private string? username;
private string? phoneNumber;
[SupplyParameterFromForm] private InputModel Input { get; set; } = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
username = await UserManager.GetUserNameAsync(UserService.User());
phoneNumber = await UserManager.GetPhoneNumberAsync(UserService.User());
Input.PhoneNumber ??= phoneNumber;
}
private async Task OnValidSubmitAsync()
{
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await UserManager.SetPhoneNumberAsync(UserService.User(), Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
GlobalNotificationService.AddErrorMessage("Phone number could not be set", true);
}
}
GlobalNotificationService.AddSuccessMessage("Your profile has been updated", true);
}
private sealed class InputModel
{
[Phone]
[Display(Name = "Phone number")]
public string? PhoneNumber { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
@page "/account/manage/reset-authenticator"
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject ILogger<ResetAuthenticator> Logger
<LayoutPageTitle>Reset authenticator key</LayoutPageTitle>
<h3 class="text-xl font-bold mb-4 dark:text-white">Reset authenticator key</h3>
<div class="bg-primary-100 border-l-4 border-primary-500 text-primary-700 p-4 mb-4" role="alert">
<p class="mb-2">
<svg class="inline w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
</svg>
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
</p>
<p>
This process disables 2FA until you verify your authenticator app.
If you do not complete your authenticator app configuration you may lose access to your account.
</p>
</div>
<div>
<form @formname="reset-authenticator" @onsubmit="OnSubmitAsync" method="post">
<AntiforgeryToken/>
<SubmitButton>Reset authenticator key</SubmitButton>
</form>
</div>
@code {
private async Task OnSubmitAsync()
{
await UserManager.SetTwoFactorEnabledAsync(UserService.User(), false);
await UserManager.ResetAuthenticatorKeyAsync(UserService.User());
var userId = await UserManager.GetUserIdAsync(UserService.User());
Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId);
GlobalNotificationService.AddSuccessMessage("Your authenticator app key has been reset, you will need to re-configure your authenticator app using the new key.");
NavigationService.RedirectTo(
"account/manage/2fa");
}
}

View File

@@ -0,0 +1,73 @@
@page "/account/manage/2fa"
@using Microsoft.AspNetCore.Identity
@inject UserManager<AdminUser> UserManager
@inject SignInManager<AdminUser> SignInManager
<LayoutPageTitle>Two-factor authentication (2FA)</LayoutPageTitle>
@if (is2FaEnabled)
{
<div class="mx-auto mt-8 p-6 bg-white dark:bg-gray-800 rounded-lg shadow-md">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Two-factor authentication (2FA)</h3>
@if (recoveryCodesLeft == 0)
{
<div class="mb-4 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 dark:bg-red-900 dark:text-red-100">
<p class="font-bold">You have no recovery codes left.</p>
<p>You must <a href="account/manage/generate-recovery-codes" class="text-red-800 dark:text-red-200 underline">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
</div>
}
else if (recoveryCodesLeft == 1)
{
<div class="mb-4 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 dark:bg-red-900 dark:text-red-100">
<p class="font-bold">You have 1 recovery code left.</p>
<p>You can <a href="account/manage/generate-recovery-codes" class="text-red-800 dark:text-red-200 underline">generate a new set of recovery codes</a>.</p>
</div>
}
else if (recoveryCodesLeft <= 3)
{
<div class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-100">
<p class="font-bold">You have @recoveryCodesLeft recovery codes left.</p>
<p>You should <a href="account/manage/generate-recovery-codes" class="text-yellow-800 dark:text-yellow-200 underline">generate a new set of recovery codes</a>.</p>
</div>
}
<div class="flex space-x-4">
<a href="account/manage/disable-2fa" class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg text-sm focus:ring-4 focus:outline-none focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Disable 2FA</a>
<a href="account/manage/generate-recovery-codes" class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg text-sm focus:ring-4 focus:outline-none focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Reset recovery codes</a>
</div>
</div>
}
<div class="mt-6 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Authenticator app</h4>
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2">
@if (!hasAuthenticator)
{
<LinkButton Href="account/manage/enable-authenticator" Color="primary" Text="Add authenticator app" />
}
else
{
<LinkButton Href="account/manage/enable-authenticator" Color="primary" Text="Add authenticator app" />
<LinkButton Href="account/manage/reset-authenticator" Color="primary" Text="Reset authenticator app" />
}
</div>
</div>
@code {
private bool hasAuthenticator;
private int recoveryCodesLeft;
private bool is2FaEnabled;
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(UserService.User()) is not null;
is2FaEnabled = await UserManager.GetTwoFactorEnabledAsync(UserService.User());
recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(UserService.User());
}
}

View File

@@ -0,0 +1,6 @@
@layout ManageLayout
@inherits MainBase
@using AliasVault.Admin.Auth
@using AliasVault.Admin.Main.Pages.Account.Manage.Components
@using AliasVault.Admin.Main.Components.Layout
@attribute [Microsoft.AspNetCore.Authorization.Authorize]

View File

@@ -0,0 +1,37 @@
@inherits LayoutComponentBase
@using AliasVault.Admin.Main.Layout
@layout MainLayout
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="Manage account"
Description="Manage your profile here.">
</PageHeader>
<div class="container mx-auto px-4 py-8">
<hr class="mb-6 border-t border-gray-300"/>
<div class="flex flex-col md:flex-row">
<div class="w-full md:w-1/4 mb-6 md:mb-0">
<ManageNavMenu/>
</div>
<div class="w-full md:w-3/4 md:pl-8">
@Body
</div>
</div>
</div>
@code {
/// <summary>
/// Gets the breadcrumb items for the page. A default set of breadcrumbs is added in the parent OnInitialized method.
/// </summary>
private List<BreadcrumbItem> BreadcrumbItems { get; } = new();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// Add base breadcrumbs.
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Home", Url = "/" });
}
}

View File

@@ -0,0 +1,15 @@
@using Microsoft.AspNetCore.Identity
@inject SignInManager<AdminUser> SignInManager
<ul class="flex flex-col space-y-1">
<li>
<NavLink href="account/manage" Match="NavLinkMatch.All" class="block px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors duration-150">Profile</NavLink>
</li>
<li>
<NavLink href="account/manage/change-password" class="block px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors duration-150">Password</NavLink>
</li>
<li>
<NavLink href="account/manage/2fa" class="block px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors duration-150">Two-factor authentication</NavLink>
</li>
</ul>

View File

@@ -0,0 +1,147 @@
@page "/emails"
@using AliasVault.RazorComponents.Tables
@inherits MainBase
<LayoutPageTitle>Emails</LayoutPageTitle>
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="Emails"
Description="This page gives an overview of recently received mails by this AliasVault server. Note that all email fields except 'To' are encrypted with the public key of the user and cannot be decrypted by the server.">
<CustomActions>
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
</CustomActions>
</PageHeader>
@if (IsLoading)
{
<LoadingIndicator />
}
else
{
<div class="overflow-x-auto px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var email in EmailList)
{
<SortableTableRow>
<SortableTableColumn IsPrimary="true">@email.Id</SortableTableColumn>
<SortableTableColumn>@email.DateSystem.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
<SortableTableColumn>@(email.FromLocal.Length > 15 ? email.FromLocal.Substring(0, 15) : email.FromLocal)@@@(email.FromDomain.Length > 15 ? email.FromDomain.Substring(0, 15) : email.FromDomain)</SortableTableColumn>
<SortableTableColumn>@email.ToLocal@@@email.ToDomain</SortableTableColumn>
<SortableTableColumn>@(email.Subject.Length > 30 ? email.Subject.Substring(0, 30) : email.Subject)</SortableTableColumn>
<SortableTableColumn>
<span class="line-clamp-1">
@(email.MessagePreview?.Length > 30 ? email.MessagePreview.Substring(0, 30) : email.MessagePreview)
</span>
</SortableTableColumn>
<SortableTableColumn>@email.Attachments.Count</SortableTableColumn>
</SortableTableRow>
}
</SortableTable>
</div>
}
@code {
private readonly List<TableColumn> _tableColumns = [
new TableColumn { Title = "ID", PropertyName = "Id" },
new TableColumn { Title = "Time", PropertyName = "DateSystem" },
new TableColumn { Title = "From", PropertyName = "From" },
new TableColumn { Title = "To", PropertyName = "To" },
new TableColumn { Title = "Subject", PropertyName = "Subject" },
new TableColumn { Title = "Preview", PropertyName = "MessagePreview" },
new TableColumn { Title = "Attachments", PropertyName = "Attachments" },
];
private List<Email> EmailList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
private int PageSize { get; set; } = 50;
private int TotalRecords { get; set; }
private string SortColumn { get; set; } = "Id";
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
private async Task HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
await RefreshData();
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await RefreshData();
}
}
private void HandlePageChanged(int newPage)
{
CurrentPage = newPage;
_ = RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
IQueryable<Email> query = DbContext.Emails;
// Apply sort
switch (SortColumn)
{
case "Id":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
case "DateSystem":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.DateSystem)
: query.OrderByDescending(x => x.DateSystem);
break;
case "From":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.FromLocal + "@" + x.FromDomain)
: query.OrderByDescending(x => x.FromLocal + "@" + x.FromDomain);
break;
case "To":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.ToLocal + "@" + x.ToDomain)
: query.OrderByDescending(x => x.ToLocal + "@" + x.ToDomain);
break;
case "Subject":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Subject)
: query.OrderByDescending(x => x.Subject);
break;
case "MessagePreview":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.MessagePreview)
: query.OrderByDescending(x => x.MessagePreview);
break;
case "Attachments":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Attachments.Count)
: query.OrderByDescending(x => x.Attachments.Count);
break;
default:
query = query.OrderByDescending(x => x.DateSystem);
break;
}
TotalRecords = await query.CountAsync();
EmailList = await query
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
IsLoading = false;
StateHasChanged();
}
}

View File

@@ -1,8 +1,7 @@
@inherits AuthorizePageBase
@page "/Error"
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<LayoutPageTitle>Error</LayoutPageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@@ -31,6 +30,7 @@
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
/// <inheritdoc />
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;

View File

@@ -0,0 +1,21 @@
@page "/"
@inherits MainBase
<LayoutPageTitle>Home</LayoutPageTitle>
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="AliasVault Admin"
Description="Welcome to the AliasVault admin portal.">
</PageHeader>
@code {
/// <inheritdoc />
protected override void OnInitialized()
{
base.OnInitialized();
// Redirect to users page.
NavigationService.RedirectTo("users");
}
}

View File

@@ -0,0 +1,225 @@
@page "/logging/auth"
@using AliasVault.RazorComponents.Tables
@using AliasVault.Shared.Models.Enums
@inherits MainBase
<LayoutPageTitle>Auth logs</LayoutPageTitle>
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="Auth logs"
Description="This page gives an overview of recent auth attempts.">
<CustomActions>
<DeleteButton OnClick="DeleteLogsWithConfirmation" ButtonText="Delete all logs" />
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
</CustomActions>
</PageHeader>
@if (IsLoading)
{
<LoadingIndicator />
}
else
{
<div class="px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<div class="mb-4 flex space-x-4">
<div class="flex w-full">
<div class="w-2/3 pr-2">
<input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search logs..." class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<div class="w-1/3 pl-2">
<select @bind="SelectedEventType" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
<option value="">All event types</option>
@foreach (var eventType in Enum.GetValues<AuthEventType>())
{
<option value="@eventType">@eventType</option>
}
</select>
</div>
</div>
</div>
<SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var log in LogList)
{
<SortableTableRow>
<SortableTableColumn IsPrimary="true">@log.Id</SortableTableColumn>
<SortableTableColumn>@log.Timestamp.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
<SortableTableColumn>@log.Username</SortableTableColumn>
<SortableTableColumn>@log.EventType</SortableTableColumn>
<SortableTableColumn><StatusPill Enabled="log.IsSuccess" TextTrue="Success" TextFalse="Failed" /></SortableTableColumn>
<SortableTableColumn>@log.IpAddress</SortableTableColumn>
</SortableTableRow>
}
</SortableTable>
</div>
}
@code {
private readonly List<TableColumn> _tableColumns = [
new TableColumn { Title = "ID", PropertyName = "Id" },
new TableColumn { Title = "Time", PropertyName = "Timestamp" },
new TableColumn { Title = "Username", PropertyName = "Username" },
new TableColumn { Title = "Event", PropertyName = "EventType" },
new TableColumn { Title = "Success", PropertyName = "IsSuccess" },
new TableColumn { Title = "IP", PropertyName = "IpAddress" },
];
private List<AuthLog> LogList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
private int PageSize { get; set; } = 50;
private int TotalRecords { get; set; }
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
if (_searchTerm != value)
{
_searchTerm = value;
_ = RefreshData();
}
}
}
private string _selectedEventType = string.Empty;
private string SelectedEventType
{
get => _selectedEventType;
set
{
if (_selectedEventType != value)
{
_selectedEventType = value;
_ = RefreshData();
}
}
}
private string SortColumn { get; set; } = "Id";
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
private async Task HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
await RefreshData();
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await RefreshData();
}
}
private void HandlePageChanged(int newPage)
{
CurrentPage = newPage;
_ = RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
var query = DbContext.AuthLogs.AsQueryable();
if (!string.IsNullOrEmpty(SearchTerm))
{
query = query.Where(x => EF.Functions.Like(x.Username.ToLower(), "%" + SearchTerm.ToLower() + "%"));
}
if (!string.IsNullOrEmpty(SelectedEventType))
{
var success = Enum.TryParse<AuthEventType>(SelectedEventType, out var eventType);
if (success)
{
query = query.Where(x => x.EventType == eventType);
}
}
query = ApplySort(query);
TotalRecords = await query.CountAsync();
LogList = await query
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
IsLoading = false;
StateHasChanged();
}
/// <summary>
/// Apply sort to the query.
/// </summary>
private IQueryable<AuthLog> ApplySort(IQueryable<AuthLog> query)
{
// Apply sort.
switch (SortColumn)
{
case "Timestamp":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Timestamp)
: query.OrderByDescending(x => x.Timestamp);
break;
case "Username":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Username)
: query.OrderByDescending(x => x.Username);
break;
case "EventType":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.EventType)
: query.OrderByDescending(x => x.EventType);
break;
case "IsSuccess":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.IsSuccess)
: query.OrderByDescending(x => x.IsSuccess);
break;
case "IpAddress":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.IpAddress)
: query.OrderByDescending(x => x.IpAddress);
break;
default:
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
}
return query;
}
private async Task DeleteLogsWithConfirmation()
{
if (await ConfirmModalService.ShowConfirmation("Confirm Delete", "Are you sure you want to delete all logs? This action cannot be undone."))
{
await DeleteLogs();
}
}
private async Task DeleteLogs()
{
IsLoading = true;
StateHasChanged();
DbContext.AuthLogs.RemoveRange(DbContext.AuthLogs);
await DbContext.SaveChangesAsync();
await RefreshData();
IsLoading = false;
StateHasChanged();
}
}

View File

@@ -0,0 +1,228 @@
@page "/logging/general"
@using AliasVault.RazorComponents.Tables
@inherits MainBase
<LayoutPageTitle>System logs</LayoutPageTitle>
<PageHeader
BreadcrumbItems="@BreadcrumbItems"
Title="General logs"
Description="This page gives an overview of recent system logs.">
<CustomActions>
<DeleteButton OnClick="DeleteLogsWithConfirmation" ButtonText="Delete all logs" />
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
</CustomActions>
</PageHeader>
@if (IsLoading)
{
<LoadingIndicator />
}
else
{
<div class="px-4">
<Paginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
<div class="mb-4 flex space-x-4">
<div class="flex w-full">
<div class="w-2/3 pr-2">
<input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search logs..." class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<div class="w-1/3 pl-2">
<select @bind="SelectedServiceName" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
<option value="">All Services</option>
@foreach (var service in ServiceNames)
{
<option value="@service">@service</option>
}
</select>
</div>
</div>
</div>
<SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var log in LogList)
{
<SortableTableRow>
<SortableTableColumn IsPrimary="true">@log.Id</SortableTableColumn>
<SortableTableColumn>@log.TimeStamp.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
<SortableTableColumn>@log.Application</SortableTableColumn>
<SortableTableColumn>
@{
string bgColor = log.Level switch
{
"Information" => "bg-blue-500",
"Error" => "bg-red-500",
"Warning" => "bg-yellow-500",
"Debug" => "bg-green-500",
_ => "bg-gray-500"
};
}
<span class="px-2 py-1 rounded-full text-white @bgColor">
@log.Level
</span>
</SortableTableColumn>
<SortableTableColumn Title="@log.Exception">
@if (log.SourceContext.Length > 0)
{
<span>@log.SourceContext: </span>
}
@log.Message
</SortableTableColumn>
</SortableTableRow>
}
</SortableTable>
</div>
}
@code {
private readonly List<TableColumn> _tableColumns = [
new TableColumn { Title = "ID", PropertyName = "Id" },
new TableColumn { Title = "Time", PropertyName = "Timestamp" },
new TableColumn { Title = "Application", PropertyName = "Application" },
new TableColumn { Title = "Level", PropertyName = "Level" },
new TableColumn { Title = "Message", PropertyName = "Message" },
];
private List<Log> LogList { get; set; } = [];
private bool IsLoading { get; set; } = true;
private int CurrentPage { get; set; } = 1;
private int PageSize { get; set; } = 50;
private int TotalRecords { get; set; }
private string _searchTerm = string.Empty;
private string SearchTerm
{
get => _searchTerm;
set
{
if (_searchTerm != value)
{
_searchTerm = value;
_ = RefreshData();
}
}
}
private string _selectedServiceName = string.Empty;
private string SelectedServiceName
{
get => _selectedServiceName;
set
{
if (_selectedServiceName != value)
{
_selectedServiceName = value;
_ = RefreshData();
}
}
}
private List<string> ServiceNames { get; set; } = [];
private string SortColumn { get; set; } = "Id";
private SortDirection SortDirection { get; set; } = SortDirection.Descending;
private async Task HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
await RefreshData();
}
/// <inheritdoc />
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
ServiceNames = await DbContext.Logs.Select(l => l.Application).Distinct().ToListAsync();
await RefreshData();
}
}
private void HandlePageChanged(int newPage)
{
CurrentPage = newPage;
_ = RefreshData();
}
private async Task RefreshData()
{
IsLoading = true;
StateHasChanged();
var query = DbContext.Logs.AsQueryable();
if (!string.IsNullOrEmpty(SearchTerm))
{
query = query.Where(x => EF.Functions.Like(x.Application.ToLower(), "%" + SearchTerm.ToLower() + "%") ||
EF.Functions.Like(x.Message.ToLower(), "%" + SearchTerm.ToLower() + "%") ||
EF.Functions.Like(x.Level.ToLower(), "%" + SearchTerm.ToLower() + "%"));
}
if (!string.IsNullOrEmpty(SelectedServiceName))
{
query = query.Where(x => x.Application == SelectedServiceName);
}
// Apply sort.
switch (SortColumn)
{
case "Application":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Application)
: query.OrderByDescending(x => x.Application);
break;
case "Message":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Message)
: query.OrderByDescending(x => x.Message);
break;
case "Level":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Level)
: query.OrderByDescending(x => x.Level);
break;
case "Timestamp":
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.TimeStamp)
: query.OrderByDescending(x => x.TimeStamp);
break;
default:
query = SortDirection == SortDirection.Ascending
? query.OrderBy(x => x.Id)
: query.OrderByDescending(x => x.Id);
break;
}
TotalRecords = await query.CountAsync();
LogList = await query
.Skip((CurrentPage - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
IsLoading = false;
StateHasChanged();
}
private async Task DeleteLogsWithConfirmation()
{
if (await ConfirmModalService.ShowConfirmation("Confirm Delete", "Are you sure you want to delete all logs? This action cannot be undone."))
{
await DeleteLogs();
}
}
private async Task DeleteLogs()
{
IsLoading = true;
StateHasChanged();
DbContext.Logs.RemoveRange(DbContext.Logs);
await DbContext.SaveChangesAsync();
await RefreshData();
IsLoading = false;
StateHasChanged();
}
}

Some files were not shown because too many files have changed in this diff Show More