Compare commits

...

902 Commits

Author SHA1 Message Date
Leendert de Borst
e830b9c482 Bump version to 0.21.1 (#1069) 2025-07-31 09:03:15 +02:00
Leendert de Borst
bc6b9da10b Add wait for i18n to fix browser extension crash on startup, specifically Firefox on Windows (#1066) 2025-07-31 08:49:30 +02:00
Leendert de Borst
40991d879e Update README.md [skip ci] 2025-07-30 13:02:52 +02:00
Leendert de Borst
2949978a11 Bump version (#1064) 2025-07-30 12:08:08 +02:00
Leendert de Borst
9715be40f3 Update changelogs and add NL language (#1064) 2025-07-30 12:08:08 +02:00
Leendert de Borst
a1d146c517 Update android target SDK to 35 as per Play Store requirements (#1064) 2025-07-30 12:08:08 +02:00
Leendert de Borst
b729efbcfb New Crowdin updates (#1063)
* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-30 10:38:47 +02:00
Leendert de Borst
ac0b7c4be8 Make useVaultSync.ts translatable (#1060) 2025-07-30 10:12:58 +02:00
Leendert de Borst
865d5c8fce Refactor app boot to prevent translation initialization errors (#1060) 2025-07-30 10:12:58 +02:00
Leendert de Borst
f154d8afe7 New Crowdin updates (#1059)
* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-29 16:31:38 +02:00
Leendert de Borst
df6bcff8b3 Update browser extension and app translations 2025-07-29 15:48:13 +02:00
Leendert de Borst
3fbfca6163 Update CONTRIBUTING.md 2025-07-29 15:13:12 +02:00
Leendert de Borst
d86ad136f7 New Crowdin updates (#1058)
* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-29 15:13:03 +02:00
Leendert de Borst
a3e51409cf Update browser extension translations 2025-07-29 14:54:08 +02:00
Leendert de Borst
a11052bc77 Simplify singular/plural translations 2025-07-29 14:49:37 +02:00
Leendert de Borst
de4b102397 New Crowdin updates (#1057)
* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (French)
Update translations from Crowdin [ci skip]

* New translations strings.xml (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]
2025-07-29 14:28:02 +02:00
Leendert de Borst
ec66e7c339 Update README.md 2025-07-29 14:26:41 +02:00
Leendert de Borst
59b118b35d Add translations (#1054) 2025-07-29 13:48:49 +02:00
Leendert de Borst
db9ba0eac3 Update translations (#1054) 2025-07-29 13:48:49 +02:00
Leendert de Borst
215e7b0eff Persist language to vault settings in web app during registration (#1054) 2025-07-29 13:48:49 +02:00
Leendert de Borst
d7b97a7139 New Crowdin updates (#1055)
* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (French)
Update translations from Crowdin [ci skip]

* New translations strings.xml (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]
2025-07-29 13:35:56 +02:00
Leendert de Borst
8b23bc6142 New Crowdin updates (#1053)
* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]
2025-07-29 10:23:20 +02:00
Leendert de Borst
49eae07bce Update CONTRIBUTING.md 2025-07-28 16:45:05 +02:00
Leendert de Borst
8a2aafacfb Update translations (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
23c386003e Update context menu (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
16e03d4dbc Refactor slider update logic (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
3616afa625 Update advanced password popup and add translations (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
2fd8ade738 Add advanced password settings to mobile app and slider component (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
e10a37328a Add advanced password settings to mobile app and slider component (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
4811eb9ebe Rearrange credential edit interface for mobile app (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
ba65e0c8ff Update translations (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
1150614722 Add advanced password generator options to content script (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
d10cc79148 Update password settings in browser extension popup CRUD (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
ec833cb430 Tweak web app credential edit layout (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
751f8b6afd Update password generator lib (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
68f351cfc5 Add separate username field component with regenerate button (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
b2177f5d98 Add separate password field component with password length slider (#883) 2025-07-28 16:39:59 +02:00
Leendert de Borst
d43efb0273 New Crowdin updates (#1047)
* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-28 08:26:55 +02:00
Leendert de Borst
490861016a Update icon for attachment preview (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
fa6ff5153a Add attachment file preview for images and text files (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
8ddefa56af Fix attachment download base64 decoding issue (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
0dac97f4ff Update IdentityGenerator return type (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
7da8189789 Add attachment upload option to mobile app (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
a674baa6d6 Add attachment download option to credential view screen (#1010) 2025-07-27 21:43:04 +02:00
Leendert de Borst
9e04e54b43 Add attachment upload during credential create flow (#808) 2025-07-26 12:33:58 +02:00
Leendert de Borst
7cb789ce9d Implement attachment uploader for credential edit flow (#808) 2025-07-26 12:33:58 +02:00
Leendert de Borst
c0a5a7db03 Update translation (#808) 2025-07-26 12:33:58 +02:00
Leendert de Borst
ccb84780eb Add attachment viewer to browser extension (#808) 2025-07-26 12:33:58 +02:00
Leendert de Borst
25acce3ae0 Add attachment to shared TS models (#808) 2025-07-26 12:33:58 +02:00
Leendert de Borst
1d29c3338d Make plain text emails selectable on Android (#1017) 2025-07-25 16:42:43 +02:00
Leendert de Borst
1c95a86c51 Fix public SpamOK email loading issue on mobile app (#998) 2025-07-25 14:29:41 +02:00
Leendert de Borst
78052e74d6 New Crowdin updates (#1040)
* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* Update source file RecentEmails.en.resx
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (French)
Update translations from Crowdin [ci skip]

* New translations strings.xml (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-24 21:47:13 +02:00
Leendert de Borst
70cc2b4985 Update EmailStorageStats hyperlink to be relative 2025-07-24 21:03:37 +02:00
Leendert de Borst
5050fdc95d Update user reference (#1041) 2025-07-24 20:27:48 +02:00
Leendert de Borst
4da10bbfba Add email storage page to admin (#1041) 2025-07-24 20:27:48 +02:00
Leendert de Borst
7844f411ef Refactoring (#1037) 2025-07-24 11:09:58 +02:00
Leendert de Borst
cca687b61f Add user usage statistics to user details page (#1037) 2025-07-24 11:09:58 +02:00
Leendert de Borst
8e6d125700 Add recent usage statistics (#1037) 2025-07-24 11:09:58 +02:00
Leendert de Borst
19fe4121ad Update linting (#990) 2025-07-24 00:30:49 +02:00
Leendert de Borst
6178303418 Add email load more button to mobile app and add missing translations (#990) 2025-07-24 00:30:49 +02:00
Leendert de Borst
d563bd5c02 Add load more button to recent emails in browser extension (#990) 2025-07-24 00:30:49 +02:00
Leendert de Borst
47f55ea08f Add load more button to recent emails in web app (#990) 2025-07-24 00:30:49 +02:00
Leendert de Borst
07bad37568 Update install.sh to avoid GitHub API rate limiting 2025-07-23 20:46:12 +02:00
Leendert de Borst
b0dda6cb77 Add InvariantCulture defaults to Api and SmtpService to prevent regional setting conflicts (#1013) 2025-07-23 20:46:12 +02:00
Leendert de Borst
7a179fcde0 New Crowdin updates (#1034)
* New translations infoplist.strings (French)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Spanish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (German)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Spanish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (German)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-22 21:43:52 +02:00
Leendert de Borst
53decce407 Update SearchWidget.razor (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
dfb8c86366 Update Android tests (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
cec6e7c303 Update Android autofill filter to handle empty strings correctly (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
1993d08487 Update search widget logic (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
6c54c270fa Align browser extension and mobile app credential filter logic (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
c92c8fc663 Update iOS app credential filter logic (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
b0d03d6bb1 Android autofill search in notes text as fallback mechanism (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
68895a7834 Improve credential filter logic for mobile app (#1013) 2025-07-22 19:09:55 +02:00
Leendert de Borst
d183a406ac New Crowdin updates (#1033)
* Update source file InfoPlist.strings
Update translations from Crowdin [ci skip]

* Update source file Localizable.strings
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (French)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Spanish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (German)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (French)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Spanish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (German)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Dutch)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Spanish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (German)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-22 18:49:15 +02:00
Leendert de Borst
55b22dcaa8 Change formatting from ios language files from utf16 to utf-8 2025-07-22 18:07:58 +02:00
Leendert de Borst
44ff1b0118 Fix iOS translation config 2025-07-22 15:39:41 +02:00
Leendert de Borst
ddd7b0a4ab Update crowdin.yml 2025-07-22 14:52:36 +02:00
Leendert de Borst
bd564a1cd9 Update iOS strings file to UTF16 LE for crowdin compatibility 2025-07-22 14:47:22 +02:00
Leendert de Borst
c7aa98a172 Update crowdin.yml 2025-07-22 14:04:51 +02:00
Leendert de Borst
553e716c31 Update crowdin.yml 2025-07-22 13:58:43 +02:00
Leendert de Borst
1e50b7b6bc Tweak enable/disable 2FA flow in web app including translations (#1029) 2025-07-22 11:47:23 +02:00
Leendert de Borst
3fce102471 Show correct breadcrumbs in admin (#995) 2025-07-22 11:39:43 +02:00
Leendert de Borst
297a7b4824 Update confirm modal z-index so it shows on top everywhere (#1026) 2025-07-22 11:39:33 +02:00
dependabot[bot]
9dc80be72a Bump nokogiri in /docs in the bundler group across 1 directory
Bumps the bundler group with 1 update in the /docs directory: [nokogiri](https://github.com/sparklemotion/nokogiri).


Updates `nokogiri` from 1.18.8 to 1.18.9
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.18.8...v1.18.9)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.9
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-22 10:35:16 +02:00
Leendert de Borst
c585bd83d2 New Crowdin updates (#1023)
* New translations importexport.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-21 19:03:28 +02:00
Leendert de Borst
0b81554b38 Update install.sh newline fix (#493) 2025-07-21 19:02:41 +02:00
Leendert de Borst
c93884c306 Update DataProtection config (#493) 2025-07-21 19:02:41 +02:00
Leendert de Borst
e8a40ea18e Update .NET DataProtection config to be resilient against container restarts (#493) 2025-07-21 19:02:41 +02:00
Leendert de Borst
80a9996a23 New translations view.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
7a300d5a46 New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
3985a9e5ab New translations deleteaccount.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
214c76b446 New translations apierrors.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
30a2b0557a New translations welcome.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
e63c198cce New translations importexport.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
1e33c22d32 New translations view.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
e253646c30 New translations home.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
6303924d01 New translations delete.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
42fff611d8 New translations addedit.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
4837d3d855 New translations unlock.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
0b461bd015 New translations createnewidentitywidget.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
a2c69bf36c New translations twofactorauthenticationsection.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
bfe08eada7 New translations showrecoverycodes.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
8361860db5 New translations recentauthlogssection.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
5a1e859185 New translations passwordchangesection.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
f9aa9005da New translations deleteaccountsection.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
de785d7e82 New translations activesessionssection.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
3aac3d9088 New translations importservices.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
ca2088fd7a New translations importservicecard.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
4e08d3f01c New translations editemailformrow.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
3bc2e47d76 New translations emailpreview.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
9546327575 New translations emailmodal.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
9dbfd3ea2b New translations totpviewer.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
b3101c5336 New translations totpcodes.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
454e005127 New translations usernamestep.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
4bde61a70f New translations termsandconditionsstep.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
bda0b11729 New translations login.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
4cc0e66d93 New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
fc091c441c New translations en.json (Dutch)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
d6e510fad3 New translations view.en.resx (German)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
a3cad05cd3 New translations view.en.resx (Spanish)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
b9f3995f5d New translations view.en.resx (French)
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
1b1a5924c3 Update source file en.json
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
7b820ccda1 Update source file Welcome.en.resx
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
459616880e Update source file View.en.resx
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
62c23d34cf Update source file en.json
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
74e7635705 Update source file Welcome.en.resx
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
0a943a5066 Update source file View.en.resx
Update translations from Crowdin [ci skip]
2025-07-21 16:15:02 +02:00
Leendert de Borst
67d3519ff8 Update installation docs (#994) 2025-07-21 15:06:43 +02:00
Leendert de Borst
02f4b53670 Add 64-bit check to install.sh (#994) 2025-07-21 15:06:43 +02:00
Leendert de Borst
3bed56231a Update i18n for web app 2025-07-21 14:36:26 +02:00
Leendert de Borst
5204726bec Add reset vault E2E test, fix delete all scope (#1007) 2025-07-21 11:16:39 +02:00
Leendert de Borst
c5a0bad44d Hard delete all credentials on vault reset (#1007) 2025-07-21 11:16:39 +02:00
Leendert de Borst
f74a09e4bb Update reset vault and refactor into its own page (#1007) 2025-07-21 11:16:39 +02:00
Leendert de Borst
99e17d0792 Add scaffolding for vault reset with local password verify (#1007) 2025-07-21 11:16:39 +02:00
Leendert de Borst
5f7730a474 Update release docs 2025-07-18 17:57:59 +02:00
dependabot[bot]
f9a4937a3a Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /apps/mobile-app directory: [on-headers](https://github.com/jshttp/on-headers) and [compression](https://github.com/expressjs/compression).


Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.8.0 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-18 15:54:16 +02:00
Leendert de Borst
468e7c8b66 Update README.md 2025-07-18 11:35:35 +02:00
Leendert de Borst
8d5d755fdf New Crowdin updates (#1012)
* New translations sharedresources.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-17 08:53:08 +02:00
Leendert de Borst
64857bcbb4 Update AuthTests.cs 2025-07-16 19:55:58 +02:00
Leendert de Borst
66db3e0571 Update CONTRIBUTING.md 2025-07-16 17:11:16 +02:00
Leendert de Borst
4cbed21e67 Update E2E tests 2025-07-16 17:00:59 +02:00
Leendert de Borst
16f8eced09 Update Playwright timeout to allow tests more time 2025-07-16 16:27:18 +02:00
Leendert de Borst
547fa57cb6 Update ApiResponseUtility.cs (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
cbacd7486a Update username validation (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
3b413a79c9 Update E2E tests (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
a2b962bb44 Make topnav structure refresh on language change (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
95739f6758 Simplify WASM localize structure, fix re-render bug in searchwidget (#1006)
This reverts commit 32a2d13fcc.
2025-07-16 11:28:28 +02:00
Leendert de Borst
cc95779f48 Update i18n folder structure and read global config for language switcher (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
25ff5bf994 Refactor browser extension i18n to use single file structure (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
32e6ca597a Update mobile app extra locales (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
a39340262e Cleanup unused translation keys (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
58ed0bf156 Update login to localize error messages returned by API (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
77994d221e Localize UnlockSuccess.tsx (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
519fc5fb24 Refactor VaultMessageHandler.ts for translations (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
accc76d8a2 Add dynamic .json translations for content script (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
0c2de27f1a Catch ApiErrors and translate them in Login.tsx (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
53047cf3ad Update EmailPreview.tsx (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
0b7cdbce02 Update AuthSettings.tsx (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
a963064dc8 Cache localized strings for performance (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
f4c4962cb8 Localize Enable2Fa page (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
3c36020812 Update clickOutsideHandler.js to only listen on mouse outside and explicit escape key (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
9892430e59 Update import/export localization (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
1e3e542f92 Localize form model validations (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
c90c5a9f2f Update user registration flow to show correct error messages (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
7621be4cbe Update AuthController.cs (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
31868b7099 Update Unlock.razor (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
8213a81321 Add ApiErrors enum translations and implement to client login (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
df2ae22a99 Refactor API to output error codes instead of literal error texts (#1006) 2025-07-16 11:28:28 +02:00
Leendert de Borst
9999529d60 Add Crowdin initial language files (#1004)
* New translations emails.json (French)
Update translations from Crowdin [ci skip]

* New translations emails.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations emails.json (German)
Update translations from Crowdin [ci skip]

* New translations emails.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations emails.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations settings.json (French)
Update translations from Crowdin [ci skip]

* New translations settings.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations settings.json (German)
Update translations from Crowdin [ci skip]

* New translations settings.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations settings.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Spanish)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations copypasteformrow.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations copypasteformrow.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations copypasteformrow.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations copypasteformrow.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations copypasteformrow.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]
2025-07-12 14:59:46 +02:00
Leendert de Borst
1df4884301 Update crowdin.yml 2025-07-12 02:04:45 +02:00
Leendert de Borst
185b7a0ad6 Update LanguageService.cs (#1000) 2025-07-11 23:44:45 +02:00
Leendert de Borst
c3dd77d6f8 Add translations documentation (#1000) 2025-07-11 23:44:45 +02:00
Leendert de Borst
c3ae769d11 Cleanup mobile app i18n config file (#1000) 2025-07-11 23:44:45 +02:00
Leendert de Borst
fc7f12471a Remove unused translation keys from browser extension (#1000) 2025-07-11 23:44:45 +02:00
Leendert de Borst
d36a3dba42 Update LanguageService.cs (#1000) 2025-07-11 23:44:45 +02:00
Leendert de Borst
9556e6dca9 Update Crowdin configuration file 2025-07-11 17:45:10 +02:00
Leendert de Borst
c0a63be92b Update crowdin.yml to use absolute paths 2025-07-11 17:44:44 +02:00
Leendert de Borst
2cf1ea2065 Update Crowdin configuration file 2025-07-11 17:33:49 +02:00
Leendert de Borst
df7d1560be Add preserve_translations flag 2025-07-11 15:34:37 +02:00
Leendert de Borst
a6a56ec9fb Update crowdin.yml 2025-07-11 15:27:29 +02:00
Leendert de Borst
3675454737 Create crowdin.yml 2025-07-11 14:59:18 +02:00
Leendert de Borst
da21565f1b Update pods, remove duplicate localizable files (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
5b6a80a7b1 Localize Android native autofill component (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
cb5cd1006c Update mobile app language setting configure for mobile app (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
ca9b9e465c Add locale config for Android app (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
9a6c86569d Bump android dependencies and fix build after adding expo-localization (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
21177e9927 Add localization keys for context menu (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
e7c79f2aa4 Localize vault setting subpages (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
8e89673cc9 Localize credential and email tabs (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
fc75532a0d Localize native iOS autofill component (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
9eb913c692 Add english and dutch languages to iOS app settings (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
e1497b74aa Mobile app i18n scaffolding (#993) 2025-07-11 12:50:41 +02:00
Leendert de Borst
2d85511ec5 Fix top level await issue (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
7c26398e9c Refactor linting (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
23052b375c Move language settings to top of auth settings (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
406505035b Update login localization (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
371ed93819 Use local:language setting (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
e715454acb Localize layout, credential components, email page (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
28c1869048 Localize main popup entrypoint pages (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
bde0877168 Update Settings.tsx (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
2f11b5507c Add i18n scaffolding to browser extension (#992) 2025-07-09 11:42:45 +02:00
Leendert de Borst
149a85dde9 Update DbUpgradeTests.cs (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
cdfe7c5a99 Update tests (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
23378368fb Refactor to prevent duplicate vault saves on vault creation (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
27fad07f92 Make languageswitcher show proper initial browser language (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
29b5501a01 Tweak E2E test flow (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
988c43ae20 Refactor SharedLocalizer to MainBase (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
f9e94c3059 Refactor (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
1969dd0b48 Add flag icon to language switcher (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
f7a0f3d29a Add dynamic language switcher via Blazor.WebAssembly.DynamicCulture.Loader (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
2464858b4e Localize index.template.html strings separately (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
f793510b1e Add language switcher to AliasVault.Client (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
e7644dc3fb Localize email components (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
67d4a0b8ff Localize all import/export subcomponents (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
0e37616ced Localize recentEmails, import, edit form (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
182e5d8d8d Localize security settings, footer, email (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
f19e288196 Localize vault sync messages (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
8bff55414c Localize forgot password, start, logout (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
63b18acbac Localize search widget, unlock, delete pages (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
49676bf1f4 Localize passwordstep and credential view page (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
db39a18ab5 Localize setup and settings (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
4d57f8dea3 Make topmenu and welcome localized (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
3160ad202a Use IStringLocalizerFactory to simplify structure (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
946a44a9a1 Make i18n work for login switching between en-US and nl-NL (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
4bba4c5911 Add i18n scaffolding to AliasVault.Client project (#820) 2025-07-07 16:35:05 +02:00
Leendert de Borst
50c401cee4 Merge branch 'main' of https://github.com/lanedirt/AliasVault
* 'main' of https://github.com/lanedirt/AliasVault:
  Revert image versions back to :latest (#986)
  Add docker-compose.yml check for latest version (#986)
2025-07-02 10:26:24 +02:00
Leendert de Borst
4e09912420 Bump version to 0.20.2 2025-07-02 10:26:22 +02:00
Leendert de Borst
6c8843dc5b Revert image versions back to :latest (#986) 2025-07-02 10:25:58 +02:00
Leendert de Borst
4c4aa4ba26 Add docker-compose.yml check for latest version (#986) 2025-07-02 10:25:58 +02:00
Leendert de Borst
5ac5f54f78 Add browser extension changelog (#983) 2025-07-01 22:45:05 +02:00
Leendert de Borst
d488107b75 Bump version (#983) 2025-07-01 22:45:05 +02:00
Leendert de Borst
fe30116b33 Check for null with API base URL (#983) 2025-07-01 22:45:05 +02:00
Leendert de Borst
77ced32206 Update install.sh (#981) 2025-07-01 14:27:34 +02:00
Leendert de Borst
299d1f6075 Fix issue with vault upgrade that used the wrong migration key (#981) 2025-07-01 14:27:34 +02:00
Leendert de Borst
9811e32a73 Add changelog for 0.20.0 (#981) 2025-07-01 14:27:34 +02:00
Leendert de Borst
7655773fa3 Bump version (#981) 2025-07-01 14:27:34 +02:00
Leendert de Borst
7a5afcac9c Update publish release docs (#981) 2025-07-01 14:27:34 +02:00
Leendert de Borst
1ab736fd03 Add fastlane Android app metadata for 0.19.0 (#979) 2025-06-30 22:50:20 +02:00
Leendert de Borst
018895e8e9 Update browser extension setting page margins 2025-06-30 16:11:15 +02:00
Leendert de Borst
0b07a37d73 Simplify loop (#976) 2025-06-30 14:53:09 +02:00
Leendert de Borst
5c0d7fc571 Make email delete not fully refresh page, refactoring (#976) 2025-06-30 14:53:09 +02:00
Leendert de Borst
d9d84dd90f Add auto refresh to emails page (#976) 2025-06-30 14:53:09 +02:00
Leendert de Borst
70b7063af2 Remove rememberMe flag from mobile app login (#974) 2025-06-30 14:24:08 +02:00
Leendert de Borst
87287e0237 Update setting update query (#974) 2025-06-30 14:24:08 +02:00
Leendert de Borst
477e786454 Update settings titles (#974) 2025-06-30 14:24:08 +02:00
Leendert de Borst
361ea77ab7 Add identity generator settings scaffolding to app (#974) 2025-06-30 14:24:08 +02:00
Leendert de Borst
36237176fd Update install.md DNS instructions 2025-06-30 14:04:51 +02:00
Leendert de Borst
e15ecaf793 Add mobile app identity generator setting retrieval (#861) 2025-06-29 11:08:02 +02:00
Leendert de Borst
4422ddcaa3 Add identity setting retrieval to content script (#861) 2025-06-29 11:08:02 +02:00
Leendert de Borst
e34e96746f Update terminology (#861) 2025-06-29 11:08:02 +02:00
Leendert de Borst
4c4d51d78e Implement identity generator gender in browser extension AddEdit screen (#861) 2025-06-29 11:08:02 +02:00
Leendert de Borst
e4b12c4617 Add alias gender config option to general settings (#861) 2025-06-29 11:08:02 +02:00
Leendert de Borst
1cf9b5e93c Revert default config for AliasVault.Client 2025-06-28 12:17:12 +02:00
Leendert de Borst
6664266c3f Update email DNS config docs (#971) 2025-06-28 11:26:41 +02:00
Leendert de Borst
79af285124 Update tests (#969) 2025-06-27 16:05:39 +02:00
Leendert de Borst
66928f74b7 Add improved email interface with sidebar for desktop browsers (#969) 2025-06-27 16:05:39 +02:00
Leendert de Borst
c8599ccd9e Add SMTP service run to vscode tasks.json (#969) 2025-06-27 16:05:39 +02:00
Leendert de Borst
53f69c97af Make new admin links relative (#967) 2025-06-27 14:41:21 +02:00
Leendert de Borst
11d8c941d2 Add all-time stats page to admin (#967) 2025-06-27 13:18:16 +02:00
Leendert de Borst
e31f3df45b Disable autocorrect on iOS autofill search field (#965) 2025-06-27 12:26:05 +02:00
Leendert de Borst
e2aafa3704 Update docs (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
c2290f3ba4 Update docker-build.yml (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
b134ef3aee Update port example (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
912c486266 Create env file before doing port availability check (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
51901e6ce3 Update docker-build.yml (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
0dbe417636 Remove redundant logic (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
6f9528ea2d Update newlines (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
3266f7394e Update README.md (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
9fd5848029 Update install script logic (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
0e2d7cabe8 Update success messages (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
2e5b00ea2c Update ssl-configuration command info (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
ff535188da Add reusable success message (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
bb41207cfe Update layout (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
5944cd3248 Add semver validation to install command (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
0f02412db2 Add minimum docker version instructions (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
db479182f0 Add port availability checks (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
d5f8516abc Add Docker lightweight dependency test (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
1682304ae7 Add dependency checks (#963) 2025-06-27 10:51:07 +02:00
Leendert de Borst
d0bbf3ac9f Update README.md 2025-06-25 21:09:21 +02:00
Leendert de Borst
12492c922d Start vault revisions from 1 instead of 0 (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
3240c3760a Remove deprecated method (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
58801926cc Make mobile app autofill more resilient towards failures (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
39b5c03ae1 Add unsupported vault detection to web client (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
b01cdc1f52 Update wording (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
ce0f466f01 Update DbService.cs (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
80e40b3ceb Improve mobile app flow for pending migration check (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
70bb8ef3e4 Add vault outdated status flag (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
00fb290598 Refactor upgrade to use vaultMutate hook (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
9d8a2e784f Add pending migration check to main app boot and reinitialize (app timeout) (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
e57cb01164 Do not wait for logout call to finish when explicitly logging out so its compatible with offline mode (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
6f421bbdc1 Only do pendingmigrations check in sync if vault is unlocked (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
eaa42196f8 Revert app index back to credentials navigation redirect (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
e844e20322 Fix self-host check based on Api Url (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
b53a4334ca Prevent double sync when opening popup (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
afe2ba52b5 Add vault upgrade check to autofill popup (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
3e82c6e5d0 Implement modal in upgrade page (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
68dbecd536 Update unlock and upgrade UI (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
c0c1b75e73 Throw error if vault version is unknown (newer) during login (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
8510648b5f Show upgrade screen when unlocking inline (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
0e803205c0 Refactor unlock success flow (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
2fc7ffa509 Linting refactor (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
b16fd8e157 Update unlock page UI (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
effeb211ff Delete UserMenu.tsx (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
bfc15fcea6 Make unlock work, simplify db upgrade checks (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
6bb204efb9 Update upgrade page UI (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
dbc9724377 Fix vault mutation issue that caused redirect to fail (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
71783f1af2 Add upgrade required checks (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
7ead1d270b Prefer /logout navigation instead of directly calling apis (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
19b89cbfda Refactor navigation in browser extension to follow mobile app reinitialize structure (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
0617ccb42e Remove min vault version check (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
a3d702f2e5 Update database version retrieval to use VaultVersion objects (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
3967b0f832 Add isSelfHosted check (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
867dd90000 Add Upgrade.tsx scaffolding (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
6ed1be3b91 Hide bottom nav for specific non-auth pages (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
56e065feea Implement ApiUrlUtility (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
3b27e647ef Add self-host warning to vault upgrade page (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
62732a71f0 Add known vault version check: logout if vault is newer than the app knows about (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
f3ad61a77a Add upgrade version info tooltip to AliasVault.Client (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
0d878f669f Show vault upgrade description in popup (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
6fba784cfe Update vault-sql (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
c46a95cf82 Add mobile app executeRaw query native implementations (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
bba16e6e14 Show API url in settings page, refactor login api url rendering (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
b4c4603868 Add onUpgradeRequired and executeRaw logic to iOS (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
925455b5d6 Update vault-sql and remove unnecessary update commands (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
6aa0c2b9df Remove obsolete version identifier (#957) 2025-06-25 16:50:26 +02:00
Leendert de Borst
1799a2f580 Update login-settings.tsx layout scaffolding (#959) 2025-06-24 19:30:19 +02:00
Leendert de Borst
615b5b2883 Update top level _layout.tsx so header has correct size on Android (#959) 2025-06-24 19:30:19 +02:00
Leendert de Borst
006f89b6b7 Update CONTRIBUTING.md 2025-06-24 11:18:27 +02:00
dependabot[bot]
76c60ad200 Bump the npm_and_yarn group across 1 directory with 3 updates
Bumps the npm_and_yarn group with 3 updates in the /shared/vault-sql directory: [esbuild](https://github.com/evanw/esbuild), [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) and [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8).


Updates `esbuild` from 0.21.5 to 0.25.5
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.21.5...v0.25.5)

Updates `vitest` from 2.1.9 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/vitest)

Updates `@vitest/coverage-v8` from 2.1.9 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/coverage-v8)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vitest
  dependency-version: 3.2.4
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 3.2.4
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 19:42:53 +02:00
Leendert de Borst
1830dc0ca1 Exclude static sql files from sonarcloud scanner (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
c3599c9f26 Simplify structure (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
5d050cd278 Commit generated SQL files to Git for documentation purposes (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
ff57091eef Update service-worker.published.js to include new shared TS libs to cache (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
64ef5837c0 Add vault-sql shared module binaries to browser extension and mobile app (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
771f372434 Replace EF pending migrations check with JsInterop version (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
7690355434 Refactor (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
822b95d940 Refactor vault sql to include release info (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
41b2a959ed Add scripts to convert EF core structure to Typescript definitions (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
3e82f78fe9 Make vault creation work via vault-sql lib in AliasVault.Client (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
421884e301 Update shared package scaffolding (#955) 2025-06-23 16:37:10 +02:00
Leendert de Borst
d149e5aeec Add vault-sql shared project scaffolding 2025-06-23 16:37:10 +02:00
Leendert de Borst
8b2702cbe3 Update App.tsx (#953) 2025-06-22 11:23:12 +02:00
Leendert de Borst
7b1cfd363c Add popout button to the credential and email pages via new methods (#953) 2025-06-22 11:23:12 +02:00
Leendert de Borst
5e965d7b3f Add popout button to login and unlock page (#953) 2025-06-22 11:23:12 +02:00
Leendert de Borst
d8ac05f325 Add favicon to browser extension html (#953) 2025-06-22 11:23:12 +02:00
Leendert de Borst
a1c13a15f9 Add manual CSV unit test (#951) 2025-06-21 23:39:52 +02:00
Leendert de Borst
f285b36c61 Add generic CSV importer based on an example template (#951) 2025-06-21 23:39:52 +02:00
Leendert de Borst
c6fa90e00c Update .gitignore (#948) 2025-06-21 15:51:39 +02:00
Leendert de Borst
cb8de80f08 Update null check (#948) 2025-06-21 15:51:39 +02:00
Leendert de Borst
15bb7f6593 Add recent auth log attempts to user details page (#948) 2025-06-21 15:51:39 +02:00
Leendert de Borst
516dd524df Make auth log username clickable (#948) 2025-06-21 15:51:39 +02:00
Leendert de Borst
87e58f8546 Add LastPass import unit test (#947) 2025-06-21 13:02:34 +02:00
Leendert de Borst
3baaf78689 Add LastPass importer logic (#947) 2025-06-21 13:02:34 +02:00
Leendert de Borst
336bbafe27 Fix inline unlock confirm message (#945) 2025-06-20 18:55:58 +02:00
Leendert de Borst
83d9eadeea Bump version to 0.19.2 (#943) 2025-06-19 15:08:19 +02:00
Leendert de Borst
1cdd8f456e Make admin redirects work with custom ports through nginx docker (#940) 2025-06-19 11:52:43 +02:00
Leendert de Borst
395f881bd0 Bump version to 0.19.1 (#938) 2025-06-18 13:49:13 +02:00
Leendert de Borst
293ae102c5 Update history handling (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
8f5852bb86 Optimize load and persist flow (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
9ccaff74cd Update imports (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
ee6b40dd3d Refactor navigation logic from Home.tsx to NavigationContext (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
3ca4c0a78d Update icons folder casing (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
b246def212 Refactor persist logic to protect data at rest (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
1eecb8be38 Clear persisted form values if time has expired (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
9a7fbe7d2a Add form persist and restore logic (#935) 2025-06-18 13:30:14 +02:00
Leendert de Borst
7776fb6d82 Remember last visited page in browser extension and navigate back on reopen (#928) 2025-06-18 13:30:14 +02:00
Leendert de Borst
0eebaddf04 Move notes to bottom for view mode in mobile app and browser extension (#933) 2025-06-17 19:39:25 +02:00
Leendert de Borst
8b145e66b5 Only show email preview if email is supported by AliasVault public or private (#928) 2025-06-17 19:39:16 +02:00
Leendert de Borst
4e3c992c24 Update ErrorVaultDecrypt.razor typo (#928) 2025-06-17 19:39:16 +02:00
Leendert de Borst
65944b1523 Fix toast text color on dark mode (#931) 2025-06-17 19:39:07 +02:00
Leendert de Borst
d05114fddc Make view details and edit buttons work in iOS autofill popup (#931) 2025-06-17 19:39:07 +02:00
Leendert de Borst
8e0fef4b16 Add x-forwarded-prefix header to admin to support running on non-default ports (#929) 2025-06-17 19:38:56 +02:00
Leendert de Borst
1bf8b7ee04 Bump version to 0.19.0 (#926) 2025-06-16 12:34:40 +02:00
Leendert de Borst
8545b2c1fd Merge pull request #925 from lanedirt/890-feature-request-add-create-credential-button-in-bottom-right-corner-for-easier-access
Move create credential button to bottom right corner for easier access
2025-06-16 00:27:47 +02:00
Leendert de Borst
2f22e4db56 Make user avatar dynamic instead of showing old icon (#890) 2025-06-15 14:00:36 +02:00
Leendert de Borst
54bbbb0647 Change create credential button into floating action button (#890) 2025-06-15 13:44:25 +02:00
Leendert de Borst
0b127a4a3e Update Android to use adaptive icon with gradient bg (#922) 2025-06-13 21:17:17 +02:00
Leendert de Borst
241f17868b Update Android app icon to use black background (#922) 2025-06-13 21:17:17 +02:00
Leendert de Borst
be536741c5 Update iOS app to use dark background (#922) 2025-06-13 21:17:17 +02:00
Leendert de Borst
7638879aa9 Update disabled email cleanup task log notice (#920) 2025-06-13 18:56:54 +02:00
Leendert de Borst
499f6e451e Add integration test for disabled email alias delete task (#920) 2025-06-13 18:02:12 +02:00
Leendert de Borst
73ad8f6acd Add disabled email cleanup task to TaskRunner (#920) 2025-06-13 18:02:12 +02:00
Leendert de Borst
c5ea7d0143 Ensure email claim UpdatedAt is properly triggered and re-enabled if claimed again by same user (#920) 2025-06-13 18:02:12 +02:00
Leendert de Borst
0473ec21bf Add disabled email retention setting to admin (#920) 2025-06-13 18:02:12 +02:00
Leendert de Borst
0eb7e97383 Add QuickCreate state service to persist values when switching between quick and advanced mode (#916) 2025-06-13 18:01:56 +02:00
Leendert de Borst
7d35777c93 Add browser extension missing AppInfo.ts to bump version script (#917) 2025-06-12 18:14:40 +02:00
Leendert de Borst
08e39ef3e9 Fix admin base url protocol mismatch on some environments (#914) 2025-06-12 17:50:25 +02:00
Leendert de Borst
fe10acb925 Add HTTP security headers to nginx reverse proxy config (#914) 2025-06-12 15:07:10 +02:00
Leendert de Borst
061f846b66 Update browser extension and mobile app download UI (#914) 2025-06-12 15:07:10 +02:00
Leendert de Borst
eb64d86c78 Remove console writelines (#914) 2025-06-12 15:07:10 +02:00
Leendert de Borst
ef2a58f784 Remove unused css import (#914) 2025-06-12 15:07:10 +02:00
Leendert de Borst
a43d50f047 Add confirmation modal to credential and email delete (#911) 2025-06-12 14:55:00 +02:00
Leendert de Borst
0d5fd55133 Make browser extension popout use full height/width in all browsers (#909) 2025-06-12 14:54:50 +02:00
Leendert de Borst
d9942844e2 Fix attachment download in browser extension and mobile app (#902) 2025-06-12 09:56:50 +02:00
Leendert de Borst
15a1276d42 Tweak android autofill item display preview (#904) 2025-06-12 09:56:39 +02:00
Leendert de Borst
37d6ead41d Clear dbcontext after loading a (new) vault from server (#906) 2025-06-12 09:56:31 +02:00
dependabot[bot]
fa99cb77d7 Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/server/AliasVault.Admin directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).
Bumps the npm_and_yarn group with 1 update in the /apps/server/AliasVault.Client directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).


Updates `brace-expansion` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v2.0.1...v2.0.2)

Updates `brace-expansion` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 2.0.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 2.0.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-12 09:56:22 +02:00
Leendert de Borst
f9987b5e2a Add email error response parsing to browser extension and mobile app (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
ec11ab0817 Move shared projects to dist/shared (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
ecd592e74f Allow null values in credential add edit (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
a3208e72bf Reduce min loading duration for client (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
d66dee3583 Fix auto sync on extension open, update icon sizes (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
68471b7c88 Tweak loading animation on credential list refresh (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
3d8c2b7086 Add (re)generate username and password controls (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
a93a7f7fff Add random alias / manual toggle icons to browser extension and mobile app (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
1b84fd1dad Fix margin issue when loading popup shows (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
c673a20fd1 Add favicon extractor (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
7e81e70ec4 Focus service name field on create mode (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
c688764831 Add credential add page (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
3da40f42c9 Add form validation to credential edit (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
fd74b7b056 Add loading animation to add edit submit (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
0ccbeb683d Make credential edit flow work (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
34d00dc7d6 Add logout section to settings page (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
ffe1a36df3 Move page primary actions to header (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
0f9c2d1f7c Make basic vault update in browser extension work with delete call (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
19499f02d6 Add edit page scaffolding (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
330a92fbb3 Add useVaultMutate hook compatible with browser extension (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
5ca29a33d0 Refactor shared metadata models, update browser extension to use vaultsync hook (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
ab6191ac62 Refactor browser extension to use shared vault models (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
f8bf575ab5 Refactor mobile app to use shared vault models (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
3576b32821 Refactor shared models to subdir structure (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
4619fe615c Add AuthEventType enum to shared models (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
e8ba964064 Update mobile app to use shared webapi models (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
4af1a127cf Apply sort lint rules to mobile app imports (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
22acea0e35 Refactor browser extension to use shared types, add import order lint rules (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
c6d7d16b27 Add import resolve checking during linting (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
aba377ac65 Update models build (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
5a0d1eabb7 Update build-and-distribute.sh (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
eb2c4c1cd3 Add models build script (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
62224c86cd Add separate build file for password-generator (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
6ab20501e9 Add separate build file for identity-generator (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
dd82803f87 Add shared models scaffolding (#900) 2025-06-11 21:52:21 +02:00
Leendert de Borst
27d19759c8 Update MinDurationLoadingService.cs (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
c6faa4db97 Add wait to E2E email test due to new loading animation (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
f35d46256f Add title tag to lock and refresh buttons (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
4683d6bea6 Add skeleton loading animation to recent emails (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
566d4259bd Add skeleton loading animation to email page (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
afee07885d Update credential card UI to prevent overflow (#897) 2025-06-07 14:14:37 +02:00
Leendert de Borst
8e8ef8fd5d Remove top level dictionaries which is now stored in shared utils (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
5589042606 Remove .NET generator projects (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
cbe8b2c471 Make shared generators work when called from .NET Blazor interop (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
4c7bef2a5a Refactor to use new factory methods for identity and password generators (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
bc6479bf5e Update sonarcloud analysis excludes (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
845f780707 Update shared utils in browser extension and mobile app (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
1089e8299f Update add-edit.tsx (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
ce9b37d299 Add generated header to ignore sonarcloud for compiled TS (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
538675f391 Replace SpamOK.PasswordGenerator with shared TS implementation (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
260aec34ce Add shared libraries to AliasVault.Client (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
a7ffc33d56 Add factories to shared generators so it can be called from Blazor (#896) 2025-06-06 14:23:54 +02:00
Leendert de Borst
89a57b6047 Push shared libraries to AliasVault.Client (#886) 2025-06-06 14:23:54 +02:00
Leendert de Borst
a66e8b6b0d Update UI margins (#886) 2025-06-04 17:13:06 +02:00
Leendert de Borst
5de0806bcc Add clear button to input field components (#886) 2025-06-04 17:13:06 +02:00
Leendert de Borst
a1d2bcbe3b Update CredentialCard.tsx (#882) 2025-06-04 17:12:55 +02:00
Leendert de Borst
fbc085439c Add native context menu to credential list (#880) 2025-06-04 17:12:55 +02:00
Leendert de Borst
4a35a1a7d3 Update project.pbxproj 2025-06-03 17:36:43 +02:00
Leendert de Borst
bd82037d8c Bump version to 0.18.1 2025-06-02 23:39:08 +02:00
Leendert de Borst
9615634bf9 Add docker build and push back to release.yml (#887) 2025-06-02 23:38:54 +02:00
Leendert de Borst
dfd2b534e6 Add iOS build workflow action (#887) 2025-06-02 23:30:40 +02:00
Leendert de Borst
314c757fe6 Refactor build android step to reusable action (#887) 2025-06-02 23:30:40 +02:00
Leendert de Borst
771abe9cc1 Update bump version script to also bump browser package.json (#887) 2025-06-02 23:30:40 +02:00
Leendert de Borst
22aaf17cd1 Refactor browser extension build to reusable workflow (#887) 2025-06-02 23:30:40 +02:00
Leendert de Borst
2134b61a78 Make release app build use the correct file location (#887) 2025-06-02 23:30:40 +02:00
Leendert de Borst
0059e31892 Update README.md 2025-06-02 17:14:26 +02:00
Leendert de Borst
2f7a4370b7 Improve sanity checks for if biometrics are not available (#880) 2025-06-02 14:21:43 +02:00
Leendert de Borst
5fc2889a03 Make username case insensitive for mobile apps (#884) 2025-06-02 11:56:43 +02:00
Leendert de Borst
f43bc402ba Make username case insensitive during login for browser extension (#884) 2025-06-02 11:56:43 +02:00
Leendert de Borst
2e6d4fbe20 Update README.md 2025-06-01 11:06:26 +02:00
Leendert de Borst
38db3c5054 Update docs 2025-05-31 15:56:50 +02:00
Leendert de Borst
971a21a16a Update README.md 2025-05-31 15:39:59 +02:00
Leendert de Borst
8058912eee Bump iOS app version and tweak bump version script (#878) 2025-05-31 15:39:59 +02:00
Leendert de Borst
8a9e1dc9a3 Update create-new-release docs (#878) 2025-05-31 15:39:59 +02:00
Leendert de Borst
cde78650b9 Bump version to 0.18.0 (#878) 2025-05-31 15:39:59 +02:00
Leendert de Borst
4ef9e58665 Update StartupTasks.cs (#876) 2025-05-31 12:12:55 +02:00
Leendert de Borst
b6b1d9dec9 Add amount of emails stored per user to admin user listing (#876) 2025-05-31 12:12:55 +02:00
Leendert de Borst
fa2dedb05a Unblock admin user when a password request has been requested (#876) 2025-05-31 12:12:55 +02:00
Leendert de Borst
f148ccdeba Add revoke all option to admin user refresh tokens (#874) 2025-05-31 11:42:43 +02:00
Leendert de Borst
9b038cb76c Truncate credential name/preview if too long (#872) 2025-05-31 08:47:41 +02:00
Leendert de Borst
aa726706a4 Make browser extension auth settings less strict (#872) 2025-05-31 08:47:41 +02:00
Leendert de Borst
d0017d9207 Add android app download link (#870) 2025-05-31 08:37:42 +02:00
Leendert de Borst
cde4b87371 Return fake login response if username is invalid (#868) 2025-05-31 07:45:40 +02:00
Leendert de Borst
431d8d4fca Only trigger autofill popup on username/email/password field types (#866) 2025-05-30 23:41:50 +02:00
Leendert de Borst
9fddb5f450 Reset client url on wrong input (#858) 2025-05-30 22:59:16 +02:00
Leendert de Borst
dbb6cf5b94 Add yup validation schema to auth settings (#858) 2025-05-30 22:59:16 +02:00
Leendert de Borst
bd41507ef9 Use absolute path for docker volume bind mounts (#859) 2025-05-30 18:03:53 +02:00
Leendert de Borst
ebb0e7cf68 Merge pull request #863 from lanedirt/846-add-native-android-app
Add native Android app
2025-05-30 18:03:42 +02:00
Leendert de Borst
4603051a91 Build and push docker images even if other optional steps fail (#846) 2025-05-30 18:01:26 +02:00
Leendert de Borst
f66fb53706 Update mobile-app-build.yml (#846) 2025-05-30 17:54:45 +02:00
Leendert de Borst
b603160d99 Add autofill screenshots to Android docs (#846) 2025-05-30 16:42:11 +02:00
Leendert de Borst
096b0277f3 Update mobile-app-build.yml (#846) 2025-05-30 15:52:05 +02:00
Leendert de Borst
f271040ff4 Improve android autofill settings open, bump version (#846) 2025-05-30 15:35:41 +02:00
Leendert de Borst
f313950112 Make safari extension project version the same for all projects (#846) 2025-05-30 15:11:24 +02:00
Leendert de Borst
ef1ad127e3 Update mobile-app-build.yml (#846) 2025-05-30 15:06:33 +02:00
Leendert de Borst
cac691a43d Delete lowercase duplicate validationSchema.ts (#846) 2025-05-30 13:50:36 +02:00
Leendert de Borst
4efe201224 Add iOS app build (#846) 2025-05-30 13:37:14 +02:00
Leendert de Borst
ca477c310c Make android app signed build manual dispatch (#846) 2025-05-30 12:40:21 +02:00
Leendert de Borst
77189373ba Add signed android app build (#846) 2025-05-30 11:57:36 +02:00
Leendert de Borst
1aaa5c2d55 Update mobile-app-build.yml (#846) 2025-05-30 11:04:51 +02:00
Leendert de Borst
163e5c51c2 Merge branch '846-add-native-android-app' of https://github.com/lanedirt/AliasVault into 846-add-native-android-app
* '846-add-native-android-app' of https://github.com/lanedirt/AliasVault:
  Make unit tests work from CLI (#846)
2025-05-30 10:58:59 +02:00
Leendert de Borst
29895f375f Split tasks in mobile-app-build.yml (#846) 2025-05-30 10:58:56 +02:00
Leendert de Borst
2803dcf02c Add bump-version.sh script (#846) 2025-05-30 10:56:32 +02:00
Leendert de Borst
a8e075d932 Update version to be equal for all subprojects (#846) 2025-05-30 09:59:30 +02:00
Leendert de Borst
49ba704135 Update docs (#846) 2025-05-30 09:38:59 +02:00
Leendert de Borst
9669307480 Make unit tests work from CLI (#846) 2025-05-29 21:22:09 +02:00
Leendert de Borst
343ced5b38 Make unit tests work from CLI (#846) 2025-05-29 21:08:38 +02:00
Leendert de Borst
8f66670804 Update mobile-app-build.yml (#846) 2025-05-29 20:12:33 +02:00
Leendert de Borst
c2d1fcfcd4 Update linting (#846) 2025-05-29 20:01:49 +02:00
Leendert de Borst
e5a340b67d Add android build to workflow (#846) 2025-05-29 20:00:48 +02:00
Leendert de Borst
6a0e8909a8 Refactor default auth method setting to be part of login flow (#846) 2025-05-29 18:40:10 +02:00
Leendert de Borst
5a90b4271c Fix android crash on back button (#846) 2025-05-29 18:39:41 +02:00
Leendert de Borst
f0bd837d5e Improve security (#846) 2025-05-29 17:16:55 +02:00
Leendert de Borst
de45c286b1 Fix android header issues (#846) 2025-05-29 16:26:13 +02:00
Leendert de Borst
fac0fd5f32 Add android edge-to-edge module to fix menu bar height issues (#846) 2025-05-29 16:05:42 +02:00
Leendert de Borst
5a8b6b7f29 Refactor android to satisfy linting rules (#846) 2025-05-29 13:48:19 +02:00
Leendert de Borst
c864bfcab5 Npx expo-doctor fixes (#846) 2025-05-28 20:23:02 +02:00
Leendert de Borst
c9c692ce6e Add detekt.yml for kotlin code style analysis (#846) 2025-05-28 20:20:07 +02:00
Leendert de Borst
a640e4d280 Update kotlin linting settings (#846) 2025-05-28 19:55:17 +02:00
Leendert de Borst
2f03db7951 Remove unnecessary call (#846) 2025-05-28 19:26:32 +02:00
Leendert de Borst
9e5b733c8a Update logo icons (#846) 2025-05-28 18:44:45 +02:00
Leendert de Borst
09c380afdd Rebuild Android via npx expo rebuild (#846) 2025-05-28 18:04:39 +02:00
Leendert de Borst
7d9cc6118e Rebuild iOS via npx expo prebuild to standardize (#846) 2025-05-28 17:17:49 +02:00
Leendert de Borst
c7ab42e9f2 Add android linting checks and integrate in build process (#846) 2025-05-28 16:43:05 +02:00
Leendert de Borst
1b07c5de9f Update Android UI (#846) 2025-05-28 16:00:49 +02:00
Leendert de Borst
84df5b7d98 Add native settings page open callback for android (#846) 2025-05-28 15:32:27 +02:00
Leendert de Borst
347721a575 Update docs (#846) 2025-05-28 13:51:10 +02:00
Leendert de Borst
463c31641d Make system bar transparent on android (#846) 2025-05-28 13:30:04 +02:00
Leendert de Borst
67759a814e Linting fixes (#846) 2025-05-28 12:33:46 +02:00
Leendert de Borst
763a859e22 Update UI margins to work with Android and iOS (#846) 2025-05-28 12:32:21 +02:00
Leendert de Borst
d7db5a4e76 Refactor UrlUtility to be app-specific (#846) 2025-05-28 10:37:44 +02:00
Leendert de Borst
85bb5cf944 Optimize create new credential for Android (#846) 2025-05-28 10:30:07 +02:00
Leendert de Borst
cdc59e43a9 Update android-autofill.tsx (#846) 2025-05-28 09:19:46 +02:00
Leendert de Borst
9d0a003b2d Refactor (#846) 2025-05-27 17:16:12 +02:00
Leendert de Borst
e430ae9f4f Refactor FieldFinder to separate file (#846) 2025-05-27 16:58:48 +02:00
Leendert de Borst
41ba1260d7 Add SVG icon support (#846) 2025-05-27 16:49:44 +02:00
Leendert de Borst
c7572ac3f7 Fix issue where open app was not displayed always (#846) 2025-05-27 16:32:45 +02:00
Leendert de Borst
fe5c50b3c4 Add vault locked notice (#846) 2025-05-27 15:53:48 +02:00
Leendert de Borst
2a8ed28ff9 Improve password field type detection (#846) 2025-05-27 15:35:21 +02:00
Leendert de Borst
f6764b2f33 Simplify logic (#846) 2025-05-27 15:16:40 +02:00
Leendert de Borst
1afa153381 Improve field type detection (#846) 2025-05-27 14:52:09 +02:00
Leendert de Borst
ac59273161 Trigger on both password and likely username fields (#846) 2025-05-27 13:55:00 +02:00
Leendert de Borst
551fc42de1 Show service logo if it has one in autofill suggestion (#846) 2025-05-27 13:50:46 +02:00
Leendert de Borst
4b844189bc Add aliasvault logo to autofill list item (#846) 2025-05-27 13:05:39 +02:00
Leendert de Borst
5c277e747f Refactor FieldFinder (#846) 2025-05-27 12:00:49 +02:00
Leendert de Borst
8cbd275134 Improve credential matching (#846) 2025-05-27 11:21:27 +02:00
Leendert de Borst
765625b163 Add credentialmatcher and autofill test scaffolding (#846) 2025-05-26 20:16:28 +02:00
Leendert de Borst
b3df153128 Remove obsolete sharedcredentialstore (#846) 2025-05-26 20:15:57 +02:00
Leendert de Borst
604cffc622 Add autofill docs (#846) 2025-05-26 19:34:25 +02:00
Leendert de Borst
3b114445a3 Add android docs (#846) 2025-05-26 19:34:17 +02:00
Leendert de Borst
e8942c9833 Make basic autofill dropdown work in chrome (#846) 2025-05-26 14:46:50 +02:00
Leendert de Borst
b1da32ceae Add inline suggestion flag (#846) 2025-05-26 13:12:35 +02:00
Leendert de Borst
ef58217ed3 Update autocomplete logic to only trigger for username or password fields (#846) 2025-05-26 12:07:36 +02:00
Leendert de Borst
e0dd04263c Refactor AutofillService to use VaultStore (#846) 2025-05-26 11:53:26 +02:00
Leendert de Borst
29c52c844f Add vaultstore generic instance for sharing main app and autofill component (#846) 2025-05-26 11:41:01 +02:00
Leendert de Borst
b99025c48a Remove deprecated files (#846) 2025-05-26 11:40:02 +02:00
Leendert de Borst
8ba8eb684e Add android autofill instructions page (#846) 2025-05-26 09:49:40 +02:00
Leendert de Borst
b736edbb68 Update skeleton loader color for light mode (#846) 2025-05-25 12:16:52 +02:00
Leendert de Borst
1fa0d275cc Update search input style (#846) 2025-05-24 19:21:52 +02:00
Leendert de Borst
4a05cd00e3 Fix add-edit on Android (#846) 2025-05-23 16:50:17 +02:00
Leendert de Borst
574b5ff693 Add generic ThemedContainer component (#846) 2025-05-23 16:32:35 +02:00
Leendert de Borst
e6b7d1afa1 Display add button on android (#846) 2025-05-23 15:44:56 +02:00
Leendert de Borst
cbe224385d Refactor function naming (#846) 2025-05-23 15:08:07 +02:00
Leendert de Borst
adb2f9a3d6 Add Android specific header style (#846) 2025-05-23 14:05:55 +02:00
Leendert de Borst
6790391d37 Use Base64.NO_WRAP for android to be compatible with other RFC 4648 clients (#846) 2025-05-23 12:35:14 +02:00
Leendert de Borst
2a7855e1dc Refactor (#846) 2025-05-23 11:50:07 +02:00
Leendert de Borst
f3e47d7e67 Add autolock timer to Android logic (#846) 2025-05-22 18:09:17 +02:00
Leendert de Borst
bc76e85a9c Update function naming (#846) 2025-05-22 16:54:37 +02:00
Leendert de Borst
890025cd49 Allow PIN fallback on Android unlock flow (#846) 2025-05-22 13:41:08 +02:00
Leendert de Borst
1868370d8f Make basic biometric keystore flow work (#846) 2025-05-22 13:01:38 +02:00
Leendert de Borst
9a4fc7fb37 Update vault unlock page for android (#846) 2025-05-21 17:56:16 +02:00
Leendert de Borst
199fdebd5d Add KeystoreProvider scaffolding (#846) 2025-05-21 16:11:35 +02:00
Leendert de Borst
d5f17ef99c Add base64 conversion logic (#846) 2025-05-21 14:56:18 +02:00
Leendert de Borst
3b1e039d75 Implement commitTransaction (#846) 2025-05-21 14:05:55 +02:00
Leendert de Borst
01cdd28e32 Add .code-workspace to .vscode folder (#846) 2025-05-20 22:39:00 +02:00
Leendert de Borst
95a71f6ab2 Merge pull request #855 from lanedirt/854-prepare-0173-release
Prepare 0.17.3 release
2025-05-20 15:42:46 +02:00
Leendert de Borst
41cb92befd Merge branch 'main' into 854-prepare-0173-release 2025-05-20 15:42:32 +02:00
Leendert de Borst
2cfd1a922f Merge pull request #853 from lanedirt/852-bug-vault-import-fails-if-one-or-more-2fa-tokens-cannot-be-read
Vault import fails if one or more 2FA tokens cannot be parsed
2025-05-20 15:37:30 +02:00
Leendert de Borst
511ec31d17 Bump version to 0.17.3 (#854) 2025-05-20 15:31:22 +02:00
Leendert de Borst
080e505991 Merge branch '850-prepare-0172-release' into 854-prepare-0173-release
* 850-prepare-0172-release:
  Bump version to 0.17.2 (#850)
2025-05-20 15:29:11 +02:00
Leendert de Borst
461c1a042d Silently fail incorrect 2FA codes during import instead of throwing exception (#852) 2025-05-20 15:22:09 +02:00
Leendert de Borst
f30fcf4624 Make SQLite in-memory writable, add test to verify (#846) 2025-05-20 12:57:57 +02:00
Leendert de Borst
522eeefda4 Update docs (#846) 2025-05-20 12:19:22 +02:00
Leendert de Borst
94656c4d14 Update iOS podfile (#846) 2025-05-20 11:48:14 +02:00
Leendert de Borst
bbba8d1393 Make icon symbols generic between Android and iOS platforms (#846) 2025-05-20 11:47:48 +02:00
Leendert de Borst
680f5ba926 Proxy all calls from NativeVaultManager to VaultStore (#846) 2025-05-20 11:24:23 +02:00
Leendert de Borst
04d3f80019 Add getMetadata call (#846) 2025-05-20 11:06:14 +02:00
Leendert de Borst
a4d78cf7fc Make login and vault store/get flow work (#846) 2025-05-20 10:43:35 +02:00
Leendert de Borst
9713c8ed11 Implement getAllCredentials in kotlin, make all unit tests work (#846) 2025-05-19 10:04:39 +02:00
Leendert de Borst
2f4dbf34ba Update formatting (#846) 2025-05-19 10:04:10 +02:00
Leendert de Borst
232d110e49 Update license in index.template.html (#846) 2025-05-18 16:30:01 +02:00
Leendert de Borst
0af1507686 Implement basic vault decrypt/unlock flow (#846) 2025-05-18 16:18:27 +02:00
Leendert de Borst
e481769198 Add storage provider abstraction, move vaultstore its own namespace (#846) 2025-05-18 15:47:02 +02:00
Leendert de Borst
830c390b95 Update Android unit test docs (#846) 2025-05-18 13:51:02 +02:00
Leendert de Borst
c733a60571 Refactor query specific logic to VaultStore instead of NativeVaultManager (#846) 2025-05-18 13:45:22 +02:00
Leendert de Borst
d164d8e785 Merge pull request #851 from lanedirt/850-prepare-0172-release
Prepare 0.17.2 release
2025-05-17 17:41:30 +02:00
Leendert de Borst
79221f35c6 Bump version to 0.17.2 (#850) 2025-05-17 17:39:16 +02:00
Leendert de Borst
826bd23767 Restore docker-compose.yml container versions to :latest (#848) 2025-05-17 17:35:51 +02:00
Leendert de Borst
baf81392eb Restore docker-compose.yml container versions to :latest (#848) 2025-05-17 17:14:01 +02:00
Leendert de Borst
a70f6fca56 Add Android native vault manager unit test scaffolding (#846) 2025-05-17 12:00:12 +02:00
Leendert de Borst
1480fd88d1 Implement NativeVaultManager kotlin scaffolding (#846) 2025-05-17 11:04:22 +02:00
Leendert de Borst
11a5e10f4b Update comments (#846) 2025-05-17 11:00:39 +02:00
Leendert de Borst
eecf61b8b2 Fix packages to make android buildable (#846) 2025-05-16 17:46:55 +02:00
Leendert de Borst
6c620e34e6 Update docs (#846) 2025-05-16 17:14:05 +02:00
Leendert de Borst
aa99bbc111 Remove sqlite migration scripts (#494) 2025-05-15 16:37:39 +02:00
Leendert de Borst
e34b5f586c Remove SQLite server database implementation in code (#494) 2025-05-15 16:37:39 +02:00
Leendert de Borst
80c0992eb4 Update docs (#494) 2025-05-15 16:37:39 +02:00
Leendert de Borst
1fe7f7d8dc Bump version to 0.17.1 (#843) 2025-05-14 11:23:03 +02:00
Leendert de Borst
e41552a2c0 Fix credential edit password existence check (#840) 2025-05-14 11:05:45 +02:00
Leendert de Borst
8e9c100eac Fix browser extension popup manual search/filter bug (#839) 2025-05-14 11:05:36 +02:00
Leendert de Borst
a46bc9699f Bump version to 0.17.0 (#837) 2025-05-14 10:40:54 +02:00
Leendert de Borst
78cc9e7ec1 Simplify account creation validation (#837) 2025-05-14 10:40:54 +02:00
Leendert de Borst
5c8b093514 Add namespace (#827) 2025-05-14 09:21:24 +02:00
Leendert de Borst
39a3c84fb8 Make browser extension popup more reliable (#834) 2025-05-14 09:20:58 +02:00
Leendert de Borst
a1ec4fb7fd Merge pull request #836 from lanedirt/834-add-extra-enabledisable-setting-toggles-to-browser-extension
Add extra enable/disable setting toggles to browser extension
2025-05-14 00:21:06 +02:00
Leendert de Borst
183130fd3e Merge branch 'main' into 834-add-extra-enabledisable-setting-toggles-to-browser-extension 2025-05-14 00:12:55 +02:00
Leendert de Borst
b03a2f5da6 Make autofill popup show logic more robust (#834) 2025-05-14 00:07:23 +02:00
Leendert de Borst
531fb2486f Add shortcuts link to Chrome (#834) 2025-05-13 22:59:52 +02:00
Leendert de Borst
db825f415e Do not show site-specific autofill settings if globally disabled (#834) 2025-05-13 22:19:08 +02:00
Leendert de Borst
3d3522a262 Use shared date util for min date check in browser extension (#834) 2025-05-13 22:10:25 +02:00
Leendert de Borst
ff112aa5f9 Fix background script errors by moving constants to separate file (#834) 2025-05-13 20:31:32 +02:00
Leendert de Borst
a0aaa8ec1e Context menu refactor (#834) 2025-05-13 20:31:32 +02:00
Leendert de Borst
ead45c0282 Add dismiss for 1 hour option, add context menu disable option (#834) 2025-05-13 20:31:32 +02:00
Leendert de Borst
aa0a61ccf1 Fix background script errors by moving constants to separate file (#834) 2025-05-13 20:25:29 +02:00
Leendert de Borst
4fd2eb7034 Context menu refactor (#834) 2025-05-13 19:01:41 +02:00
Leendert de Borst
fa3f4e78cc Add dismiss for 1 hour option, add context menu disable option (#834) 2025-05-13 18:34:43 +02:00
Leendert de Borst
71a1c105bd Update eslint.config.js (#832) 2025-05-13 16:56:16 +02:00
Leendert de Borst
86fcc66a75 Upgrade WXT to latest stable version (#832) 2025-05-13 16:56:16 +02:00
Leendert de Borst
d29cb5e654 Update browser extension npm package-lock.json (#832) 2025-05-13 16:56:16 +02:00
Leendert de Borst
291d245e47 Update file headers in new files (#828) 2025-05-13 16:47:16 +02:00
Leendert de Borst
85528cdb79 Update CONTRIBUTING.md (#828) 2025-05-13 16:47:16 +02:00
Leendert de Borst
96fd9bb837 Update to AGPLv3 license (#828) 2025-05-13 16:47:16 +02:00
Leendert de Borst
4d1d982eeb Combine mobile app links with browser extension links (#827) 2025-05-13 15:35:24 +02:00
Leendert de Borst
bc7f2d9d3a Add mobile app download link constants (#827) 2025-05-13 15:35:24 +02:00
Leendert de Borst
7e86d0f99a Update sonarcloud-code-analysis.yml 2025-05-13 15:21:31 +02:00
Leendert de Borst
4efa91beeb Update app build version (#825) 2025-05-13 15:21:31 +02:00
Leendert de Borst
2d347dd435 Return explicit new revision number on password change (#825) 2025-05-13 15:21:31 +02:00
Leendert de Borst
56b9fee4fb Merge pull request #822 from lanedirt/771-add-native-ios-app
Add native iOS app
2025-05-13 14:51:51 +02:00
Leendert de Borst
0fca053dba Update README.md 2025-05-12 21:30:16 +02:00
Leendert de Borst
349a911eda Bump iOS build version (#771) 2025-05-11 18:20:58 +02:00
Leendert de Borst
ba373d5d0d Add confirm dialog to logout action (#771) 2025-05-11 18:20:47 +02:00
Leendert de Borst
3cde63c5e9 Only sync emails when component is visible (#771) 2025-05-11 18:14:06 +02:00
Leendert de Borst
f7f6873689 UI tweaks (#771) 2025-05-11 18:11:12 +02:00
Leendert de Borst
afa9ad8b2f Add delete account implementation (#771) 2025-05-11 16:59:09 +02:00
Leendert de Borst
822815080c Add vault password change logic to react native (#771) 2025-05-11 15:31:47 +02:00
Leendert de Borst
9a0a5f3bbf Set correct Argon2id defaults in Typescript (#771) 2025-05-11 15:30:09 +02:00
Leendert de Borst
819e00feaa Use correct default encryption type (#771) 2025-05-11 13:16:10 +02:00
Leendert de Borst
9a43e74c3f Implement active sessions and recent auth logs pages (#771) 2025-05-10 22:43:19 +02:00
Leendert de Borst
3958ce94c1 Add security settings nav scaffolding (#771) 2025-05-10 11:50:57 +02:00
Leendert de Borst
6714201057 Make notes section properly selectable (#771) 2025-05-10 11:00:55 +02:00
Leendert de Borst
64b3cd16e4 Update README.md
Update README.md

Update README.md

Update README.md
2025-05-10 10:54:34 +02:00
Leendert de Borst
bddc128202 Fix username input (#771) 2025-05-09 18:28:06 +02:00
Leendert de Borst
e2e0413912 Add native InAppBrowserView component for handling external links (#771) 2025-05-09 08:57:17 +02:00
Leendert de Borst
68a1cc0b4e Update README.md (#771) 2025-05-08 21:56:29 +02:00
Leendert de Borst
068041db70 Update mobile app version tags (#771) 2025-05-08 16:07:11 +02:00
Leendert de Borst
073510e58f Update dotnet-e2e-tests.yml (#771) 2025-05-08 15:21:22 +02:00
Leendert de Borst
c24fca5bfe Update version and release docs (#771) 2025-05-08 15:17:24 +02:00
Leendert de Borst
2b49834c83 Remove duplicate workspace (#771) 2025-05-08 15:04:33 +02:00
Leendert de Borst
ca37c62bab Add exempt flag to browser extension xcode (#771) 2025-05-08 15:03:44 +02:00
Leendert de Borst
0ed0f3ab47 Remove duplicate aliasvault lowercase folder (#771) 2025-05-08 15:02:21 +02:00
Leendert de Borst
bd07510251 Add exempt encryption flag (#771) 2025-05-08 15:01:44 +02:00
Leendert de Borst
1219d49fb4 Update offline mode checks (#771) 2025-05-08 14:35:20 +02:00
Leendert de Borst
928860bd38 Add server sanity checks to login flow (#771) 2025-05-08 14:32:00 +02:00
Leendert de Borst
7238792895 Responsive tweaks (#771) 2025-05-08 14:05:07 +02:00
Leendert de Borst
a22875081b Responsive tweaks (#771) 2025-05-08 12:31:43 +02:00
Leendert de Borst
2eb7bbf1b3 Update CollapsibleHeader margins (#771) 2025-05-08 12:11:46 +02:00
Leendert de Borst
da3515d44c Offline mode tweaks (#771) 2025-05-08 11:41:04 +02:00
Leendert de Borst
255ac518a2 Store key derivation params locally for offline password unlock (#771) 2025-05-08 11:35:40 +02:00
Leendert de Borst
05575bdc5f Tweak pull to refresh (#771) 2025-05-07 22:44:55 +02:00
Leendert de Borst
e0e7315d44 Fix unlock page flow (#771) 2025-05-07 22:44:45 +02:00
Leendert de Borst
4ed6b30256 Add read-only offline mode (#771) 2025-05-07 17:59:01 +02:00
Leendert de Borst
88a1491b83 Update UI margins (#771) 2025-05-07 09:06:56 +02:00
Leendert de Borst
6dc4a6e307 Update NPM packages (#771) 2025-05-07 09:06:47 +02:00
Leendert de Borst
e24b270610 Tweak email display (#771) 2025-05-06 23:09:03 +02:00
Leendert de Borst
8d6b04448f Update view margins for main tabs (#771) 2025-05-06 22:54:07 +02:00
Leendert de Borst
a1dd44d8fc Disable autocorrect and autocapitalize for login/register (#771) 2025-05-06 22:50:19 +02:00
Leendert de Borst
b705263144 Performance tweaks (#771) 2025-05-06 22:43:06 +02:00
Leendert de Borst
b0a40ef5ee Add inline skeleton loader (#771) 2025-05-06 22:12:24 +02:00
Leendert de Borst
e11c647f50 Add skeleton loading animations (#771) 2025-05-06 20:29:20 +02:00
Leendert de Borst
94c4783c01 Create separate add-edit page instead of modal for better deep link compatibility (#771) 2025-05-06 19:32:04 +02:00
Leendert de Borst
751903029c Refactor initial sync to happen outside of navigation context (#771) 2025-05-06 18:25:53 +02:00
Leendert de Borst
fb318c7669 Update login screen (#771) 2025-05-06 14:57:36 +02:00
Leendert de Borst
52340b8238 Update colors (#771) 2025-05-06 14:34:45 +02:00
Leendert de Borst
82423fffcb Add translucent headers (#771) 2025-05-06 14:22:22 +02:00
Leendert de Borst
e714d8563c UI tweaks (#771) 2025-05-06 13:05:38 +02:00
Leendert de Borst
e6ab1be77f Update light/dark mode styling (#771) 2025-05-05 22:23:00 +02:00
Leendert de Borst
0be6caf0bb Merge branch '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault into 771-add-native-ios-app
* '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault:
  Update light/dark mode styling (#771)
2025-05-05 22:16:40 +02:00
Leendert de Borst
51b03b5eaa Update iOS app download link (#771) 2025-05-05 22:16:23 +02:00
Leendert de Borst
5a019098e1 Update light/dark mode styling (#771) 2025-05-05 22:16:12 +02:00
Leendert de Borst
e992dea23e Update light/dark mode styling (#771) 2025-05-05 17:52:27 +02:00
Leendert de Borst
095a3f617e Update browser extension imports (#771) 2025-05-05 17:50:37 +02:00
Leendert de Borst
77a48f4235 Git check in with correct casing in mobile app dir (#771) 2025-05-05 15:33:00 +02:00
Leendert de Borst
af5a830759 Update docs (#771) 2025-05-05 15:07:08 +02:00
Leendert de Borst
af1a4b6930 Update package.json (#771) 2025-05-05 13:28:31 +02:00
Leendert de Borst
0fe969dfad Merge branch '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault into 771-add-native-ios-app
* '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault:
  Update README.md
2025-05-05 13:22:39 +02:00
Leendert de Borst
43e642116e Add passWithNoTests flag (#771) 2025-05-05 13:21:58 +02:00
Leendert de Borst
68da15603a Merge branch 'main' into 771-add-native-ios-app 2025-05-05 13:16:25 +02:00
Leendert de Borst
f5753f654c Update workflow names (#771) 2025-05-05 13:13:05 +02:00
Leendert de Borst
6f26649647 Update README.md badges 2025-05-05 13:12:48 +02:00
Leendert de Borst
20135afe4d Update mobile app build workflow (#771) 2025-05-05 13:04:12 +02:00
Leendert de Borst
19a38ba14f Update CredentialDetails.tsx (#771) 2025-05-05 12:40:11 +02:00
Leendert de Borst
eae70b41f1 Update GitHub workflows (#771) 2025-05-05 12:32:46 +02:00
Leendert de Borst
fb3530281f Update .gitignore (#771) 2025-05-05 12:08:18 +02:00
Leendert de Borst
df937cf039 Group related GitHub workflows (#771) 2025-05-05 12:08:11 +02:00
Leendert de Borst
f77a220096 Add shared lib build to github workflow (#771) 2025-05-05 12:07:23 +02:00
Leendert de Borst
93040e2377 Linting refactor (#771) 2025-05-05 11:51:10 +02:00
Leendert de Borst
d758de2674 Replace KeychainAccess with native iOS API for more control (#771) 2025-05-05 11:47:09 +02:00
Leendert de Borst
8bc1a49602 Add toast on vault unlock biometric enable (#771) 2025-05-05 11:40:07 +02:00
Leendert de Borst
38a69649af Update CredentialProviderView (#771) 2025-05-05 11:24:39 +02:00
Leendert de Borst
f3aa8fe085 Prompt for authentication and sanity checks before opening autofill view (#771( 2025-05-05 10:48:45 +02:00
Leendert de Borst
3066512341 Auto hide credential created confirm when app opens again (#771) 2025-05-05 09:34:28 +02:00
Leendert de Borst
af53abac0b Show loading state when deleting a credential (#771) 2025-05-05 09:02:46 +02:00
Leendert de Borst
5743056084 Fix mobile app credential creation (#771) 2025-05-05 09:01:32 +02:00
Leendert de Borst
7917270aee Update date parsing to use shared library (#771) 2025-05-05 09:01:01 +02:00
Leendert de Borst
533b4cf7a2 Update vault mutate private email filter (#771) 2025-05-05 08:22:12 +02:00
Leendert de Borst
9435c7e657 Cleanup xcode generated file headers (#771) 2025-05-04 22:42:01 +02:00
Leendert de Borst
30a6315e9e Refactor native iOS parts (#771) 2025-05-04 22:38:49 +02:00
Leendert de Borst
61692db40b Refactor VaultStoreKit and fix linting issues (#771) 2025-05-04 18:06:37 +02:00
Leendert de Borst
e7aae996d1 Refactor VaultStoreKit (#771) 2025-05-04 16:39:27 +02:00
Leendert de Borst
5e576b1ed2 Add swiftlint scaffolding (#771) 2025-05-04 16:39:15 +02:00
Leendert de Borst
51b1b383f9 Merge branch '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault into 771-add-native-ios-app
* '771-add-native-ios-app' of https://github.com/lanedirt/AliasVault:
  Linting refactor misc utilities (#771)
2025-05-04 16:37:55 +02:00
Leendert de Borst
76a5e1bae4 Fix notes mutation (#771) 2025-05-04 16:37:51 +02:00
Leendert de Borst
3429c34eb4 Linting refactor misc utilities (#771) 2025-05-04 15:58:06 +02:00
Leendert de Borst
d9942ea57c Linting refactor misc utilities (#771) 2025-05-04 15:54:55 +02:00
Leendert de Borst
738f228ea6 Linting refactor turbo native spec (#771) 2025-05-04 14:19:25 +02:00
Leendert de Borst
3318748853 Linting refactor hooks (#771) 2025-05-04 14:11:41 +02:00
Leendert de Borst
f06cf511eb Linting refactor context (#771) 2025-05-04 14:00:45 +02:00
Leendert de Borst
15d707615a Linting refactor components (#771) 2025-05-04 13:48:14 +02:00
Leendert de Borst
5e2bdc6861 Linting refactor (#771) 2025-05-04 12:11:05 +02:00
Leendert de Borst
6e7e985c26 Enable eslint for react native project (#771) 2025-05-03 19:46:25 +02:00
Leendert de Borst
d47852420a Cleanup xcscheme and refactor swift test setup (#771) 2025-05-03 19:32:32 +02:00
Leendert de Borst
1cc6567d6f Refactor storeEncryptedDatabase method to separate metadata set (#771) 2025-05-03 18:08:22 +02:00
Leendert de Borst
616705b80d Remove VaultUITests file as its not used (#771) 2025-05-03 10:04:25 +02:00
Leendert de Borst
198b532299 Refactor VaultStoreKit function naming (#771) 2025-05-03 09:59:10 +02:00
Leendert de Borst
c763b79c3c Extract service icons during both credential create and update (#771) 2025-05-03 08:51:06 +02:00
Leendert de Borst
043ce5c588 Explicitly navigate to show credential after creation/update (#771) 2025-05-03 08:39:06 +02:00
Leendert de Borst
e7e7bc5f87 Remove unused native add methods (#771) 2025-05-03 08:38:41 +02:00
Leendert de Borst
15f10788b1 Add dynamic public email domain check (#771) 2025-05-03 08:29:06 +02:00
Leendert de Borst
71530ef79d Enforce biometrics for native iOS autofill (#771) 2025-05-02 18:28:20 +02:00
Leendert de Borst
d6f7d8c039 Add context menu links to native autofill list (#771) 2025-05-02 16:56:46 +02:00
Leendert de Borst
1dfbd38586 Fix credential name focus and refactor (#771) 2025-05-02 16:25:14 +02:00
Leendert de Borst
f849b94d5d Update README.md 2025-05-02 14:48:57 +02:00
Leendert de Borst
06a5c50359 Add transaction commit to credential delete (#771) 2025-05-02 14:46:46 +02:00
Leendert de Borst
866dd1c919 Add loading overlay component (#771) 2025-05-02 14:01:40 +02:00
Leendert de Borst
3be65beb06 Add credential form validation (#771) 2025-05-02 13:22:15 +02:00
Leendert de Borst
8c06b46044 Add proper UX flow for identity and password generator into mobile app (#771) 2025-05-01 17:38:42 +02:00
Leendert de Borst
f8d5ae6107 Implement identity and password generator in mobile app (#771) 2025-05-01 13:03:06 +02:00
Leendert de Borst
b6b9e05a31 Update manual setup docs (#771) 2025-05-01 12:51:50 +02:00
Leendert de Borst
b80e47dab3 Copy shared modules to mobile app project (#771) 2025-05-01 11:19:50 +02:00
Leendert de Borst
d291ad5ba2 Update shared build script (#771) 2025-05-01 11:19:33 +02:00
Leendert de Borst
037e1902b1 Merge branch 'main' into 771-add-native-ios-app
* main:
  Bump version to 0.16.2 (#818)
  Bump vite
2025-05-01 11:11:15 +02:00
Leendert de Borst
18e6698f7d Fix linting (#771) 2025-04-30 23:32:47 +02:00
Leendert de Borst
b002b23078 Update file extensions (#771) 2025-04-30 23:22:47 +02:00
Leendert de Borst
1f2f53a124 Update shared package names (#771) 2025-04-30 23:19:24 +02:00
Leendert de Borst
46d56b06ed Remove deprecated identityGenDictLoader from browser extension (#771) 2025-04-30 23:19:12 +02:00
Leendert de Borst
4f6e419524 Implement new shared generator modules in browser extension (#771) 2025-04-30 23:11:42 +02:00
Leendert de Borst
7ae8655c2d Add build script for shared modules (#771) 2025-04-30 23:10:44 +02:00
Leendert de Borst
048e6f8d2a Rename packages to shared in root and to be standalone modules (#771) 2025-04-30 22:13:37 +02:00
Leendert de Borst
8498102076 Make shared packages buildable (#771) 2025-04-30 19:45:37 +02:00
Leendert de Borst
f2d78538a9 Refactor dictionaries from txt to .ts (#771) 2025-04-30 19:19:46 +02:00
Leendert de Borst
5babd0b982 Move all apps to subfolder in monorepo (#771) 2025-04-30 19:03:18 +02:00
Leendert de Borst
0f45f49ed1 Add shared packages folder (#771) 2025-04-30 17:08:48 +02:00
Leendert de Borst
699aa83c4e Merge branch 'main' into 771-add-native-ios-app
* main: (33 commits)
  Update date format in RecentEmails.razor (#815)
  Tweak Login.razor margins (#809)
  Refresh password salt and ephemeral after changing it  (#809)
  Add dashlane importer and unittest (#811)
  Bump the npm_and_yarn group across 1 directory with 2 updates
  Bump nokogiri in /docs in the bundler group across 1 directory
  Fix nullability warning in FaviconExtractor.cs (#805)
  Bump HtmlAgilityPack from 1.12.0 to 1.12.1
  Bump version to 0.16.1 (#803)
  Add form detector improvements and tests (#794)
  Add reliable click handler for all autofill popup elements (#797)
  Update Filter.ts (#801)
  Fix autofill popup z-index visibility (#801)
  Show email in credential list if username is empty (#801)
  Improve autofill matching (#801)
  Make browser extension autofill dismiss button more reliable (#797)
  Remove cancel for sonarcloud runner as it uses pull_request_target
  Simplify service name to a single input for both modes (#798)
  Show service name suggestions (#798)
  Add service name extraction unit tests (#798)
  ...
2025-04-30 17:04:32 +02:00
Leendert de Borst
7e3bb548e4 Refactor add-edit by wrapping vault mutate (#771) 2025-04-30 15:50:41 +02:00
Leendert de Borst
ae3ae6032f Add credential delete functionality (#771) 2025-04-30 14:57:46 +02:00
Leendert de Borst
96b7b4cc97 Add autofill credential create confirm screen to improve UX (#771) 2025-04-30 14:26:04 +02:00
Leendert de Borst
d95807711a Make toast a shared component (#771) 2025-04-30 14:25:34 +02:00
Leendert de Borst
1051ee653e Add favicon extraction to mobile app (#771) 2025-04-30 13:56:24 +02:00
Leendert de Borst
2fd117ae96 Make vault save to server flow work in app (#771) 2025-04-29 17:16:52 +02:00
Leendert de Borst
6c5247a4b0 Make local vault db persist and encryption flow work (#771) 2025-04-29 16:05:52 +02:00
Leendert de Borst
a46d1ca39e Add credential update scaffolding (#771) 2025-04-29 14:19:50 +02:00
Leendert de Borst
caef74477b Refactor nav structure, make credential create deep link work (#771) 2025-04-29 13:37:20 +02:00
Leendert de Borst
d554f0f3cc Make credential create work locally (#771) 2025-04-29 11:43:14 +02:00
Leendert de Borst
c0e1c75a1b Add credential AddEdit page scaffolding to app (#771) 2025-04-28 21:32:26 +02:00
Leendert de Borst
8d68d175bf Create new credential via main app link (#771) 2025-04-28 17:00:02 +02:00
Leendert de Borst
27dbd53786 Add "text to insert" method support (#771) 2025-04-28 16:39:18 +02:00
Leendert de Borst
a37052e4dc Add actionsheet when selecting autofill credential to choose username or email (#771) 2025-04-28 16:07:19 +02:00
Leendert de Borst
cd8b2deb04 Add encryption key retry to make it more robust (#771) 2025-04-28 16:06:51 +02:00
Leendert de Borst
16a858ee08 Add iOS suggestion credential insert scaffolding (#771) 2025-04-28 13:34:32 +02:00
Leendert de Borst
01f1cc8bc3 Auto filter native credentials list based on provided URL (#771) 2025-04-28 12:26:06 +02:00
Leendert de Borst
9f79c0cfeb Update credential cards for all clients to show email if username is empty (#771) 2025-04-28 12:08:46 +02:00
Leendert de Borst
65d3b1d94f Add iOS autofill tip to settings screen with simple state (#771) 2025-04-28 11:18:33 +02:00
Leendert de Borst
96d5606632 Update credential view search input style (#771) 2025-04-27 21:17:50 +02:00
Leendert de Borst
535de6b7b4 Refactor iOS app native logic to frameworks and make SwiftUI preview work (#771) 2025-04-27 21:01:42 +02:00
Leendert de Borst
a86896ee99 Make SVG work in autofill credential view (#771) 2025-04-27 15:19:49 +02:00
Leendert de Borst
51bb5b84bd Add default placeholder icon to swift view (#771) 2025-04-27 13:23:29 +02:00
Leendert de Borst
c005c0cdc0 Make logo SQLIte blob to Data() conversion work (#771) 2025-04-27 10:26:14 +02:00
Leendert de Borst
83f64c505e Update swift logic to test for specific credential values (#771) 2025-04-26 16:10:32 +02:00
Leendert de Borst
1c3627d985 Make autofill work in simulator and fix password get (#771) 2025-04-26 11:31:44 +02:00
Leendert de Borst
c05b436e3c Fix GetAllCredentials so unittest passes (#771) 2025-04-26 11:09:18 +02:00
Leendert de Borst
cb61b73553 Make db init test work (#771) 2025-04-25 21:08:37 +02:00
Leendert de Borst
51b3956caf Fix vault generator output by taking latest vault blob (#771) 2025-04-25 19:50:48 +02:00
Leendert de Borst
128826d0ec Add TestVaultGeneratorTests.cs (#771) 2025-04-25 17:39:50 +02:00
Leendert de Borst
605ac2e6d4 Add VaultStoreTests scaffolding which can run via xcode (#771) 2025-04-25 14:42:33 +02:00
Leendert de Borst
5a7e0cf9e5 Update VaultStore logic, getAllCredentials currently breaks (#771) 2025-04-25 14:13:42 +02:00
Leendert de Borst
0bd9b990b6 Make native autofill init db first (#771) 2025-04-25 13:14:16 +02:00
Leendert de Borst
a37d2d0934 Make 2FA input field render properly (#771) 2025-04-24 23:49:16 +02:00
Leendert de Borst
b747161b24 Rename SharedCredentialStore to VaultStore, add readmes (#771) 2025-04-24 23:36:46 +02:00
Leendert de Borst
ce6cdf1d9a Rename NativeCredentialManager to NativeVaultManager (#771) 2025-04-24 23:01:47 +02:00
Leendert de Borst
c94b5339b9 Refactor React Native bridge to Turbo Native Module (#771) 2025-04-24 22:33:13 +02:00
Leendert de Borst
9af505ffb3 Tweak login and unlock layout (#771) 2025-04-24 16:40:39 +02:00
Leendert de Borst
b1b4d7bff7 Make vault decryption catch and print errors (#771) 2025-04-24 16:37:15 +02:00
Leendert de Borst
d4a39caa22 Fix color scheme light mode flash when app comes to foreground (#771) 2025-04-24 16:00:39 +02:00
Leendert de Borst
cc1ca7f683 Make 2FA login flow show proper loading indicator (#771) 2025-04-24 15:03:07 +02:00
Leendert de Borst
4f3c436dd9 Tweak faceid enable/disable logic flow (#771) 2025-04-24 14:43:55 +02:00
Leendert de Borst
01adb3df07 Fix settings screen flicker issue (#771) 2025-04-24 13:50:10 +02:00
Leendert de Borst
038e22db62 Move enabled auth methods to native module and add conditional faceid check (#771) 2025-04-24 13:44:55 +02:00
Leendert de Borst
b0203882f3 Fix bug with birthdate parsing (#771) 2025-04-23 19:38:45 +02:00
Leendert de Borst
d88e0ca4cf Add two-factor auth viewer to mobile app (#771) 2025-04-23 19:20:55 +02:00
Leendert de Borst
1bf6a5dec3 Fix login flow (#771) 2025-04-23 18:57:08 +02:00
Leendert de Borst
951f80b4bb Tweak app timeout lock logic (#771) 2025-04-23 18:37:05 +02:00
Leendert de Borst
079415184f Store autolock timeout in native code (#771) 2025-04-23 17:21:03 +02:00
Leendert de Borst
db58bc8942 Refactor app index flow, add auto lock settings (#771) 2025-04-23 16:47:21 +02:00
Leendert de Borst
2a808fb137 Fix keychain accessibility issue (#771) 2025-04-23 14:15:46 +02:00
Leendert de Borst
b19ee32b28 Add settings page stack and add unlock method options (#771) 2025-04-23 13:53:17 +02:00
Leendert de Borst
31d3c1d1a2 Make manual unlock page with password unlock work (#771) 2025-04-23 11:43:48 +02:00
Leendert de Borst
70151f0587 Tweak collapsible header UI (#771) 2025-04-22 22:11:00 +02:00
Leendert de Borst
37f2f445c2 Add search icon to credential search input (#771) 2025-04-22 20:35:24 +02:00
Leendert de Borst
d88ecb5fec Make list content overflow under bottom nav bar (#771) 2025-04-22 20:21:28 +02:00
Leendert de Borst
91b223bac9 Add collapsible header component (#771) 2025-04-22 20:09:58 +02:00
Leendert de Borst
ecb2d29dcf Tweak index.tsx app startup and vault unlock flow (#771) 2025-04-22 17:33:06 +02:00
Leendert de Borst
470ffec21a Refresh email list on email delete (#771) 2025-04-22 15:58:10 +02:00
Leendert de Borst
30ac7b1f1d Make emailpreview auto refresh (#771) 2025-04-22 15:47:30 +02:00
Leendert de Borst
bef4ee7c03 Make credential service url clickable (#771) 2025-04-22 14:40:33 +02:00
Leendert de Borst
af3e455477 Add email preview component (#771) 2025-04-22 14:27:06 +02:00
Leendert de Borst
279a2f6bba Add scroll to top on email bottom nav bar click (#771) 2025-04-22 13:49:45 +02:00
Leendert de Borst
a04586c959 Add credential link to email details page (#771) 2025-04-22 13:44:39 +02:00
Leendert de Borst
26b63a282b Fix email navigation path (#771) 2025-04-22 13:08:52 +02:00
Leendert de Borst
0eb7e74eb6 Add pull to refresh to email page (#771) 2025-04-22 13:01:54 +02:00
Leendert de Borst
14b90fb047 Refactor email row to separate component (#771) 2025-04-22 12:56:46 +02:00
Leendert de Borst
4480eb897a Update emails nav structure (#771) 2025-04-22 12:36:47 +02:00
Leendert de Borst
38b3b242d9 Make RSA decryption for emails work in react native (#771) 2025-04-22 11:26:19 +02:00
Leendert de Borst
c2b313c272 Disable autocorrect in search field (#771) 2025-04-22 09:25:23 +02:00
Leendert de Borst
758597353f Remove haptic touch from bottom nav bar (#771) 2025-04-20 22:02:17 +02:00
Leendert de Borst
f720c46958 Tweak login flow (#771) 2025-04-20 21:10:48 +02:00
Leendert de Borst
b9c0f23e45 Add empty field sanity check to login (#771) 2025-04-20 20:48:27 +02:00
Leendert de Borst
13fcadb2fa Add AliasVault themed loading indicators (#771) 2025-04-20 19:46:47 +02:00
Leendert de Borst
6c7f5f2e02 Add login API URL settings page (#771) 2025-04-20 18:16:46 +02:00
Leendert de Borst
dd01ada1b7 Add TitleContainer component with SVG logo (#771) 2025-04-20 16:58:55 +02:00
Leendert de Borst
abfaefa289 Add credential search reset button (#771) 2025-04-19 12:22:55 +02:00
Leendert de Borst
f40ba50607 Reset credentials page when tapping on tabbar item again (#771) 2025-04-19 11:58:02 +02:00
Leendert de Borst
0a19318e2d Add CredentialCard component (#771) 2025-04-19 11:13:57 +02:00
Leendert de Borst
2e5107098e Add app version to settings page (#771) 2025-04-18 17:43:32 +02:00
Leendert de Borst
ba4eea2dc8 Make notes component detect links and show on top (#771) 2025-04-18 14:09:44 +02:00
Leendert de Borst
87b1d49544 Refactor credential detail component structure (#771) 2025-04-18 13:49:10 +02:00
Leendert de Borst
355f198293 Make credential card show username/email uniformly in all clients (#771) 2025-04-18 13:24:18 +02:00
Leendert de Borst
9e1eb67ae8 Add URL to credential search filter logic (#771) 2025-04-18 13:17:08 +02:00
Leendert de Borst
8dcacd2ea4 Refactor color scheme usage (#771) 2025-04-18 09:27:59 +02:00
Leendert de Borst
0eb6a501af Update credential index layout (#771) 2025-04-18 08:57:36 +02:00
Leendert de Borst
05dae94336 Use generic colors in credential list (#771) 2025-04-18 08:52:22 +02:00
Leendert de Borst
2e7fde861e Add generic color definition (#771) 2025-04-18 00:13:32 +02:00
Leendert de Borst
c1ba8217fb Add separate index page that calls init logic (#771) 2025-04-17 18:40:45 +02:00
Leendert de Borst
39f8157683 Refactor get metadata call (#771) 2025-04-16 22:09:49 +02:00
Leendert de Borst
c0b0c97106 Persist vault metadata via react native bridge (#771) 2025-04-16 21:55:10 +02:00
Leendert de Borst
984adce3d4 Fix typo (#771) 2025-04-16 21:02:06 +02:00
Leendert de Borst
98dead8c0a Add vault update check to main layout scaffolding (#771) 2025-04-16 15:42:02 +02:00
Leendert de Borst
e771af4a7a Add pull to refresh to credential list (#771) 2025-04-16 08:13:43 +02:00
Leendert de Borst
ba5f81ee86 Show icon in react native app (#771) 2025-04-14 22:17:21 +02:00
Leendert de Borst
3a8d08c53b Make credential tap work if keyboard is up (#771) 2025-04-13 14:12:11 +02:00
Leendert de Borst
8b9a60689a Add ThemedSafeAreaView component (#771) 2025-04-13 13:52:57 +02:00
Leendert de Borst
8bb6d04038 Update index.tsx header (#771) 2025-04-13 10:10:55 +02:00
Leendert de Borst
8455d6be88 Add credential search (#771) 2025-04-12 18:55:04 +02:00
Leendert de Borst
4d72c74731 Update theme (#771) 2025-04-12 18:31:13 +02:00
Leendert de Borst
f76a396e6d Add toast message on copy (#771) 2025-04-12 18:16:17 +02:00
Leendert de Borst
5ae6e069ab Add credentials detail page (#771) 2025-04-12 18:01:27 +02:00
Leendert de Borst
a73d6adad5 Update main nav and cleanup (#771)
Update main nav (#771)

Make autofill filter work (#771)

Add isVaultInitialized native func (#771)

Cleanup (#771)
2025-04-12 17:59:18 +02:00
Leendert de Borst
461f765395 Make autofill work with new sqlite client (#771) 2025-04-11 22:18:03 +02:00
Leendert de Borst
f8b5f4a3dd Fix Swift base64 sqlite decryption flow (#771) 2025-04-11 21:00:01 +02:00
Leendert de Borst
55648e95b4 Add native swift SQLite implementation (#771) 2025-04-11 18:09:55 +02:00
Leendert de Borst
1c75782e46 Fix 2FA code entry (#771) 2025-04-11 17:00:10 +02:00
Leendert de Borst
098cf162fd Add deploy to phone task (#771) 2025-04-11 16:50:05 +02:00
Leendert de Borst
653ea2b657 Show credentials when logged in (#771) 2025-04-11 16:41:42 +02:00
Leendert de Borst
eda0fb4d4e Make sqlite work (#771) 2025-04-11 16:41:15 +02:00
Leendert de Borst
baf1f24379 Implement react-native AES-GCM-256 decryption (#771) 2025-04-11 15:24:02 +02:00
Leendert de Borst
cfcce0ec3e Add native react argon2id implementation, add SRP polyfill (#771) 2025-04-11 14:32:43 +02:00
Leendert de Borst
1b70c0c727 Add scaffolding for react native based on browser extension react (#771) 2025-04-11 13:48:46 +02:00
Leendert de Borst
0065d4be10 Update iOS scheme name (#771) 2025-04-11 12:56:18 +02:00
Leendert de Borst
27e279e812 Update android logo and splashscreen (#771) 2025-04-10 17:03:08 +02:00
Leendert de Borst
568f2c61af Refactor xcode project name conventions (#771) 2025-04-10 15:51:53 +02:00
Leendert de Borst
d1b3e97c6b Port java to kotlin (#771) 2025-04-10 14:37:46 +02:00
Leendert de Borst
87a6807d04 Make basic autofill implementation work for FF in Android (#771) 2025-04-10 13:26:04 +02:00
Leendert de Borst
77a14bedcd Make AutofillService mock implementation (#711) 2025-04-10 13:12:40 +02:00
Leendert de Borst
59fc34a09e Add Android autofill service declaration (#771) 2025-04-10 12:47:33 +02:00
Leendert de Borst
9dfca63703 Only trigger encrypt key retrieval if there are encrypted contents (#771) 2025-04-10 12:25:44 +02:00
Leendert de Borst
7e7d86be25 Generate random encryption key and persist in keyvault (#771) 2025-04-10 12:20:37 +02:00
Leendert de Borst
15b44e4575 Add basic encrypt/decrypt with IV part of encrypted blob (#771) 2025-04-10 12:04:56 +02:00
Leendert de Borst
c87a1cc37a Revert SharedCredentialStore.java to simple starting point (#771) 2025-04-10 11:56:33 +02:00
Leendert de Borst
4b828002ec Refactor SharedCredentialStore.java (#771) 2025-04-09 23:31:53 +02:00
Leendert de Borst
46d9709dcf Make biometric auth protection work in Android (#771) 2025-04-09 22:51:00 +02:00
Leendert de Borst
ba02533b5c Make android app buildable (#771) 2025-04-09 22:41:41 +02:00
Leendert de Borst
a99fa1215e Update splash screen logo (#771) 2025-04-09 18:54:10 +02:00
Leendert de Borst
e8a077041b Add app icons (#771) 2025-04-09 18:25:22 +02:00
Leendert de Borst
82304029bf Convert UIKit to SwiftUI (#771) 2025-04-09 17:55:09 +02:00
Leendert de Borst
ae5b4e070f Remove storyboard from extension (#771) 2025-04-09 17:23:20 +02:00
Leendert de Borst
e6149a8936 Add more scaffolding (#771) 2025-04-09 15:49:37 +02:00
Leendert de Borst
de6a46fb8e Add keychain access protected storage to iOS app (#771) 2025-04-09 12:39:45 +02:00
Leendert de Borst
dc0ee00084 Refresh credential list on focus (#771) 2025-04-08 17:23:33 +02:00
Leendert de Borst
8a1154522e Show saved credentials in react native (#771) 2025-04-08 17:19:58 +02:00
Leendert de Borst
a6c4d1e8a5 Make credentialmanager work from react native (#771) 2025-04-08 17:05:24 +02:00
Leendert de Borst
1652a41840 Add example credential add to extension (#771) 2025-04-08 16:55:11 +02:00
Leendert de Borst
03772511bb Enable autofill extension to show up (#771) 2025-04-08 16:23:02 +02:00
Leendert de Borst
dd3a41735f Update bundle identifier and add app group (#771) 2025-04-08 14:09:53 +02:00
Leendert de Borst
5cfa1e2b13 Update app.json (#771) 2025-04-08 13:57:10 +02:00
Leendert de Borst
d661b3961b Add iOS app run task (#771) 2025-04-08 13:48:57 +02:00
Leendert de Borst
9b0661f0de Add react native expo scaffolding (#771) 2025-04-08 13:44:44 +02:00
1925 changed files with 162600 additions and 41670 deletions

View File

@@ -14,9 +14,9 @@
# Docker containers to apply the changes.
# ----------------------------------------------------------------------------
# Set the ports that your AliasVault will be accessible at.
# These are the default ports that will be used by the `reverse-proxy` and `smtp` containers.
# You can change these to any other ports that are available on your system.
# Configure the network ports used by AliasVault by the `reverse-proxy` and `smtp` containers.
# You can change these if the defaults are in use on your system.
# After making changes, re-run the install script to apply them.
HTTP_PORT=80
HTTPS_PORT=443
SMTP_PORT=25

View File

@@ -0,0 +1,132 @@
name: "Build Android App"
description: "Builds Android APK/AAB, optionally signs and uploads to GitHub Release"
inputs:
run_tests:
description: "Whether to run Android unit tests"
required: false
default: "false"
signed:
description: "Whether to sign the Android build"
required: false
default: "false"
upload_to_release:
description: "Whether to upload the APK to GitHub Release"
required: false
default: "false"
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/mobile-app/package-lock.json
- name: Install dependencies
run: npm ci
shell: bash
working-directory: apps/mobile-app
- name: Extract version
run: |
VERSION=$(node -p "require('./app.json').expo.version")
echo "VERSION=$VERSION" >> $GITHUB_ENV
shell: bash
working-directory: apps/mobile-app
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build JS bundle (Expo)
run: |
mkdir -p build
npx expo export \
--dev \
--output-dir ./build \
--platform android
shell: bash
working-directory: apps/mobile-app
- name: Run Android Unit Tests
if: ${{ inputs.run_tests == 'true' }}
run: |
cd android
./gradlew :app:testDebugUnitTest --tests "net.aliasvault.app.*"
shell: bash
working-directory: apps/mobile-app
- name: Upload Android Test Reports
if: ${{ inputs.run_tests == 'true' }}
uses: actions/upload-artifact@v4
with:
name: android-test-reports
path: apps/mobile-app/android/app/build/reports/tests/testDebugUnitTest/
retention-days: 7
- name: Decode keystore
if: ${{ inputs.signed == 'true' }}
run: echo "${{ env.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
shell: bash
working-directory: apps/mobile-app
- name: Configure signing
if: ${{ inputs.signed == 'true' }}
run: |
cat >> android/gradle.properties <<EOF
ALIASVAULT_UPLOAD_STORE_FILE=keystore.jks
ALIASVAULT_UPLOAD_KEY_ALIAS=${{ env.ANDROID_KEY_ALIAS }}
ALIASVAULT_UPLOAD_STORE_PASSWORD=${{ env.ANDROID_KEYSTORE_PASSWORD }}
ALIASVAULT_UPLOAD_KEY_PASSWORD=${{ env.ANDROID_KEY_PASSWORD }}
EOF
shell: bash
working-directory: apps/mobile-app
- name: Build APK & AAB (Release only if signed)
if: ${{ inputs.signed == 'true' }}
run: |
cd android
./gradlew bundleRelease
./gradlew assembleRelease
shell: bash
working-directory: apps/mobile-app
- name: Rename APK and AAB files
if: ${{ inputs.signed == 'true' }}
run: |
mv android/app/build/outputs/apk/release/app-release.apk android/app/build/outputs/apk/release/aliasvault-${VERSION}-android.apk
mv android/app/build/outputs/bundle/release/app-release.aab android/app/build/outputs/bundle/release/aliasvault-${VERSION}-android.aab
shell: bash
working-directory: apps/mobile-app
- name: Upload AAB as artifact
if: ${{ inputs.signed == 'true' }}
uses: actions/upload-artifact@v4
with:
name: aliasvault-${{ env.VERSION }}-android.aab
path: apps/mobile-app/android/app/build/outputs/bundle/release/aliasvault-${{ env.VERSION }}-android.aab
retention-days: 14
- name: Upload APK as artifact
if: ${{ inputs.signed == 'true' }}
uses: actions/upload-artifact@v4
with:
name: aliasvault-${{ env.VERSION }}-android.apk
path: apps/mobile-app/android/app/build/outputs/apk/release/aliasvault-${{ env.VERSION }}-android.apk
retention-days: 14
- name: Upload APK to release
if: ${{ inputs.upload_to_release == 'true' }}
uses: softprops/action-gh-release@v2
with:
files: apps/mobile-app/android/app/build/outputs/apk/release/aliasvault-${{ env.VERSION }}-android.apk
env:
GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}

View File

@@ -0,0 +1,104 @@
name: "Build Browser Extension"
description: "Builds, tests, lints, zips, and optionally uploads a browser extension"
inputs:
browser:
description: "Target browser to build for (chrome, firefox, edge)"
required: true
upload_to_release:
description: "Whether to upload the resulting zip to GitHub Release"
required: false
default: "false"
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/browser-extension/package-lock.json
- name: Install dependencies
run: npm ci
shell: bash
working-directory: apps/browser-extension
- name: Build extension
run: npm run build:${{ inputs.browser }}
shell: bash
working-directory: apps/browser-extension
- name: Run tests
run: npm run test
shell: bash
working-directory: apps/browser-extension
- name: Run linting
run: npm run lint
shell: bash
working-directory: apps/browser-extension
- name: Zip Extension
run: npm run zip:${{ inputs.browser }}
shell: bash
working-directory: apps/browser-extension
- name: Extract version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "VERSION=$VERSION" >> $GITHUB_ENV
shell: bash
working-directory: apps/browser-extension
- name: Unzip extension
run: |
mkdir -p dist/${{ inputs.browser }}-unpacked
unzip dist/aliasvault-browser-extension-${{ env.VERSION }}-${{ inputs.browser }}.zip -d dist/${{ inputs.browser }}-unpacked
shell: bash
working-directory: apps/browser-extension
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: aliasvault-${{ env.VERSION }}-${{ inputs.browser }}
path: apps/browser-extension/dist/${{ inputs.browser }}-unpacked
- name: Unzip and upload Firefox sources
if: ${{ inputs.browser == 'firefox' }}
run: |
mkdir -p dist/sources-unpacked
unzip dist/aliasvault-browser-extension-${{ env.VERSION }}-sources.zip -d dist/sources-unpacked
shell: bash
working-directory: apps/browser-extension
- name: Upload Firefox sources artifact
if: ${{ inputs.browser == 'firefox' }}
uses: actions/upload-artifact@v4
with:
name: aliasvault-${{ env.VERSION }}-browser-extension-sources
path: apps/browser-extension/dist/sources-unpacked
- name: Rename zip files
run: |
mv apps/browser-extension/dist/aliasvault-browser-extension-${{ env.VERSION }}-${{ inputs.browser }}.zip apps/browser-extension/dist/aliasvault-${{ env.VERSION }}-${{ inputs.browser }}.zip
if [ -f apps/browser-extension/dist/aliasvault-browser-extension-${{ env.VERSION }}-sources.zip ]; then
mv apps/browser-extension/dist/aliasvault-browser-extension-${{ env.VERSION }}-sources.zip apps/browser-extension/dist/aliasvault-${{ env.VERSION }}-browser-extension-sources.zip
fi
shell: bash
- name: Upload to GitHub Release
if: ${{ inputs.upload_to_release == 'true' }}
uses: softprops/action-gh-release@v1
with:
files: |
apps/browser-extension/dist/aliasvault-${{ env.VERSION }}-${{ inputs.browser }}.zip
env:
GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}
- name: Upload Firefox sources to Release
if: ${{ inputs.upload_to_release == 'true' && inputs.browser == 'firefox' }}
uses: softprops/action-gh-release@v1
with:
files: apps/browser-extension/dist/aliasvault-${{ env.VERSION }}-browser-extension-sources.zip
env:
GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}

118
.github/actions/build-ios-app/action.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: "Build iOS App"
description: "Builds iOS App, optionally signs and uploads to App Store Connect"
inputs:
run_tests:
description: "Whether to run iOS unit tests"
required: false
default: "false"
signed:
description: "Whether to sign the iOS build"
required: false
default: "false"
upload_to_app_store_connect:
description: "Whether to upload the iOS App to App Store Connect"
required: false
default: "false"
runs:
using: "composite"
steps:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/mobile-app/package-lock.json
- name: Install dependencies
run: npm ci
shell: bash
working-directory: apps/mobile-app
- name: Extract version
run: |
VERSION=$(node -p "require('./app.json').expo.version")
echo "VERSION=$VERSION" >> $GITHUB_ENV
shell: bash
working-directory: apps/mobile-app
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Install Fastlane
run: gem install fastlane
shell: bash
- name: Install CocoaPods
run: |
sudo gem install cocoapods
shell: bash
- name: Create ASC private key file
if: ${{ inputs.signed == 'true' }}
run: |
mkdir -p $RUNNER_TEMP/asc
echo "${{ env.ASC_PRIVATE_KEY_BASE64 }}" | base64 --decode > $RUNNER_TEMP/asc/AuthKey.p8
shell: bash
- name: Install CocoaPods
run: |
cd ios
pod install
shell: bash
working-directory: apps/mobile-app
- name: Build iOS IPA
if: ${{ inputs.signed == 'true' }}
env:
IDEFileSystemSynchronizedGroupsAreEnabled: NO
XCODE_WORKSPACE: AliasVault.xcworkspace
XCODE_SCHEME: AliasVault
XCODE_CONFIGURATION: Release
XCODE_ARCHIVE_PATH: AliasVault.xcarchive
XCODE_EXPORT_PATH: ./build
XCODE_SKIP_FILESYSTEM_SYNC: true
run: |
cd ios
xcodebuild clean -workspace "$XCODE_WORKSPACE" \
-scheme "$XCODE_SCHEME" \
-configuration "$XCODE_CONFIGURATION"
xcodebuild -workspace "$XCODE_WORKSPACE" \
-scheme "$XCODE_SCHEME" \
-configuration "$XCODE_CONFIGURATION" \
-archivePath "$XCODE_ARCHIVE_PATH" \
-destination 'generic/platform=iOS' \
-allowProvisioningUpdates \
-authenticationKeyPath $RUNNER_TEMP/asc/AuthKey.p8 \
-authenticationKeyID ${{ env.ASC_KEY_ID }} \
-authenticationKeyIssuerID ${{ env.ASC_ISSUER_ID }} \
archive
xcodebuild -exportArchive \
-archivePath "$XCODE_ARCHIVE_PATH" \
-exportOptionsPlist ../exportOptions.plist \
-exportPath "$XCODE_EXPORT_PATH"
shell: bash
working-directory: apps/mobile-app
- name: Upload IPA as artifact
if: ${{ inputs.signed == 'true' }}
uses: actions/upload-artifact@v4
with:
name: aliasvault-${{ env.VERSION }}-ios.ipa
path: apps/mobile-app/ios/build/AliasVault.ipa
retention-days: 14
- name: Upload to App Store Connect via Fastlane
if: ${{ inputs.upload_to_app_store_connect == 'true' }}
env:
ASC_KEY_ID: ${{ env.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ env.ASC_ISSUER_ID }}
run: |
cd apps/mobile-app/ios
fastlane pilot upload \
--ipa "./build/AliasVault.ipa" \
--api_key_path "$RUNNER_TEMP/asc/AuthKey.p8" \
--skip_waiting_for_build_processing true
shell: bash

View File

@@ -12,157 +12,76 @@ concurrency:
cancel-in-progress: true
jobs:
build-chrome-extension:
build-shared-libraries:
runs-on: ubuntu-latest
defaults:
run:
working-directory: browser-extension
steps:
- uses: actions/checkout@v4
- name: Get short SHA
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: browser-extension/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build extension
run: npm run build:chrome
- name: Run tests
run: npm run test
- name: Run linting
run: npm run lint
- name: Zip Chrome Extension
run: npm run zip:chrome
- name: Unzip for artifact
- name: Build and distribute shared libraries
run: |
mkdir -p dist/chrome-unpacked
unzip dist/aliasvault-browser-extension-*-chrome.zip -d dist/chrome-unpacked
cd ./shared
chmod +x build-and-distribute.sh
./build-and-distribute.sh
- name: Upload dist artifact Chrome
uses: actions/upload-artifact@v4
- name: Verify shared library distribution
run: |
# Check if files exist and were recently modified
TARGET_DIRS=(
"apps/browser-extension/src/utils/dist/shared/identity-generator"
"apps/browser-extension/src/utils/dist/shared/password-generator"
"apps/browser-extension/src/utils/dist/shared/models"
"apps/browser-extension/src/utils/dist/shared/vault-sql"
)
for dir in "${TARGET_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
echo "❌ Directory $dir does not exist"
exit 1
fi
# Check if files were modified in the last 5 minutes
find "$dir" -type f -mmin -5 | grep -q . || {
echo "❌ Files in $dir were not recently modified"
exit 1
}
done
echo "✅ Shared library distribution verified"
build-chrome-extension:
needs: build-shared-libraries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Chrome Extension
uses: ./.github/actions/build-browser-extension
with:
name: aliasvault-browser-extension-${{ github.event_name == 'release' && github.ref_name || (github.ref_name == 'main' && format('main-{0}', steps.vars.outputs.sha_short) || steps.vars.outputs.sha_short) }}-chrome
path: browser-extension/dist/chrome-unpacked
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
browser: chrome
build-firefox-extension:
needs: build-shared-libraries
runs-on: ubuntu-latest
defaults:
run:
working-directory: browser-extension
steps:
- uses: actions/checkout@v4
- name: Get short SHA
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Build Firefox Extension
uses: ./.github/actions/build-browser-extension
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: browser-extension/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build extension
run: npm run build:firefox
- name: Run tests
run: npm run test
- name: Run linting
run: npm run lint
- name: Zip Firefox Extension
run: npm run zip:firefox
- name: Unzip for artifact
run: |
mkdir -p dist/firefox-unpacked
unzip dist/aliasvault-browser-extension-*-firefox.zip -d dist/firefox-unpacked
mkdir -p dist/sources-unpacked
unzip dist/aliasvault-browser-extension-*-sources.zip -d dist/sources-unpacked
- name: Upload dist artifact Firefox
uses: actions/upload-artifact@v4
with:
name: aliasvault-browser-extension-${{ github.event_name == 'release' && github.ref_name || (github.ref_name == 'main' && format('main-{0}', steps.vars.outputs.sha_short) || steps.vars.outputs.sha_short) }}-firefox
path: browser-extension/dist/firefox-unpacked
- name: Upload dist artifact Firefox sources
uses: actions/upload-artifact@v4
with:
name: aliasvault-browser-extension-${{ github.event_name == 'release' && github.ref_name || (github.ref_name == 'main' && format('main-{0}', steps.vars.outputs.sha_short) || steps.vars.outputs.sha_short) }}-sources
path: browser-extension/dist/sources-unpacked
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
browser: firefox
build-edge-extension:
needs: build-shared-libraries
runs-on: ubuntu-latest
defaults:
run:
working-directory: browser-extension
steps:
- uses: actions/checkout@v4
- name: Get short SHA
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Build Edge Extension
uses: ./.github/actions/build-browser-extension
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: browser-extension/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build extension
run: npm run build:edge
- name: Run tests
run: npm run test
- name: Run linting
run: npm run lint
- name: Zip Edge Extension
run: npm run zip:edge
- name: Unzip for artifact
run: |
mkdir -p dist/edge-unpacked
unzip dist/aliasvault-browser-extension-*-edge.zip -d dist/edge-unpacked
- name: Upload dist artifact Edge
uses: actions/upload-artifact@v4
with:
name: aliasvault-browser-extension-${{ github.event_name == 'release' && github.ref_name || (github.ref_name == 'main' && format('main-{0}', steps.vars.outputs.sha_short) || steps.vars.outputs.sha_short) }}-edge
path: browser-extension/dist/edge-unpacked
outputs:
sha_short: ${{ steps.vars.outputs.sha_short }}
browser: edge

238
.github/workflows/docker-build.yml vendored Normal file
View File

@@ -0,0 +1,238 @@
name: Docker Pull and Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker-compose-pull:
name: Docker Compose Pull Test
runs-on: ubuntu-latest
services:
docker:
image: docker:26.0.0
options: --privileged
steps:
- name: Get repository and branch information
id: repo-info
run: |
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
echo "REPO_FULL_NAME=lanedirt/AliasVault" >> $GITHUB_ENV
echo "BRANCH_NAME=main" >> $GITHUB_ENV
else
echo "REPO_FULL_NAME=${GITHUB_REPOSITORY}" >> $GITHUB_ENV
echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
fi
- uses: actions/checkout@v2
- name: Check local docker-compose.yml for :latest tags
run: |
# Check for explicit version tags instead of :latest
if grep -E "ghcr\.io/lanedirt/aliasvault-[^:]+:[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml; then
echo "❌ Error: docker-compose.yml contains explicit version tags instead of :latest"
echo "Found the following explicit versions:"
grep -E "ghcr\.io/lanedirt/aliasvault-[^:]+:[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml
echo ""
echo "All AliasVault images in docker-compose.yml must use ':latest' tags, not explicit versions."
echo "Please update docker-compose.yml to use ':latest' for all AliasVault images."
exit 1
fi
echo "✅ docker-compose.yml correctly uses :latest tags for all AliasVault images"
- name: Download install script from current branch
run: |
INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/$REPO_FULL_NAME/$BRANCH_NAME/install.sh"
echo "Downloading install script from: $INSTALL_SCRIPT_URL"
curl -f -o install.sh "$INSTALL_SCRIPT_URL"
- name: Create .env file with custom SMTP port
run: echo "SMTP_PORT=2525" > .env
- name: Set permissions and run install.sh (install)
id: install_script
run: |
chmod +x install.sh
{
./install.sh install --verbose
exit_code=$?
if [ $exit_code -eq 2 ]; then
echo "skip_remaining=true" >> $GITHUB_OUTPUT
true
elif [ $exit_code -ne 0 ]; then
false
fi
} || {
if [ $exit_code -eq 2 ]; then
echo "skip_remaining=true" >> $GITHUB_OUTPUT
true
else
exit $exit_code
fi
}
- name: Run docker compose up
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: docker compose -f docker-compose.yml up -d
- name: Wait for services
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: sleep 10
- name: Test WASM App
if: ${{ !steps.install_script.outputs.skip_remaining }}
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 "WASM app failed with $http_code"
exit 1
fi
- name: Test WebApi
if: ${{ !steps.install_script.outputs.skip_remaining }}
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 "WebApi failed with $http_code"
exit 1
fi
- name: Test Admin App
if: ${{ !steps.install_script.outputs.skip_remaining }}
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 "Admin app failed with $http_code"
exit 1
fi
- name: Test SMTP
if: ${{ !steps.install_script.outputs.skip_remaining }}
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 "SMTP failed"
exit 1
fi
- name: Test reset-admin-password output
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: |
output=$(./install.sh reset-admin-password | sed 's/\x1b\[[0-9;]*m//g')
if ! echo "$output" | grep -Eq '^\s*Password: [A-Za-z0-9+/=]{8,}'; then
echo "Invalid reset-admin-password output"
exit 1
fi
docker-compose-build:
name: Docker Compose Build Test
runs-on: ubuntu-latest
services:
docker:
image: docker:26.0.0
options: --privileged
steps:
- uses: actions/checkout@v2
- name: Check local docker-compose.yml for :latest tags
run: |
# Check for explicit version tags instead of :latest
if grep -E "ghcr\.io/lanedirt/aliasvault-[^:]+:[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml; then
echo "❌ Error: docker-compose.yml contains explicit version tags instead of :latest"
echo "Found the following explicit versions:"
grep -E "ghcr\.io/lanedirt/aliasvault-[^:]+:[0-9]+\.[0-9]+\.[0-9]+" docker-compose.yml
echo ""
echo "All AliasVault images in docker-compose.yml must use ':latest' tags, not explicit versions."
echo "Please update docker-compose.yml to use ':latest' for all AliasVault images."
exit 1
fi
echo "✅ docker-compose.yml correctly uses :latest tags for all AliasVault images"
- name: Create .env file with custom SMTP port
run: echo "SMTP_PORT=2525" > .env
- name: Set permissions and run install.sh build
run: |
chmod +x install.sh
./install.sh build --verbose
- name: Test services are responding
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: |
sleep 15
declare -A endpoints=(
["WASM"]="https://localhost:443"
["WebApi"]="https://localhost:443/api"
["Admin"]="https://localhost:443/admin/user/login"
)
failed=false
for name in "${!endpoints[@]}"; do
url="${endpoints[$name]}"
echo "Testing $name at $url"
response=$(curl -k -s -w "\nHTTP_CODE=%{http_code}" "$url")
http_code=$(echo "$response" | grep "HTTP_CODE=" | cut -d= -f2)
if [ "$http_code" -ne 200 ]; then
echo "❌ $name failed with $http_code"
failed=true
else
echo "✅ $name passed"
fi
done
echo "Testing SMTP on port 2525"
if ! nc -zv localhost 2525 2>&1 | grep -q 'succeeded'; then
echo "❌ SMTP failed"
failed=true
else
echo "✅ SMTP passed"
fi
if [ "$failed" = true ]; then
echo "Dumping logs"
docker compose logs admin
docker compose logs api
docker compose logs client
docker compose logs smtp
docker compose logs reverse-proxy
docker compose restart
exit 1
fi
- name: Test reset-admin-password output
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: |
output=$(./install.sh reset-admin-password | sed 's/\x1b\[[0-9;]*m//g')
if ! echo "$output" | grep -Eq '^\s*Password: [A-Za-z0-9+/=]{8,}'; then
echo "Invalid reset-admin-password output"
exit 1
fi

View File

@@ -1,107 +0,0 @@
name: Docker Compose Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-docker:
runs-on: ubuntu-latest
services:
docker:
image: docker:26.0.0
options: --privileged
steps:
- uses: actions/checkout@v2
- name: Create .env file with custom SMTP port as port 25 is not allowed in GitHub Actions
run: |
echo "SMTP_PORT=2525" > .env
- name: Set permissions and run install.sh
run: |
chmod +x install.sh
./install.sh build --verbose
- name: Test if services are responding
uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
command: |
sleep 15
# Array of endpoints to test
declare -A endpoints=(
["WASM"]="https://localhost:443"
["WebApi"]="https://localhost:443/api"
["Admin"]="https://localhost:443/admin/user/login"
)
failed=false
# Test HTTP endpoints
for name in "${!endpoints[@]}"; do
url="${endpoints[$name]}"
echo "Testing $name at $url"
# Store both response body and HTTP code
response=$(curl -k -s -w "\nHTTP_CODE=%{http_code}" "$url")
http_code=$(echo "$response" | grep "HTTP_CODE=" | cut -d= -f2)
body=$(echo "$response" | sed '$d') # Remove the last line (HTTP_CODE)
if [ "$http_code" -ne 200 ]; then
echo "❌ $name failed with HTTP $http_code at $url"
echo "Response body:"
echo "$body"
failed=true
else
echo "✅ $name responded with HTTP 200"
fi
done
# Test SMTP
echo "Testing SmtpService at localhost:2525"
if ! nc -zv localhost 2525 2>&1 | grep -q 'succeeded'; then
echo "❌ SmtpService failed to respond on port 2525"
failed=true
else
echo "✅ SmtpService responded successfully"
fi
# Exit with error if any service failed
if [ "$failed" = true ]; then
# Get container logs
echo "Container Logs admin:"
docker compose logs admin
echo "Container Logs api:"
docker compose logs api
echo "Container Logs client:"
docker compose logs client
echo "Container Logs smtp:"
docker compose logs smtp
echo "Container Logs reverse-proxy:"
docker compose logs reverse-proxy
# Restart containers for next test in case of failure
docker compose restart
exit 1
fi
- name: Test install.sh reset-admin-password output
run: |
output=$(./install.sh reset-admin-password)
if ! echo "$output" | grep -E '.*New admin password: [A-Za-z0-9+/=]{8,}.*'; then
echo "Password reset output format is incorrect"
echo "Expected: 'New admin password: <at least 8 base64 chars>'"
echo "Actual: $output"
exit 1
fi

View File

@@ -1,150 +0,0 @@
# 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" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-docker:
runs-on: ubuntu-latest
services:
docker:
image: docker:26.0.0
options: --privileged
steps:
- name: Get repository and branch information
id: repo-info
run: |
# Check if this is a PR from a fork
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
# If PR is from a fork, use main branch from lanedirt/AliasVault
echo "REPO_FULL_NAME=lanedirt/AliasVault" >> $GITHUB_ENV
echo "BRANCH_NAME=main" >> $GITHUB_ENV
else
# Otherwise use the current repository and branch
echo "REPO_FULL_NAME=${GITHUB_REPOSITORY}" >> $GITHUB_ENV
echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
fi
- name: Download install script from current branch
run: |
INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/$REPO_FULL_NAME/$BRANCH_NAME/install.sh"
echo "Downloading install script from: $INSTALL_SCRIPT_URL"
curl -f -o install.sh "$INSTALL_SCRIPT_URL"
- name: Create .env file with custom SMTP port as port 25 is not allowed in GitHub Actions
run: |
echo "SMTP_PORT=2525" > .env
- name: Set permissions and run install.sh
id: install_script
run: |
chmod +x install.sh
{
./install.sh install --verbose
exit_code=$?
if [ $exit_code -eq 2 ]; then
echo "Test skipped: Install script version is newer than latest release version. This is expected behavior if the install script is run on a branch that is ahead of the latest release."
echo "skip_remaining=true" >> $GITHUB_OUTPUT
true # Force success exit code
elif [ $exit_code -ne 0 ]; then
false # Propagate failure
fi
} || {
if [ $exit_code -eq 2 ]; then
echo "skip_remaining=true" >> $GITHUB_OUTPUT
true # Version mismatch is okay
else
exit $exit_code # Propagate other failures
fi
}
- name: Set up Docker Compose
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: docker compose -f docker-compose.yml up -d
- name: Wait for services to be up
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: |
# Wait for a few seconds
sleep 10
- name: Test if localhost:443 (WASM app) responds
if: ${{ !steps.install_script.outputs.skip_remaining }}
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
if: ${{ !steps.install_script.outputs.skip_remaining }}
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
if: ${{ !steps.install_script.outputs.skip_remaining }}
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
if: ${{ !steps.install_script.outputs.skip_remaining }}
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-admin-password output
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: |
output=$(./install.sh reset-admin-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

@@ -1,50 +0,0 @@
# 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" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
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: Start dev database
run: ./install.sh configure-dev-db start
- 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@v4
with:
name: admin-test-results
path: TestResults-Admin.xml

View File

@@ -1,47 +0,0 @@
# 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" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
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: Start dev database
run: ./install.sh configure-dev-db start
- 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 }}."

88
.github/workflows/dotnet-e2e-tests.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
# This workflow will test if running the e2e tests works.
name: E2E Tests
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
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
working-directory: apps/server
run: dotnet build
- name: Start dev database
run: ./install.sh configure-dev-db start
- name: Ensure browsers are installed
working-directory: apps/server
run: pwsh 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: cd apps/server && dotnet test Tests/AliasVault.E2ETests --no-build --verbosity normal --filter "Category=AdminTests"
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: admin-test-results
path: TestResults-Admin.xml
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
working-directory: apps/server
run: dotnet build
- name: Start dev database
run: ./install.sh configure-dev-db start
- name: Ensure browsers are installed
working-directory: apps/server
run: pwsh 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: cd apps/server && dotnet test Tests/AliasVault.E2ETests --no-build --verbosity normal --filter "FullyQualifiedName~.E2ETests.Tests.Client.Shard${{ matrix.shard }}."

View File

@@ -1,5 +1,5 @@
# This workflow will test if running the integration tests works.
name: .NET Integration Tests
name: Integration Tests
on:
push:
@@ -27,10 +27,12 @@ jobs:
run: dotnet workload install wasm-tools
- name: Build
working-directory: apps/server
run: dotnet build
- name: Start dev database
run: ./install.sh configure-dev-db start
- name: Run integration tests
run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal
working-directory: apps/server
run: dotnet test Tests/AliasVault.IntegrationTests --no-build --verbosity normal

View File

@@ -1,5 +1,5 @@
# This workflow will test if running the unit tests works.
name: .NET Unit Tests
name: Unit Tests
on:
push:
@@ -26,10 +26,13 @@ jobs:
run: dotnet workload install wasm-tools
- name: Restore dependencies
working-directory: apps/server
run: dotnet restore
- name: Build
working-directory: apps/server
run: dotnet build --no-restore
- name: Run unittests
run: dotnet test src/Tests/AliasVault.UnitTests --no-build --verbosity normal
working-directory: apps/server
run: dotnet test Tests/AliasVault.UnitTests --no-build --verbosity normal

168
.github/workflows/mobile-app-build.yml vendored Normal file
View File

@@ -0,0 +1,168 @@
name: Mobile App Build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
inputs:
build_android_signed:
description: 'Build signed Android APK/AAB'
required: true
type: boolean
default: false
build_ios_signed:
description: 'Build signed iOS IPA'
required: true
type: boolean
default: false
upload_to_app_store_connect:
description: 'Upload iOS IPA to App Store Connect'
required: true
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
setup:
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/mobile-app
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/mobile-app/package-lock.json
- name: Build and distribute shared libraries
run: |
cd ../../shared
chmod +x build-and-distribute.sh
./build-and-distribute.sh
- name: Verify shared library distribution
run: |
# Check if files exist and were recently modified
TARGET_DIRS=(
"utils/dist/shared/identity-generator"
"utils/dist/shared/password-generator"
"utils/dist/shared/models"
"utils/dist/shared/vault-sql"
)
for dir in "${TARGET_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
echo "❌ Directory $dir does not exist"
exit 1
fi
# Check if files were modified in the last 5 minutes
find "$dir" -type f -mmin -5 | grep -q . || {
echo "❌ Files in $dir were not recently modified"
exit 1
}
done
echo "✅ Shared library distribution verified"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test
- name: Run linting
run: npm run lint
build-ios:
needs: setup
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/mobile-app
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: apps/mobile-app/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build JS bundle (iOS - Expo)
run: |
mkdir -p build
npx expo export \
--dev \
--output-dir ./build \
--platform ios
build-android:
needs: setup
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Android App
uses: ./.github/actions/build-android-app
with:
run_tests: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
build-android-signed:
needs: setup
if: github.event_name == 'workflow_dispatch' && github.event.inputs.build_android_signed == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Android App
uses: ./.github/actions/build-android-app
with:
signed: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
build-ios-signed:
needs: setup
if: github.event_name == 'workflow_dispatch' && github.event.inputs.build_ios_signed == 'true'
runs-on: macos-15
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build iOS App
uses: ./.github/actions/build-ios-app
with:
signed: true
upload_to_app_store_connect: ${{ github.event.inputs.upload_to_app_store_connect }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASC_PRIVATE_KEY_BASE64: ${{ secrets.ASC_PRIVATE_KEY_BASE64 }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_TEAM_ID: ${{ secrets.ASC_TEAM_ID }}

View File

@@ -24,42 +24,67 @@ jobs:
files: install.sh
token: ${{ secrets.GITHUB_TOKEN }}
package-browser-extensions:
build-chrome-extension:
runs-on: ubuntu-latest
defaults:
run:
working-directory: browser-extension
steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Build Chrome Extension
uses: ./.github/actions/build-browser-extension
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: browser-extension/package-lock.json
browser: chrome
upload_to_release: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
run: npm ci
build-firefox-extension:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Zip extensions
run: |
npm run zip:chrome
npm run zip:firefox
npm run zip:edge
- name: Upload extensions to release
uses: softprops/action-gh-release@v2
- name: Build Firefox Extension
uses: ./.github/actions/build-browser-extension
with:
files: |
browser-extension/dist/aliasvault-browser-extension-*-chrome.zip
browser-extension/dist/aliasvault-browser-extension-*-firefox.zip
browser-extension/dist/aliasvault-browser-extension-*-edge.zip
browser-extension/dist/aliasvault-browser-extension-*-sources.zip
token: ${{ secrets.GITHUB_TOKEN }}
browser: firefox
upload_to_release: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-edge-extension:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Edge Extension
uses: ./.github/actions/build-browser-extension
with:
browser: edge
upload_to_release: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-android-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build Android App
uses: ./.github/actions/build-android-app
with:
signed: true
upload_to_release: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
build-and-push-docker:
needs: [upload-install-script, package-browser-extensions]
runs-on: ubuntu-latest
permissions:
contents: read
@@ -96,7 +121,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/Databases/AliasServerDb/Dockerfile
file: apps/server/Databases/AliasServerDb/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-postgres:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-postgres:${{ github.ref_name }}
@@ -105,7 +130,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/AliasVault.Api/Dockerfile
file: apps/server/AliasVault.Api/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-api:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-api:${{ github.ref_name }}
@@ -114,7 +139,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/AliasVault.Client/Dockerfile
file: apps/server/AliasVault.Client/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-client:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-client:${{ github.ref_name }}
@@ -123,7 +148,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/AliasVault.Admin/Dockerfile
file: apps/server/AliasVault.Admin/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:${{ github.ref_name }}
@@ -132,7 +157,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
file: apps/server/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:${{ github.ref_name }}
@@ -141,7 +166,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/Services/AliasVault.SmtpService/Dockerfile
file: apps/server/Services/AliasVault.SmtpService/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-smtp:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-smtp:${{ github.ref_name }}
@@ -150,7 +175,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/Services/AliasVault.TaskRunner/Dockerfile
file: apps/server/Services/AliasVault.TaskRunner/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-task-runner:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-task-runner:${{ github.ref_name }}
@@ -159,7 +184,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: src/Utilities/AliasVault.InstallCli/Dockerfile
file: apps/server/Utilities/AliasVault.InstallCli/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:${{ github.ref_name }}
tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-installcli:${{ github.ref_name }}

View File

@@ -58,16 +58,18 @@ jobs:
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
- name: Build and analyze
working-directory: apps/server
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
$scanner = "${{ github.workspace }}\.sonar\scanner\dotnet-sonarscanner"
if ('${{ github.event_name }}' -eq 'pull_request_target') {
.\.sonar\scanner\dotnet-sonarscanner begin /k:"lanedirt_AliasVault" /o:"lanedirt" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.pullrequest.key=${{ github.event.pull_request.number }} /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.coverage.exclusions="**Tests*.cs" /d:sonar.exclusions="**/__tests__/test-forms/*.html"
& $scanner begin /k:"lanedirt_AliasVault" /o:"lanedirt" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.pullrequest.key=${{ github.event.pull_request.number }} /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.coverage.exclusions="**Tests*.cs" /d:sonar.exclusions="**/__tests__/test-forms/*.html,**/dist/shared/**,**/*.sql"
} else {
.\.sonar\scanner\dotnet-sonarscanner begin /k:"lanedirt_AliasVault" /o:"lanedirt" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.coverage.exclusions="**Tests*.cs" /d:sonar.exclusions="**/__tests__/test-forms/*.html"
& $scanner begin /k:"lanedirt_AliasVault" /o:"lanedirt" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" /d:sonar.coverage.exclusions="**Tests*.cs" /d:sonar.exclusions="**/__tests__/test-forms/*.html,**/dist/shared/**,**/*.sql"
}
dotnet build
dotnet test -c Release /p:CollectCoverage=true /p:CoverletOutput=coverage /p:CoverletOutputFormat=opencover --filter 'FullyQualifiedName!~AliasVault.E2ETests'
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
& $scanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"

34
.gitignore vendored
View File

@@ -9,7 +9,6 @@
*.user
*.userosscache
*.sln.docstates
*.code-workspace
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
@@ -197,12 +196,7 @@ PublishScripts/
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
@@ -384,17 +378,13 @@ FodyWeavers.xsd
# Codebuddy Rider plugin
.codebuddy
# Claude Code
.claude
CLAUDE.md
# -------------------
# AliasVault specifics
# -------------------
# index.html is generated by the build process from index.template.html and therefore should be ignored
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
@@ -405,6 +395,10 @@ install.sh.backup
# Draw.io diagram temp files
*.drawio.*
# Dist files
packages/**/dist/
*.tsbuildinfo
# Certificates
certificates/**/*.crt
certificates/**/*.key
@@ -423,3 +417,13 @@ database/postgres-dev
# Temp files
temp
*.zip
# Don't check in .js.map or .mjs.map files. These are generated by the build process in the shared
# libraries and copied to the application so they can be used for debugging, but we don't need
# to check them in as it's not needed for the applications to actually run.
**/*.js.map
**/*.mjs.map
# Android keystore file (for publishing to Google Play)
*.keystore

27
.vscode/AliasVault.code-workspace vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"folders": [
{
"name": "AliasVault",
"path": "../"
},
{
"name": "server",
"path": "../apps/server"
},
{
"name": "browser-extension",
"path": "../apps/browser-extension"
},
{
"name": "mobile-app",
"path": "../apps/mobile-app"
},
{
"path": "../docs"
},
{
"path": "../shared"
}
],
"settings": {}
}

19
.vscode/launch.json vendored
View File

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

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"typescript.tsc.autoDetect": "off",
"npm.autoDetect": "off",
"grunt.autoDetect": "off",
"gulp.autoDetect": "off",
"jake.autoDetect": "off",
"task.autoDetect": "off",
"dotnet.autoDetect": "off"
}

99
.vscode/tasks.json vendored
View File

@@ -12,7 +12,7 @@
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/src/AliasVault.Api"
"cwd": "${workspaceFolder}/apps/server/AliasVault.Api"
}
},
{
@@ -26,7 +26,7 @@
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/src/AliasVault.Client"
"cwd": "${workspaceFolder}/apps/server/AliasVault.Client"
}
},
{
@@ -40,7 +40,21 @@
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/src/AliasVault.Admin"
"cwd": "${workspaceFolder}/apps/server/AliasVault.Admin"
}
},
{
"label": "Build and watch SMTP Service",
"type": "shell",
"command": "dotnet watch",
"args": [],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/server/Services/AliasVault.SmtpService"
}
},
{
@@ -50,7 +64,7 @@
"args": ["run", "build:client-css"],
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/src/AliasVault.Client"
"cwd": "${workspaceFolder}/apps/server/AliasVault.Client"
},
"group": {
"kind": "build",
@@ -64,7 +78,7 @@
"args": ["run", "build:admin-css"],
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/src/AliasVault.Admin"
"cwd": "${workspaceFolder}/apps/server/AliasVault.Admin"
},
"group": {
"kind": "build",
@@ -91,7 +105,7 @@
"args": ["test"],
"problemMatcher": "$msCompile",
"options": {
"cwd": "${workspaceFolder}/src/Tests/AliasVault.UnitTests"
"cwd": "${workspaceFolder}/apps/server/Tests/AliasVault.UnitTests"
},
"group": {
"kind": "test",
@@ -105,12 +119,81 @@
"args": ["run", "dev:chrome"],
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/browser-extension"
"cwd": "${workspaceFolder}/apps/browser-extension"
},
"group": {
"kind": "build",
"isDefault": true
}
}
},
{
"label": "Run debug iOS App (simulator)",
"type": "shell",
"command": "npx",
"args": ["expo", "run:ios"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/mobile-app"
}
},
{
"label": "Run debug iOS App (device)",
"type": "shell",
"command": "npx",
"args": ["expo", "run:ios", "--device"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/mobile-app"
}
},
{
"label": "Run release iOS App (device)",
"type": "shell",
"command": "npx",
"args": ["expo", "run:ios", "--device", "--configuration", "Release"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/mobile-app"
}
},
{
"label": "Run release Android App (device)",
"type": "shell",
"command": "npx",
"args": ["expo", "run:android", "--device", "--variant", "release"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/mobile-app"
}
},
{
"label": "Build and watch Docs",
"type": "shell",
"command": "docker compose up",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/docs"
}
},
]
}

View File

@@ -1,14 +1,74 @@
# Contributing to the source code
We welcome contributions to AliasVault. Please read the guidelines on the official AliasVault docs website on how to get your local development environment setup and the general contribution guidelines:
# Contributing to AliasVault
Thanks for your interest in contributing to the AliasVault project! There are a lot of ways to help out.
## Table of Contents
1. [Help spread the word](#1-help-spread-the-word)
2. [Contributing to Translations](#2-contributing-to-translations)
3. [Contributing to the Documentation](#3-contributing-to-the-documentation)
4. [Contributing to the Main Codebase](#4-contributing-to-the-main-codebase)
- [4.1 Get in contact](#41-get-in-contact)
- [4.2 Set up your local development environment](#42-set-up-your-local-development-environment)
5. [License and Contributions](#5-license-and-contributions)
---
## 1. Help spread the word
Help grow the AliasVault community by:
- Answering questions and helping users in our [Discord](https://discord.gg/DsaXMTEtpF)
- Reporting bugs and suggesting improvements
- Sharing on social media and writing about your experience
- Creating tutorials and documentation
- Spreading the word about privacy and self-hosting
## 2. Contributing to Translations
Help make AliasVault accessible to users worldwide by contributing translations! AliasVault is currently available in English and Dutch, but we're looking for volunteers to help translate it into other languages such as German, French, Spanish, Ukrainian, Italian, and more.
AliasVault translations are managed through [Crowdin](https://crowdin.com/), an online translation platform. If youd like to help translate AliasVault into your native language, please [request access to the Crowdin project](https://crowdin.com/project/aliasvault).
If you're willing to help, we also encourage you to get in contact via [Discord](https://discord.gg/DsaXMTEtpF) to chat (quickest), or contact us via email at [contact@support.aliasvault.net](mailto:contact@support.aliasvault.net) to discuss the language(s) you are willing to contribute to, and so we can answer any technical questions you might have.
Your translation contributions will help make AliasVault more accessible to privacy-conscious users around the world!
## 3. Contributing to the Documentation
The docs are built using Jekyll and automatically deploy to GitHub Pages via GitHub Actions. You can build the docs locally by running `docker compose up` in the `./docs` folder.
The docs site is based on the open-source template called Just The Docs. Find more information about how this template works in the [official docs](https://just-the-docs.github.io/just-the-docs/).
To make changes to the AliasVault documentation please make a PR that directly edits the `docs` markdown files in this repository.
## 4. Contributing to the Main Codebase
### 4.1 Get in contact
If you're planning to work on a new feature or improvement for AliasVault, we strongly encourage you to get in touch with us first. This ensures that your proposed changes align with the project's direction and increases the likelihood of your work being accepted into the official repository. You can reach us through:
- Opening an issue on GitHub to discuss your proposed changes
- Reaching out via Discord or email
- Contacting the maintainers directly
### 4.2 Set up your local development environment
You can find instructions on how to get your local development environment setup for the different parts of the AliasVault codebase here:
https://docs.aliasvault.net/misc/dev/
> Tip: if the URL above is not available, the raw doc pages can also be found in the `docs` folder in this repository.
## Contributing to the documentation
The docs are built using Jekyll and automatically deploy to GitHub Pages via GitHub Actions. You can build the docs locally by running `docker compose up` in in the `./docs` folder.
If you run into any issues, feel free to join our [Discord](https://discord.gg/DsaXMTEtpF) to chat with the maintainers and author.
The docs site is based on the open-source template called Just The Docs. Find more information about how this template works in the [official docs](https://just-the-docs.github.io/just-the-docs/).
## 5. License and Contributions
AliasVault is licensed under the GNU Affero General Public License v3.0 (AGPLv3). By submitting code, documentation, or other contributions to this project, you agree that:
To make changes to the AliasVault documentation please make a PR that directly edits the `docs` markdown files in this repository.
1. Your contribution will be licensed under the same AGPLv3 license as the project
2. You have the legal right to grant this license (e.g., you are the author, or have permission)
3. You understand that your contribution will be made public under the AGPLv3 terms
4. You are not expected to provide support or warranties for your contribution
✅ There is no Contributor License Agreement (CLA) required. We believe in a balanced open source model where all contributors are treated equally under the terms of the AGPLv3.
> By opening a pull request, you agree to these terms. Your contributions will be published under the AGPLv3 license.

View File

@@ -1,21 +1,660 @@
MIT License
# GNU AFFERO GENERAL PUBLIC LICENSE
Copyright (c) 2024 lanedirt
Version 3, 19 November 2007
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains
free software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing
under this license.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public
License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your
version supports such interaction) an opportunity to receive the
Corresponding Source of your version by providing access to the
Corresponding Source from a network server at no charge, through some
standard or customary means of facilitating copying of software. This
Corresponding Source shall include the Corresponding Source for any
work covered by version 3 of the GNU General Public License that is
incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Affero General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever
published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for
the specific requirements.
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU AGPL, see <https://www.gnu.org/licenses/>.

View File

@@ -1,72 +1,78 @@
# AliasVault: password & (email) alias manager [<img src="https://github.com/user-attachments/assets/933c8b45-a190-4df6-913e-b7c64ad9938b" width="100" align="right" alt="AliasVault">](https://github.com/lanedirt/AliasVault)
# <img src="https://github.com/user-attachments/assets/933c8b45-a190-4df6-913e-b7c64ad9938b" width="35" alt="AliasVault"> AliasVault
AliasVault is a privacy-first password and email alias manager. Create unique identities, strong passwords, and random email aliases for every website you use. Fully end-to-end encrypted, with a built-in email server and zero third-party dependencies.
[<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/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/quality_gate/lanedirt_AliasVault?server=https%3A%2F%2Fsonarcloud.io&label=sonarcloud&logo=sonarcloud">](https://sonarcloud.io/summary/new_code?id=lanedirt_AliasVault)
[<img alt="Discord" src="https://img.shields.io/discord/1309300619026235422?logo=discord&logoColor=%237289da&label=discord&color=%237289da">](https://discord.gg/DsaXMTEtpF)
[<img src="https://img.shields.io/github/v/release/lanedirt/AliasVault?include_prereleases&logo=github&label=Release">](https://github.com/lanedirt/AliasVault/releases)
[![.NET E2E Tests (with Sharding)](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-e2e-tests.yml/badge.svg)](https://github.com/lanedirt/AliasVault/actions/workflows/dotnet-e2e-tests.yml)
[<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)
[<img src="https://badges.crowdin.net/aliasvault/localized.svg">](https://crowdin.com/project/aliasvault)
[<img alt="Discord" src="https://img.shields.io/discord/1309300619026235422?logo=discord&logoColor=%237289da&label=Discord&color=%237289da">](https://discord.gg/DsaXMTEtpF)
> AliasVault is an end-to-end encrypted password and (email) alias manager that protects your privacy by creating alternative identities, passwords and email addresses for every website you use. Use the official supported cloud version or self-host AliasVault on your own server with Docker.
<a href="https://app.aliasvault.net">Try the cloud version 🔥</a> | <a href="https://aliasvault.net?utm_source=gh-readme">Website </a> | <a href="https://docs.aliasvault.net?utm_source=gh-readme">Documentation </a> | <a href="#self-hosting">Self-host instructions</a>
- <a href="https://app.aliasvault.net">Try the cloud version 🔥</a> - <a href="https://aliasvault.net?utm_source=gh-readme">Website 🌐</a> - <a href="https://docs.aliasvault.net?utm_source=gh-readme">Documentation 📚</a> - <a href="#self-hosting">Self-host instructions ⚙️</a> - <a href="https://aliasvault.net/plugins?utm_source=gh-readme">Browser Extensions 🔌</a>
⭐ Star us on GitHub, it motivates us a lot!
### 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 real working email addresses for each alias. Emails sent to these addresses are instantly visible in the AliasVault app and browser extension.
- **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 & Self-hostable**:
- The source code is available on GitHub and AliasVault can be self-hosted on your own server via an easy install script.
## About
Built on 15 years of experience, AliasVault is independent, open-source, self-hostable and community-driven. Its the response to a web that tracks everything: a way to take back control of your digital privacy and help you stay secure online.
Leendert de Borst ([@lanedirt](https://github.com/lanedirt)), Creator of AliasVault
## Screenshots
<table>
<tr>
<th align="center">Browser Extension</th>
<th align="center">Generate email and aliases</th>
<th align="center">Responsive web app</th>
<th align="center">Browser extensions</th>
</tr>
<tr>
<td align="center">
<img src="https://github.com/user-attachments/assets/d9ffd3dc-08a0-462d-8148-e8da5ec5a520" alt="Browser Autofill" />
<img src="https://github.com/user-attachments/assets/fa5bf64a-704d-4f09-b4e0-0310ab662204" alt="Responsive web app" />
</td>
<td align="center">
<img src="https://github.com/user-attachments/assets/86752994-d469-4b0e-b633-c089e0aed12b" alt="Generate Aliases" />
<img src="https://github.com/user-attachments/assets/b5218609-217b-4c8d-8d5d-8c71e19bf057"alt="Browser extensions" />
</td>
</tr>
<tr>
<th align="center">Strong security</th>
<th align="center">Easy self-host</th>
<th align="center">Native iOS & Android apps</th>
<th align="center">& much more</th>
</tr>
<tr>
<td align="center">
<img src="https://github.com/user-attachments/assets/26b66379-10a5-4b8b-9c69-e64b553a10be" alt="Strong security" />
<img src="https://github.com/user-attachments/assets/5d09ad78-d145-48a1-b8da-c5a1dc708886" alt="Native iOS & Android Apps" />
</td>
<td align="center">
<img src="https://github.com/user-attachments/assets/47c7002a-e326-4507-8801-194e134e00dd" alt="Easy self-host installation" />
<img src="https://github.com/user-attachments/assets/34fe650d-f08d-4c92-92e0-4e750b7a662a" alt="Lots of features" />
</td>
</tr>
</table>
## Official Cloud Version
The official cloud version of AliasVault is freely available at [app.aliasvault.net](https://app.aliasvault.net). This fully supported platform is always up to date with our latest release. Create an account to protect your privacy today.
## Cloud-hosted
Use the official cloud version of AliasVault at [app.aliasvault.net](https://app.aliasvault.net). This fully supported platform is always up to date with our latest release.
AliasVault is available on:
- [Web (universal)](https://app.aliasvault.net)
- [Chrome](https://chromewebstore.google.com/detail/aliasvault/bmoggiinmnodjphdjnmpcnlleamkfedj)
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/aliasvault/)
- [Edge](https://microsoftedge.microsoft.com/addons/detail/aliasvault/kabaanafahnjkfkplbnllebdmppdemfo)
- [Safari](https://apps.apple.com/app/id6743163173)
<p>
<a href="https://apps.apple.com/app/id6745490915" style="display: inline-block; margin-right: 20px;"><img src="https://github.com/user-attachments/assets/bad09b85-2635-4e3e-b154-9f348b88f6d6" style="height: 40px;margin-right:10px;" alt="Download on the App Store"></a>
<a href="https://play.google.com/store/apps/details?id=net.aliasvault.app" style="display: inline-block;"><img src="https://github.com/user-attachments/assets/b28979c9-f4b8-4090-8735-e384a7fdaa47" style="height: 40px;" alt="Get it on Google Play"></a>
<a href="https://f-droid.org/packages/net.aliasvault.app" style="display: inline-block;"><img src="https://github.com/user-attachments/assets/0fb25df1-0ea2-46a6-bfee-a9d70f22a02a" style="height: 40px;" alt="Get it on F-Droid"></a>
</p>
[<img width="700" alt="Screenshot of AliasVault" src="docs/assets/img/screenshot.png">](https://app.aliasvault.net)
## Self-hosting
For full control over your own data you can self-host and install AliasVault on your own servers. The easiest method is to use the provided install script. This will download the pre-built Docker images and start the containers.
For full control over your own data you can self-host and install AliasVault on your own servers.
### Install using install script
This method uses pre-built Docker images and works on minimal hardware specifications:
- Linux VM with root access (Ubuntu/AlmaLinux recommended) or Raspberry Pi
- 1 vCPU
- 1GB RAM
- 16GB disk space
- Docker installed
- 64-bit Linux VM (Ubuntu/AlmaLinux) or Raspberry Pi, with root access
- Minimum: 1 vCPU, 1GB RAM, 16GB disk
- Docker ≥ 20.10 and Docker Compose ≥ 2.0
```bash
# Download install script from latest stable release
@@ -83,7 +89,7 @@ The install script will output the URL where the app is available. By default th
> Note: If you want to change the default AliasVault ports you can do so in the `.env` file.
## Documentation
## Technical documentation
For more information about the installation process, manual setup instructions and other topics, please see the official documentation website:
- [Documentation website (docs.aliasvault.net) 📚](https://docs.aliasvault.net)
@@ -100,12 +106,12 @@ For detailed information about our encryption implementation and security archit
- [SECURITY.md](SECURITY.md)
- [Security Architecture Diagram](https://docs.aliasvault.net/architecture)
## Roadmap
## Features & Roadmap
AliasVault is under active development, with a strong focus on usability, security, and cross-platform support.
AliasVault is under active development, with a strong focus on usability, security, and cross-platform support.
The main focus is on ensuring robust usability for everyday tasks, including comprehensive autofill capabilities across all platforms.
🛠️ Incremental releases are published every 23 weeks, with a strong emphasis on real-world testing and user feedback.
🛠️ Incremental releases are published every 23 weeks, with a strong emphasis on real-world testing and user feedback.
During this phase, AliasVault can safely be used in production as it maintains strict data integrity and automatic migration guarantees.
Core features that are being worked on:
@@ -117,18 +123,20 @@ Core features that are being worked on:
- [x] Browser extensions with autofill feature (Chrome, Firefox, Edge, Safari, Brave)
- [x] Built-in TOTP authenticator
- [x] Import passwords from traditional password managers
- [ ] iOS and Android native apps
- [ ] Data model improvements to support reusable identities in combination with aliases
- [x] iOS native app
- [x] Android native app
- [x] Editing in browser extension
- [x] Multi-language support across all client applications
- [ ] Data model and usability improvements (more flexible aliases and credential types, folder support, bulk selecting etc.)
- [ ] Support for FIDO2/WebAuthn hardware keys and passkeys
- [ ] Adding support for family/team sharing (organization features)
👉 [View the full AliasVault roadmap here](https://github.com/lanedirt/AliasVault/issues/731)
### Got feedback or ideas?
### Got feedback or ideas?
Feel free to open an issue or join our [Discord](https://discord.gg/DsaXMTEtpF)! Contributions are warmly welcomed—whether in feature development, testing, or spreading the word. Get in touch on Discord if you're interested in contributing.
### Support the mission
Your donation helps me dedicate more time and resources to improving AliasVault, making the internet safer for everyone!
<a href="https://www.buymeacoffee.com/lanedirt" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
<a href="https://www.buymeacoffee.com/lanedirt" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 50px !important;" ></a>

28
apps/browser-extension/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.output
dist
!src/utils/dist
stats.html
stats-*.json
.wxt
web-ext.config.ts
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,174 @@
import js from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import reactPlugin from "eslint-plugin-react";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import importPlugin from "eslint-plugin-import";
import jsdocPlugin from "eslint-plugin-jsdoc";
import globals from 'globals';
export default [
{
ignores: [
"dist/**",
"node_modules/**",
"src/utils/dist/**",
]
},
js.configs.recommended,
{
files: ["src/**/*.{ts,tsx}"],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaFeatures: { jsx: true },
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
tsconfigRootDir: ".",
},
},
plugins: {
"@typescript-eslint": tsPlugin,
"react": reactPlugin,
"react-hooks": reactHooksPlugin,
"import": importPlugin,
"jsdoc": jsdocPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
...reactPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
"curly": ["error", "all"],
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
"@typescript-eslint/await-thenable": "error",
"react/react-in-jsx-scope": "off",
"react/no-unused-prop-types": "error",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": true,
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}],
"indent": ["error", 2, {
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
"MemberExpression": 1,
"FunctionDeclaration": { "parameters": 1, "body": 1 },
"FunctionExpression": { "parameters": 1, "body": 1 },
"CallExpression": { "arguments": 1 },
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoreComments": false
}],
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1, "maxBOF": 0 }],
"no-console": ["error", { allow: ["warn", "error", "info", "debug"] }],
"jsdoc/require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true,
"ArrowFunctionExpression": true,
"FunctionExpression": true
}
}],
"jsdoc/require-description": ["error", {
"contexts": [
"FunctionDeclaration",
"MethodDefinition",
"ClassDeclaration",
"ArrowFunctionExpression",
"FunctionExpression"
]
}],
"spaced-comment": ["error", "always"],
"multiline-comment-style": ["error", "starred-block"],
"@typescript-eslint/explicit-member-accessibility": ["error"],
"@typescript-eslint/explicit-function-return-type": ["error"],
"@typescript-eslint/typedef": ["error"],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": ["PascalCase"],
"prefix": ["I"]
},
{
"selector": "class",
"format": ["PascalCase"]
}
],
"react-hooks/exhaustive-deps": "warn",
"react/jsx-no-constructed-context-values": "error",
"import/no-unresolved": [
"error",
{
ignore: ['^#imports$'] // Ignore virtual imports from WXT which are not resolved by the typescript compiler
}
],
"import/order": [
"error",
{
"groups": [
"builtin", // Node "fs", "path", etc.
"external", // "react", "lodash", etc.
"internal", // Aliased paths like "@/utils"
"parent", // "../"
"sibling", // "./"
"index", // "./index"
"object", // import 'foo'
"type" // import type ...
],
"pathGroups": [
{
pattern: "@/entrypoints/**",
group: "internal",
position: "before"
},
{
pattern: "@/utils/**",
group: "internal",
position: "before"
},
{
pattern: "@/hooks/**",
group: "internal",
position: "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"],
"newlines-between": "always",
"alphabetize": {
order: "asc",
caseInsensitive: true
}
}
],
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json',
},
},
react: {
version: "detect",
},
},
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
NodeJS: true,
chrome: 'readonly',
}
}
}
];

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,13 @@
"name": "aliasvault-browser-extension",
"description": "AliasVault Browser Extension",
"private": true,
"version": "0.0.0",
"version": "0.21.1",
"type": "module",
"scripts": {
"dev:chrome": "wxt -b chrome",
"dev:firefox": "wxt -b firefox",
"dev:edge": "wxt -b edge",
"dev:safari": "wxt -b safari",
"build:chrome": "wxt build -b chrome",
"build:firefox": "wxt build -b firefox",
"build:edge": "wxt build -b edge",
@@ -25,31 +26,38 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"@types/node": "^22.13.10",
"@hookform/resolvers": "^5.1.1",
"argon2-browser": "^1.18.0",
"buffer": "^6.0.3",
"globals": "^16.0.0",
"i18next": "^25.3.1",
"otpauth": "^9.3.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.57.0",
"react-i18next": "^15.6.0",
"react-router-dom": "^7.5.2",
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
"sql.js": "^1.12.0",
"vitest": "^3.0.8",
"webext-bridge": "^6.0.1"
"webext-bridge": "^6.0.1",
"yup": "^1.6.1"
},
"devDependencies": {
"@types/chrome": "^0.0.280",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.13.10",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
"@types/sql.js": "^1.4.9",
"@types/yup": "^0.29.14",
"@typescript-eslint/eslint-plugin": "^8.21.0",
"@typescript-eslint/parser": "^8.21.0",
"@vitest/coverage-v8": "^3.0.8",
"@wxt-dev/module-react": "^1.1.2",
"autoprefixer": "^10.4.20",
"eslint": "^9.19.0",
"eslint-import-resolver-typescript": "^4.4.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^50.6.3",
"eslint-plugin-react": "^7.37.4",
@@ -59,6 +67,6 @@
"tailwindcss": "^3.4.17",
"typescript": "^5.6.3",
"vite-plugin-static-copy": "^2.2.0",
"wxt": "^0.19.13"
"wxt": "^0.20.6"
}
}

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,10 +1,3 @@
//
// SafariWebExtensionHandler.swift
// AliasVault Extension
//
// Created by Leendert de Borst on 12/03/2025.
//
import SafariServices
import os.log

View File

@@ -447,7 +447,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -460,7 +460,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.21.1;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -479,7 +479,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "AliasVault Extension/AliasVault_Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -492,7 +492,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.21.1;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -515,7 +515,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -530,7 +530,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.16.2;
MARKETING_VERSION = 0.21.1;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -554,7 +554,7 @@
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -569,7 +569,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.16.2;
MARKETING_VERSION = 0.21.1;
OTHER_LDFLAGS = (
"-framework",
SafariServices,

View File

@@ -1,10 +1,3 @@
//
// AppDelegate.swift
// AliasVault
//
// Created by Leendert de Borst on 12/03/2025.
//
import Cocoa
@main

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>SFSafariWebExtensionConverterVersion</key>
<string>15.4</string>
</dict>

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,10 +1,3 @@
//
// ViewController.swift
// AliasVault
//
// Created by Leendert de Borst on 12/03/2025.
//
import Cocoa
import SafariServices
import WebKit

View File

@@ -0,0 +1,13 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@media (max-width: 380px) {
html, body {
width: 350px;
max-width: 350px;
height: 600px;
max-height: 600px;
overflow: hidden;
}
}

View File

@@ -0,0 +1,80 @@
import { onMessage, sendMessage } from "webext-bridge/background";
import { setupContextMenus } from '@/entrypoints/background/ContextMenu';
import { handleOpenPopup, handlePopupWithCredential, handleToggleContextMenu } from '@/entrypoints/background/PopupMessageHandler';
import { handleCheckAuthStatus, handleClearPersistedFormValues, handleClearVault, handleCreateIdentity, handleGetCredentials, handleGetDefaultEmailDomain, handleGetDefaultIdentitySettings, handleGetDerivedKey, handleGetPasswordSettings, handleGetPersistedFormValues, handleGetVault, handlePersistFormValues, handleStoreVault, handleSyncVault, handleUploadVault } from '@/entrypoints/background/VaultMessageHandler';
import { GLOBAL_CONTEXT_MENU_ENABLED_KEY } from '@/utils/Constants';
import { defineBackground, storage, browser } from '#imports';
export default defineBackground({
/**
* This is the main entry point for the background script.
*/
async main() {
// Listen for messages using webext-bridge
onMessage('CHECK_AUTH_STATUS', () => handleCheckAuthStatus());
onMessage('STORE_VAULT', ({ data }) => handleStoreVault(data));
onMessage('UPLOAD_VAULT', ({ data }) => handleUploadVault(data));
onMessage('SYNC_VAULT', () => handleSyncVault());
onMessage('GET_VAULT', () => handleGetVault());
onMessage('CLEAR_VAULT', () => handleClearVault());
onMessage('GET_CREDENTIALS', () => handleGetCredentials());
onMessage('CREATE_IDENTITY', ({ data }) => handleCreateIdentity(data));
onMessage('GET_DEFAULT_EMAIL_DOMAIN', () => handleGetDefaultEmailDomain());
onMessage('GET_DEFAULT_IDENTITY_SETTINGS', () => handleGetDefaultIdentitySettings());
onMessage('GET_PASSWORD_SETTINGS', () => handleGetPasswordSettings());
onMessage('GET_DERIVED_KEY', () => handleGetDerivedKey());
onMessage('OPEN_POPUP', () => handleOpenPopup());
onMessage('OPEN_POPUP_WITH_CREDENTIAL', ({ data }) => handlePopupWithCredential(data));
onMessage('TOGGLE_CONTEXT_MENU', ({ data }) => handleToggleContextMenu(data));
onMessage('PERSIST_FORM_VALUES', ({ data }) => handlePersistFormValues(data));
onMessage('GET_PERSISTED_FORM_VALUES', () => handleGetPersistedFormValues());
onMessage('CLEAR_PERSISTED_FORM_VALUES', () => handleClearPersistedFormValues());
// Setup context menus
const isContextMenuEnabled = await storage.getItem(GLOBAL_CONTEXT_MENU_ENABLED_KEY) ?? true;
if (isContextMenuEnabled) {
await setupContextMenus();
}
// Listen for custom commands
try {
browser.commands.onCommand.addListener(async (command) => {
if (command === "show-autofill-popup") {
// Get the currently active tab
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
return;
}
// Execute script in the active tab
await browser.scripting.executeScript({
target: { tabId: tab.id },
func: getActiveElementIdentifier,
}).then((results) => {
const elementIdentifier = results[0]?.result;
if (elementIdentifier) {
sendMessage('OPEN_AUTOFILL_POPUP', { elementIdentifier }, `content-script@${tab.id}`);
}
}).catch(console.error);
}
});
} catch (error) {
console.error('Error setting up command listener:', error);
}
}
});
/**
* Activate AliasVault for the active input element.
*/
function getActiveElementIdentifier() : string {
const target = document.activeElement;
if (target instanceof HTMLInputElement) {
return target.id || target.name || '';
}
return '';
}

View File

@@ -1,11 +1,16 @@
import { type Browser } from '@wxt-dev/browser';
import { sendMessage } from 'webext-bridge/background';
import { PasswordGenerator } from '../../utils/generators/Password/PasswordGenerator';
import { browser } from 'wxt/browser';
import { PasswordGenerator } from '@/utils/dist/shared/password-generator';
import { t } from '@/i18n/StandaloneI18n';
import { browser } from "#imports";
/**
* Setup the context menus.
*/
export function setupContextMenus() : void {
export async function setupContextMenus() : Promise<void> {
// Create root menu
browser.contextMenus.create({
id: "aliasvault-root",
@@ -17,7 +22,7 @@ export function setupContextMenus() : void {
browser.contextMenus.create({
id: "aliasvault-activate-form",
parentId: "aliasvault-root",
title: "Autofill with AliasVault",
title: await t('content.autofillWithAliasVault'),
contexts: ["editable"],
});
@@ -33,15 +38,19 @@ export function setupContextMenus() : void {
browser.contextMenus.create({
id: "aliasvault-generate-password",
parentId: "aliasvault-root",
title: "Generate random password (copy to clipboard)",
title: await t('content.generateRandomPassword'),
contexts: ["all"]
});
browser.contextMenus.onClicked.addListener((info: Browser.contextMenus.OnClickData, tab?: Browser.tabs.Tab) =>
handleContextMenuClick(info, tab)
);
}
/**
* Handle context menu clicks.
*/
export function handleContextMenuClick(info: browser.contextMenus.OnClickData, tab?: browser.tabs.Tab) : void {
export function handleContextMenuClick(info: Browser.contextMenus.OnClickData, tab?: Browser.tabs.Tab) : void {
if (info.menuItemId === "aliasvault-generate-password") {
// Initialize password generator
const passwordGenerator = new PasswordGenerator();
@@ -49,15 +58,16 @@ export function handleContextMenuClick(info: browser.contextMenus.OnClickData, t
// Use browser.scripting to write password to clipboard from active tab
if (tab?.id) {
browser.scripting.executeScript({
target: { tabId: tab.id },
func: copyPasswordToClipboard,
args: [password]
// Get confirm text translation.
t('content.passwordCopiedToClipboard').then((message) => {
browser.scripting.executeScript({
target: { tabId: tab.id },
func: copyPasswordToClipboard,
args: [message, password]
});
});
}
}
if (info.menuItemId === "aliasvault-activate-form" && tab?.id) {
} else if (info.menuItemId === "aliasvault-activate-form" && tab?.id) {
// First get the active element's identifier
browser.scripting.executeScript({
target: { tabId: tab.id },
@@ -75,9 +85,9 @@ export function handleContextMenuClick(info: browser.contextMenus.OnClickData, t
/**
* Copy provided password to clipboard.
*/
function copyPasswordToClipboard(generatedPassword: string) : void {
function copyPasswordToClipboard(message: string, generatedPassword: string) : void {
navigator.clipboard.writeText(generatedPassword).then(() => {
showToast('Password copied to clipboard');
showToast(message);
});
/**

View File

@@ -1,6 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { browser } from "wxt/browser";
import { BoolResponse } from '../../utils/types/messaging/BoolResponse';
import { setupContextMenus } from '@/entrypoints/background/ContextMenu';
import { BoolResponse } from '@/utils/types/messaging/BoolResponse';
import { browser } from '#imports';
/**
* Handle opening the popup.
*/
@@ -31,4 +35,18 @@ export function handlePopupWithCredential(message: any) : Promise<BoolResponse>
});
return { success: true };
})();
}
/**
* Handle toggling the context menu.
*/
export function handleToggleContextMenu(message: any) : Promise<BoolResponse> {
return (async () : Promise<BoolResponse> => {
if (!message.enabled) {
browser.contextMenus.removeAll();
} else {
await setupContextMenus();
}
return { success: true };
})();
}

View File

@@ -1,21 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import EncryptionUtility from '../../utils/EncryptionUtility';
import SqliteClient from '../../utils/SqliteClient';
import { WebApiService } from '../../utils/WebApiService';
import { Vault } from '../../utils/types/webapi/Vault';
import { VaultResponse } from '../../utils/types/webapi/VaultResponse';
import { VaultPostResponse } from '../../utils/types/webapi/VaultPostResponse';
import { storage } from 'wxt/storage';
import { BoolResponse as messageBoolResponse } from '../../utils/types/messaging/BoolResponse';
import { VaultResponse as messageVaultResponse } from '../../utils/types/messaging/VaultResponse';
import { CredentialsResponse as messageCredentialsResponse } from '../../utils/types/messaging/CredentialsResponse';
import { StringResponse as stringResponse } from '../../utils/types/messaging/StringResponse';
import { PasswordSettingsResponse as messagePasswordSettingsResponse } from '../../utils/types/messaging/PasswordSettingsResponse';
import { storage } from 'wxt/utils/storage';
import type { Vault, VaultResponse, VaultPostResponse } from '@/utils/dist/shared/models/webapi';
import { EncryptionUtility } from '@/utils/EncryptionUtility';
import { SqliteClient } from '@/utils/SqliteClient';
import { BoolResponse as messageBoolResponse } from '@/utils/types/messaging/BoolResponse';
import { CredentialsResponse as messageCredentialsResponse } from '@/utils/types/messaging/CredentialsResponse';
import { IdentitySettingsResponse } from '@/utils/types/messaging/IdentitySettingsResponse';
import { PasswordSettingsResponse as messagePasswordSettingsResponse } from '@/utils/types/messaging/PasswordSettingsResponse';
import { StoreVaultRequest } from '@/utils/types/messaging/StoreVaultRequest';
import { StringResponse as stringResponse } from '@/utils/types/messaging/StringResponse';
import { VaultResponse as messageVaultResponse } from '@/utils/types/messaging/VaultResponse';
import { VaultUploadResponse as messageVaultUploadResponse } from '@/utils/types/messaging/VaultUploadResponse';
import { WebApiService } from '@/utils/WebApiService';
import { t } from '@/i18n/StandaloneI18n';
/**
* Check if the user is logged in and if the vault is locked.
* Check if the user is logged in and if the vault is locked, and also check for pending migrations.
*/
export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, isVaultLocked: boolean }> {
export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, isVaultLocked: boolean, hasPendingMigrations: boolean, error?: string }> {
const username = await storage.getItem('local:username');
const accessToken = await storage.getItem('local:accessToken');
const vaultData = await storage.getItem('session:encryptedVault');
@@ -23,10 +27,42 @@ export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, i
const isLoggedIn = username !== null && accessToken !== null;
const isVaultLocked = isLoggedIn && vaultData === null;
return {
isLoggedIn,
isVaultLocked
};
// If vault is locked, we can't check for pending migrations
if (isVaultLocked) {
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false
};
}
// If not logged in, no need to check migrations
if (!isLoggedIn) {
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false
};
}
// Vault is unlocked, check for pending migrations
try {
const sqliteClient = await createVaultSqliteClient();
const hasPendingMigrations = await sqliteClient.hasPendingMigrations();
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations
};
} catch (error) {
console.error('Error checking pending migrations:', error);
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false,
error: error instanceof Error ? error.message : await t('common.errors.unknownError')
};
}
}
/**
@@ -36,22 +72,37 @@ export async function handleStoreVault(
message: any,
) : Promise<messageBoolResponse> {
try {
const vaultResponse = message.vaultResponse as VaultResponse;
const encryptedVaultBlob = vaultResponse.vault.blob;
const vaultRequest = message as StoreVaultRequest;
// Store encrypted vault and derived key in session storage.
await storage.setItems([
{ key: 'session:encryptedVault', value: encryptedVaultBlob },
{ key: 'session:derivedKey', value: message.derivedKey },
{ key: 'session:publicEmailDomains', value: vaultResponse.vault.publicEmailDomainList },
{ key: 'session:privateEmailDomains', value: vaultResponse.vault.privateEmailDomainList },
{ key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber }
]);
// Store new encrypted vault in session storage.
await storage.setItem('session:encryptedVault', vaultRequest.vaultBlob);
/*
* For all other values, check if they have a value and store them in session storage if they do.
* Some updates, e.g. when mutating local database, these values will not be set.
*/
// Store derived key in session storage (if it has a value)
if (vaultRequest.derivedKey) {
await storage.setItem('session:derivedKey', vaultRequest.derivedKey);
}
if (vaultRequest.publicEmailDomainList) {
await storage.setItem('session:publicEmailDomains', vaultRequest.publicEmailDomainList);
}
if (vaultRequest.privateEmailDomainList) {
await storage.setItem('session:privateEmailDomains', vaultRequest.privateEmailDomainList);
}
if (vaultRequest.vaultRevisionNumber) {
await storage.setItem('session:vaultRevisionNumber', vaultRequest.vaultRevisionNumber);
}
return { success: true };
} catch (error) {
console.error('Failed to store vault:', error);
return { success: false, error: 'Failed to store vault' };
return { success: false, error: await t('common.errors.failedToStoreVault') };
}
}
@@ -64,7 +115,7 @@ export async function handleSyncVault(
const statusResponse = await webApi.getStatus();
const statusError = webApi.validateStatusResponse(statusResponse);
if (statusError !== null) {
return { success: false, error: statusError };
return { success: false, error: await t('common.errors.' + statusError) };
}
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
@@ -98,7 +149,7 @@ export async function handleGetVault(
if (!encryptedVault) {
console.error('Vault not available');
return { success: false, error: 'Vault not available' };
return { success: false, error: await t('common.errors.vaultNotAvailable') };
}
const decryptedVault = await EncryptionUtility.symmetricDecrypt(
@@ -115,7 +166,7 @@ export async function handleGetVault(
};
} catch (error) {
console.error('Failed to get vault:', error);
return { success: false, error: 'Failed to get vault' };
return { success: false, error: await t('common.errors.failedToGetVault') };
}
}
@@ -143,7 +194,7 @@ export async function handleGetCredentials(
const derivedKey = await storage.getItem('session:derivedKey') as string;
if (!derivedKey) {
return { success: false, error: 'Vault is locked' };
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
@@ -152,7 +203,7 @@ export async function handleGetCredentials(
return { success: true, credentials: credentials };
} catch (error) {
console.error('Error getting credentials:', error);
return { success: false, error: 'Failed to get credentials' };
return { success: false, error: await t('common.errors.failedToGetCredentials') };
}
}
@@ -165,7 +216,7 @@ export async function handleCreateIdentity(
const derivedKey = await storage.getItem('session:derivedKey') as string;
if (!derivedKey) {
return { success: false, error: 'Vault is locked' };
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
@@ -180,7 +231,7 @@ export async function handleCreateIdentity(
return { success: true };
} catch (error) {
console.error('Failed to create identity:', error);
return { success: false, error: 'Failed to create identity' };
return { success: false, error: await t('common.errors.failedToCreateIdentity') };
}
}
@@ -210,68 +261,43 @@ export async function getEmailAddressesForVault(
/**
* Get default email domain for a vault.
*/
export function handleGetDefaultEmailDomain(
) : Promise<stringResponse> {
return (async () : Promise<stringResponse> => {
export function handleGetDefaultEmailDomain(): Promise<stringResponse> {
return (async (): Promise<stringResponse> => {
try {
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[];
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[];
const sqliteClient = await createVaultSqliteClient();
const defaultEmailDomain = sqliteClient.getDefaultEmailDomain();
const defaultEmailDomain = sqliteClient.getDefaultEmailDomain(privateEmailDomains, publicEmailDomains);
/**
* Check if a domain is valid.
*/
const isValidDomain = (domain: string) : boolean => {
const isValid = (domain &&
domain !== 'DISABLED.TLD' &&
(privateEmailDomains.includes(domain) || publicEmailDomains.includes(domain))) as boolean;
return isValid;
};
// First check if the default domain that is configured in the vault is still valid.
if (defaultEmailDomain && isValidDomain(defaultEmailDomain)) {
return { success: true, value: defaultEmailDomain };
}
// If default domain is not valid, fall back to first available private domain.
const firstPrivate = privateEmailDomains.find(isValidDomain);
if (firstPrivate) {
return { success: true, value: firstPrivate };
}
// Return first valid public domain if no private domains are available.
const firstPublic = publicEmailDomains.find(isValidDomain);
if (firstPublic) {
return { success: true, value: firstPublic };
}
// Return null if no valid domains are found
return { success: true };
return { success: true, value: defaultEmailDomain ?? undefined };
} catch (error) {
console.error('Error getting default email domain:', error);
return { success: false, error: 'Failed to get default email domain' };
return { success: false, error: await t('common.errors.failedToGetDefaultEmailDomain') };
}
})();
}
/**
* Get the default identity language.
* Get the default identity settings.
*/
export async function handleGetDefaultIdentityLanguage(
) : Promise<stringResponse> {
export async function handleGetDefaultIdentitySettings(
) : Promise<IdentitySettingsResponse> {
try {
const sqliteClient = await createVaultSqliteClient();
const settingValue = sqliteClient.getDefaultIdentityLanguage();
const language = sqliteClient.getDefaultIdentityLanguage();
const gender = sqliteClient.getDefaultIdentityGender();
return { success: true, value: settingValue };
return {
success: true,
settings: {
language,
gender
}
};
} catch (error) {
console.error('Error getting default identity language:', error);
return { success: false, error: 'Failed to get default identity language' };
console.error('Error getting default identity settings:', error);
return { success: false, error: await t('common.errors.failedToGetDefaultIdentitySettings') };
}
}
@@ -287,7 +313,7 @@ export async function handleGetPasswordSettings(
return { success: true, settings: passwordSettings };
} catch (error) {
console.error('Error getting password settings:', error);
return { success: false, error: 'Failed to get password settings' };
return { success: false, error: await t('common.errors.failedToGetPasswordSettings') };
}
}
@@ -300,10 +326,82 @@ export async function handleGetDerivedKey(
return derivedKey;
}
/**
* Upload the vault to the server.
*/
export async function handleUploadVault(
message: any
) : Promise<messageVaultUploadResponse> {
try {
// Store the new vault blob in session storage.
await storage.setItem('session:encryptedVault', message.vaultBlob);
// Create new sqlite client which will use the new vault blob.
const sqliteClient = await createVaultSqliteClient();
// Upload the new vault to the server.
const response = await uploadNewVaultToServer(sqliteClient);
return { success: true, status: response.status, newRevisionNumber: response.newRevisionNumber };
} catch (error) {
console.error('Failed to upload vault:', error);
return { success: false, error: await t('common.errors.failedToUploadVault') };
}
}
/**
* Handle persisting form values to storage.
* Data is encrypted using the derived key for additional security.
*/
export async function handlePersistFormValues(data: any): Promise<void> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
if (!derivedKey) {
throw new Error(await t('common.errors.noDerivedKeyAvailable'));
}
// Always stringify the data properly
const serializedData = JSON.stringify(data);
const encryptedData = await EncryptionUtility.symmetricEncrypt(
serializedData,
derivedKey
);
await storage.setItem('session:persistedFormValues', encryptedData);
}
/**
* Handle retrieving persisted form values from storage.
* Data is decrypted using the derived key.
*/
export async function handleGetPersistedFormValues(): Promise<any | null> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
const encryptedData = await storage.getItem('session:persistedFormValues') as string | null;
if (!encryptedData || !derivedKey) {
return null;
}
try {
const decryptedData = await EncryptionUtility.symmetricDecrypt(
encryptedData,
derivedKey
);
return JSON.parse(decryptedData);
} catch (error) {
console.error('Failed to decrypt or parse persisted form values:', error);
return null;
}
}
/**
* Handle clearing persisted form values from storage.
*/
export async function handleClearPersistedFormValues(): Promise<void> {
await storage.removeItem('session:persistedFormValues');
}
/**
* Upload a new version of the vault to the server using the provided sqlite client.
*/
async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<void> {
async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<VaultPostResponse> {
const updatedVaultData = sqliteClient.exportToBase64();
const derivedKey = await storage.getItem('session:derivedKey') as string;
@@ -335,7 +433,7 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<void
client: '', // Empty on purpose, API will not use this for vault updates.
updatedAt: new Date().toISOString(),
username: username,
version: sqliteClient.getDatabaseVersion() ?? '0.0.0'
version: sqliteClient.getDatabaseVersion().version
};
const webApi = new WebApiService(() => {});
@@ -345,8 +443,10 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<void
if (response.status === 0) {
await storage.setItem('session:vaultRevisionNumber', response.newRevisionNumber);
} else {
throw new Error('Failed to upload new vault to server');
throw new Error(await t('common.errors.failedToUploadVaultToServer'));
}
return response;
}
/**
@@ -355,9 +455,8 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<void
async function createVaultSqliteClient() : Promise<SqliteClient> {
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
const derivedKey = await storage.getItem('session:derivedKey') as string;
if (!encryptedVault || !derivedKey) {
throw new Error('No vault or derived key found');
throw new Error(await t('common.errors.noVaultOrDerivedKeyFound'));
}
// Decrypt the vault.

View File

@@ -0,0 +1,188 @@
import '@/entrypoints/contentScript/style.css';
import { onMessage } from "webext-bridge/content-script";
import { injectIcon, popupDebounceTimeHasPassed, validateInputField } from '@/entrypoints/contentScript/Form';
import { isAutoShowPopupEnabled, openAutofillPopup, removeExistingPopup, createUpgradeRequiredPopup } from '@/entrypoints/contentScript/Popup';
import { FormDetector } from '@/utils/formDetector/FormDetector';
import { BoolResponse as messageBoolResponse } from '@/utils/types/messaging/BoolResponse';
import { t } from '@/i18n/StandaloneI18n';
import { defineContentScript } from '#imports';
import { createShadowRootUi } from '#imports';
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
allFrames: true,
matchAboutBlank: true,
runAt: 'document_start',
/**
* Main entry point for the content script.
*/
async main(ctx) {
if (ctx.isInvalid) {
return;
}
// Wait for 750ms to give the host page time to load and to increase the chance that the body is available and ready.
await new Promise(resolve => setTimeout(resolve, 750));
// Create a shadow root UI for isolation
const ui = await createShadowRootUi(ctx, {
name: 'aliasvault-ui',
position: 'inline',
anchor: 'body',
/**
* Handle mount.
*/
onMount(container) {
/**
* Handle input field focus.
*/
const handleFocusIn = async (e: FocusEvent) : Promise<void> => {
if (ctx.isInvalid) {
return;
}
// Check if element itself, html or body has av-disable attribute like av-disable="true"
const avDisable = ((e.target as HTMLElement).getAttribute('av-disable') ?? document.body?.getAttribute('av-disable') ?? document.documentElement.getAttribute('av-disable')) === 'true';
if (avDisable) {
return;
}
const { isValid, inputElement } = validateInputField(e.target as Element);
if (isValid && inputElement) {
const formDetector = new FormDetector(document, inputElement);
if (!formDetector.containsLoginForm()) {
return;
}
// Only show popup for autofill-triggerable fields
if (!formDetector.isAutofillTriggerableField()) {
return;
}
// Only inject icon and show popup if autofill popup is enabled
if (await isAutoShowPopupEnabled()) {
injectIcon(inputElement, container);
// Only show popup if debounce time has passed
if (popupDebounceTimeHasPassed()) {
await showPopupWithAuthCheck(inputElement, container);
}
}
}
};
// Listen for input field focus in the main document
document.addEventListener('focusin', handleFocusIn);
// Check if currently something is focused, if so, apply check for that element
const currentFocusedElement = document.activeElement;
if (currentFocusedElement) {
showPopupForElement(currentFocusedElement);
}
// Listen for popstate events (back/forward navigation)
window.addEventListener('popstate', () => {
if (ctx.isInvalid) {
return;
}
removeExistingPopup(container);
});
// Listen for messages from the background script
onMessage('OPEN_AUTOFILL_POPUP', async (message: { data: { elementIdentifier: string } }) : Promise<messageBoolResponse> => {
const { data } = message;
const { elementIdentifier } = data;
if (!elementIdentifier) {
return { success: false, error: 'No element identifier provided' };
}
const target = document.getElementById(elementIdentifier) ?? document.getElementsByName(elementIdentifier)[0];
await showPopupForElement(target, true);
return { success: true };
});
/**
* Show popup for element.
*/
async function showPopupForElement(element: Element, forceShow: boolean = false) : Promise<void> {
const { isValid, inputElement } = validateInputField(element);
if (!isValid || !inputElement) {
return;
}
const formDetector = new FormDetector(document, inputElement);
if (!formDetector.containsLoginForm()) {
return;
}
/**
* By default we check if the popup is not disabled (for current site) and if the field is autofill-triggerable
* but if forceShow is true, we show the popup regardless.
*/
const canShowPopup = forceShow || (await isAutoShowPopupEnabled() && formDetector.isAutofillTriggerableField());
if (canShowPopup) {
injectIcon(inputElement, container);
await showPopupWithAuthCheck(inputElement, container);
}
}
/**
* Show popup with auth check.
*/
async function showPopupWithAuthCheck(inputElement: HTMLInputElement, container: HTMLElement) : Promise<void> {
try {
// Check auth status and pending migrations in a single call
const { sendMessage } = await import('webext-bridge/content-script');
const authStatus = await sendMessage('CHECK_AUTH_STATUS', {}, 'background') as {
isLoggedIn: boolean,
isVaultLocked: boolean,
hasPendingMigrations: boolean,
error?: string
};
if (authStatus.isVaultLocked) {
// Vault is locked, show vault locked popup
const { createVaultLockedPopup } = await import('@/entrypoints/contentScript/Popup');
createVaultLockedPopup(inputElement, container);
return;
}
if (authStatus.hasPendingMigrations) {
// Show upgrade required popup
await createUpgradeRequiredPopup(inputElement, container, await t('content.vaultUpgradeRequired'));
return;
}
if (authStatus.error) {
// Show upgrade required popup for version-related errors
await createUpgradeRequiredPopup(inputElement, container, authStatus.error);
return;
}
// No upgrade required, show normal autofill popup
openAutofillPopup(inputElement, container);
} catch (error) {
console.error('Error checking vault status:', error);
// Fall back to normal autofill popup if check fails
openAutofillPopup(inputElement, container);
}
}
},
});
// Mount the UI to create the shadow root
ui.autoMount();
},
});

View File

@@ -1,5 +1,5 @@
import { CombinedStopWords } from "@/utils/formDetector/FieldPatterns";
import { Credential } from "../../utils/types/Credential";
import type { Credential } from '@/utils/dist/shared/models/vault';
import { CombinedStopWords } from '@/utils/formDetector/FieldPatterns';
type CredentialWithPriority = Credential & {
priority: number;

View File

@@ -1,7 +1,8 @@
import { FormDetector } from "../../utils/formDetector/FormDetector";
import { FormFiller } from "../../utils/formDetector/FormFiller";
import { Credential } from "../../utils/types/Credential";
import { openAutofillPopup } from "./Popup";
import { openAutofillPopup } from '@/entrypoints/contentScript/Popup';
import type { Credential } from '@/utils/dist/shared/models/vault';
import { FormDetector } from '@/utils/formDetector/FormDetector';
import { FormFiller } from '@/utils/formDetector/FormFiller';
/**
* Global timestamp to track popup debounce time.

View File

File diff suppressed because it is too large Load Diff

View File

@@ -196,6 +196,7 @@ body {
justify-content: center;
gap: 4px;
transition: background-color 0.2s ease;
min-width: 0px;
}
.av-button:hover {
@@ -231,6 +232,7 @@ body {
outline: none;
line-height: 1;
text-align: center;
min-width: 0px;
}
.av-search-input::placeholder {
@@ -297,6 +299,71 @@ body {
border: 1px solid #6f6f6f;
}
/* Upgrade Required Popup */
.av-upgrade-required {
padding: 12px 16px;
position: relative;
}
.av-upgrade-required:hover {
background-color: #374151;
}
.av-upgrade-required-container {
display: flex;
align-items: center;
padding-right: 32px;
width: 100%;
transition: background-color 0.2s ease;
border-radius: 4px;
}
.av-upgrade-required-message {
color: #d1d5db;
font-size: 14px;
flex-grow: 1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.av-upgrade-required-button {
background: none;
border: none;
cursor: pointer;
padding: 4px;
padding-right: 28px;
display: flex;
align-items: center;
justify-content: center;
color: #f59e0b;
border-radius: 4px;
margin-left: 8px;
}
.av-upgrade-required-close {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
padding: 4px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
border: 1px solid #6f6f6f;
}
.av-icon-upgrade {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Create Name Popup */
.av-create-popup-overlay {
position: fixed;
@@ -787,4 +854,331 @@ body {
margin: 4px 0 0;
font-size: 12px;
color: #9ca3af;
}
/* Context Menu */
.av-context-menu {
position: fixed;
background: #1f2937;
border: 1px solid #374151;
border-radius: 6px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
z-index: 2147483647;
min-width: 180px;
padding: 4px;
margin-top: 4px;
}
.av-context-menu-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
border: none;
background: none;
color: #e5e7eb;
font-size: 14px;
text-align: left;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.av-context-menu-item:hover {
background-color: #374151;
}
.av-context-menu-item .av-icon {
width: 16px;
height: 16px;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Password Configuration Styles */
.av-password-length-container {
margin-top: 12px;
padding-top: 8px;
}
.av-password-length-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.av-password-length-header label {
font-size: 0.875rem;
font-weight: 500;
color: #9ca3af;
margin: 0;
}
.av-password-length-controls {
display: flex;
align-items: center;
gap: 8px;
}
.av-password-length-value {
font-size: 0.875rem;
color: #e5e7eb;
font-family: 'Courier New', monospace;
min-width: 24px;
text-align: center;
}
.av-password-config-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
background: none;
border: none;
border-radius: 4px;
cursor: pointer;
color: #9ca3af;
transition: color 0.2s ease, background-color 0.2s ease;
}
.av-password-config-btn:hover {
color: #e5e7eb;
background-color: rgba(75, 85, 99, 0.3);
}
.av-password-config-btn .av-icon {
width: 16px;
height: 16px;
}
.av-password-length-slider {
width: 100%;
height: 8px;
background: #374151;
border-radius: 4px;
appearance: none;
cursor: pointer;
outline: none;
}
.av-password-length-slider::-webkit-slider-thumb {
appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #d68338;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.av-password-length-slider::-moz-range-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: #d68338;
cursor: pointer;
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Password Config Dialog */
.av-password-config-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 2147483647;
display: flex;
align-items: center;
justify-content: center;
}
.av-password-config-dialog {
background: #1f2937;
border: 1px solid #374151;
border-radius: 8px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
width: 400px;
max-width: 90vw;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.av-password-config-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #374151;
}
.av-password-config-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #f8f9fa;
}
.av-password-config-close {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #9ca3af;
border-radius: 4px;
transition: color 0.2s ease, background-color 0.2s ease;
}
.av-password-config-close:hover {
color: #e5e7eb;
background-color: #374151;
}
.av-password-config-close .av-icon {
width: 16px;
height: 16px;
}
.av-password-config-content {
padding: 20px;
}
.av-password-preview-section {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
}
.av-password-config-preview {
flex: 1;
padding: 10px 12px;
border: 1px solid #374151;
border-radius: 6px;
background: #374151;
color: #f8f9fa;
font-size: 14px;
font-family: 'Courier New', monospace;
outline: none;
}
.av-password-config-refresh {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
background: #374151;
border: none;
border-radius: 6px;
cursor: pointer;
color: #e5e7eb;
transition: background-color 0.2s ease;
}
.av-password-config-refresh:hover {
background-color: #4b5563;
}
.av-password-config-refresh .av-icon {
width: 16px;
height: 16px;
}
.av-password-config-options {
margin-bottom: 20px;
}
.av-password-config-toggles {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 16px;
}
.av-password-config-toggle {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
background: #374151;
border: none;
border-radius: 6px;
cursor: pointer;
color: #9ca3af;
transition: all 0.2s ease;
font-family: 'Courier New', monospace;
font-size: 14px;
font-weight: 500;
}
.av-password-config-toggle:hover {
background-color: #4b5563;
}
.av-password-config-toggle.active {
background-color: #d68338;
color: white;
}
.av-password-config-toggle.active:hover {
background-color: #c97731;
}
.av-password-config-checkbox {
display: flex;
align-items: center;
gap: 8px;
}
.av-password-config-checkbox label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 14px;
color: #e5e7eb;
margin: 0;
}
.av-password-config-checkbox input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: #d68338;
cursor: pointer;
}
.av-password-config-actions {
display: flex;
justify-content: flex-end;
}
.av-password-config-use {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 16px;
background: #6b7280;
border: none;
border-radius: 6px;
cursor: pointer;
color: white;
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.av-password-config-use:hover {
background-color: #4b5563;
}
.av-password-config-use .av-icon {
width: 16px;
height: 16px;
}

View File

@@ -0,0 +1,132 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
import BottomNav from '@/entrypoints/popup/components/Layout/BottomNav';
import Header from '@/entrypoints/popup/components/Layout/Header';
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import { useHeaderButtons } from '@/entrypoints/popup/context/HeaderButtonsContext';
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
import { NavigationProvider } from '@/entrypoints/popup/context/NavigationContext';
import AuthSettings from '@/entrypoints/popup/pages/AuthSettings';
import CredentialAddEdit from '@/entrypoints/popup/pages/CredentialAddEdit';
import CredentialDetails from '@/entrypoints/popup/pages/CredentialDetails';
import CredentialsList from '@/entrypoints/popup/pages/CredentialsList';
import EmailDetails from '@/entrypoints/popup/pages/EmailDetails';
import EmailsList from '@/entrypoints/popup/pages/EmailsList';
import Index from '@/entrypoints/popup/pages/Index';
import Login from '@/entrypoints/popup/pages/Login';
import Logout from '@/entrypoints/popup/pages/Logout';
import Reinitialize from '@/entrypoints/popup/pages/Reinitialize';
import Settings from '@/entrypoints/popup/pages/Settings';
import Unlock from '@/entrypoints/popup/pages/Unlock';
import UnlockSuccess from '@/entrypoints/popup/pages/UnlockSuccess';
import Upgrade from '@/entrypoints/popup/pages/Upgrade';
import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
import '@/entrypoints/popup/style.css';
/**
* Route configuration.
*/
type RouteConfig = {
path: string;
element: React.ReactNode;
showBackButton?: boolean;
title?: string;
};
/**
* App component.
*/
const App: React.FC = () => {
const { t } = useTranslation();
const authContext = useAuth();
const { isInitialLoading } = useLoading();
const [isLoading, setIsLoading] = useMinDurationLoading(true, 150);
const [message, setMessage] = useState<string | null>(null);
const { headerButtons } = useHeaderButtons();
// Move routes definition to useMemo to prevent recreation on every render
const routes: RouteConfig[] = React.useMemo(() => [
{ path: '/', element: <Index />, showBackButton: false },
{ path: '/reinitialize', element: <Reinitialize />, showBackButton: false },
{ path: '/login', element: <Login />, showBackButton: false },
{ path: '/unlock', element: <Unlock />, showBackButton: false },
{ path: '/unlock-success', element: <UnlockSuccess />, showBackButton: false },
{ path: '/upgrade', element: <Upgrade />, showBackButton: false },
{ path: '/auth-settings', element: <AuthSettings />, showBackButton: true, title: t('settings.title') },
{ path: '/credentials', element: <CredentialsList />, showBackButton: false },
{ path: '/credentials/add', element: <CredentialAddEdit />, showBackButton: true, title: t('credentials.addCredential') },
{ path: '/credentials/:id', element: <CredentialDetails />, showBackButton: true, title: t('credentials.credentialDetails') },
{ path: '/credentials/:id/edit', element: <CredentialAddEdit />, showBackButton: true, title: t('credentials.editCredential') },
{ path: '/emails', element: <EmailsList />, showBackButton: false },
{ path: '/emails/:id', element: <EmailDetails />, showBackButton: true, title: t('emails.title') },
{ path: '/settings', element: <Settings />, showBackButton: false },
{ path: '/logout', element: <Logout />, showBackButton: false },
], [t]);
useEffect(() => {
if (!isInitialLoading) {
setIsLoading(false);
}
}, [isInitialLoading, setIsLoading]);
/**
* Print global message if it exists.
*/
useEffect(() => {
if (authContext.globalMessage) {
setMessage(authContext.globalMessage);
} else {
setMessage(null);
}
}, [authContext, authContext.globalMessage]);
return (
<Router>
<NavigationProvider>
<div className="min-h-screen min-w-[350px] bg-white dark:bg-gray-900 flex flex-col max-h-[600px]">
{isLoading && (
<div className="fixed inset-0 bg-white dark:bg-gray-900 z-50 flex items-center justify-center">
<LoadingSpinner />
</div>
)}
<Header
routes={routes}
rightButtons={headerButtons}
/>
<main
className="flex-1 overflow-y-auto bg-gray-100 dark:bg-gray-900"
style={{
paddingTop: '64px',
height: 'calc(100% - 120px)',
}}
>
<div className="p-4 mb-16">
{message && (
<p className="text-red-500 mb-4">{message}</p>
)}
<Routes>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
element={route.element}
/>
))}
</Routes>
</div>
</main>
<BottomNav />
</div>
</NavigationProvider>
</Router>
);
};
export default App;

View File

@@ -0,0 +1,81 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import type { Credential } from '@/utils/dist/shared/models/vault';
import SqliteClient from '@/utils/SqliteClient';
type CredentialCardProps = {
credential: Credential;
};
/**
* CredentialCard component
*
* This component displays a credential card with a service name, username, and email.
* It allows the user to navigate to the credential details page when clicked.
*
*/
const CredentialCard: React.FC<CredentialCardProps> = ({ credential }) => {
const navigate = useNavigate();
/**
* Get the display text for the credential
* @param cred - The credential to get the display text for
* @returns The display text for the credential
*/
const getDisplayText = (cred: Credential): string => {
let returnValue = '';
// Show username if available
if (cred.Username) {
returnValue = cred.Username;
}
// Show email if username is not available
if (cred.Alias?.Email) {
returnValue = cred.Alias.Email;
}
// Trim the return value to max. 33 characters.
return returnValue.length > 33 ? returnValue.slice(0, 30) + '...' : returnValue;
};
/**
* Get the service name for a credential, trimming it to maximum length so it doesn't overflow the UI.
*/
const getCredentialServiceName = (cred: Credential): string => {
let returnValue = 'Untitled';
if (cred.ServiceName) {
returnValue = cred.ServiceName;
}
// Trim the return value to max. 33 characters.
return returnValue.length > 33 ? returnValue.slice(0, 30) + '...' : returnValue;
};
return (
<li>
<button
onClick={() => navigate(`/credentials/${credential.Id}`)}
className="w-full p-2 border dark:border-gray-600 rounded flex items-center bg-white dark:bg-gray-800 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<img
src={SqliteClient.imgSrcFromBytes(credential.Logo)}
alt={credential.ServiceName}
className="w-8 h-8 mr-2 flex-shrink-0"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = '/assets/images/service-placeholder.webp';
}}
/>
<div className="text-left">
<p className="font-medium text-gray-900 dark:text-white">{getCredentialServiceName(credential)}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">{getDisplayText(credential)}</p>
</div>
</button>
</li>
);
};
export default CredentialCard;

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/FormInputCopyToClipboard';
import { IdentityHelperUtils } from '@/utils/dist/shared/identity-generator';
import type { Credential } from '@/utils/dist/shared/models/vault';
type AliasBlockProps = {
credential: Credential;
}
/**
* Render the alias block.
*/
const AliasBlock: React.FC<AliasBlockProps> = ({ credential }) => {
const { t } = useTranslation();
const hasFirstName = Boolean(credential.Alias?.FirstName?.trim());
const hasLastName = Boolean(credential.Alias?.LastName?.trim());
const hasNickName = Boolean(credential.Alias?.NickName?.trim());
const hasBirthDate = IdentityHelperUtils.isValidBirthDate(credential.Alias?.BirthDate);
if (!hasFirstName && !hasLastName && !hasNickName && !hasBirthDate) {
return null;
}
return (
<div className="space-y-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">{t('common.alias')}</h2>
{(hasFirstName || hasLastName) && (
<FormInputCopyToClipboard
id="fullName"
label={t('common.fullName')}
value={[credential.Alias?.FirstName, credential.Alias?.LastName].filter(Boolean).join(' ')}
/>
)}
{hasFirstName && (
<FormInputCopyToClipboard
id="firstName"
label={t('common.firstName')}
value={credential.Alias?.FirstName ?? ''}
/>
)}
{hasLastName && (
<FormInputCopyToClipboard
id="lastName"
label={t('common.lastName')}
value={credential.Alias?.LastName ?? ''}
/>
)}
{hasBirthDate && (
<FormInputCopyToClipboard
id="birthDate"
label={t('common.birthDate')}
value={IdentityHelperUtils.normalizeBirthDateForDisplay(credential.Alias?.BirthDate)}
/>
)}
{hasNickName && (
<FormInputCopyToClipboard
id="nickName"
label={t('common.nickname')}
value={credential.Alias?.NickName ?? ''}
/>
)}
</div>
);
};
export default AliasBlock;

View File

@@ -0,0 +1,120 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import type { Attachment } from '@/utils/dist/shared/models/vault';
type AttachmentBlockProps = {
credentialId: string;
}
/**
* This component shows attachments for a credential.
*/
const AttachmentBlock: React.FC<AttachmentBlockProps> = ({ credentialId }) => {
const { t } = useTranslation();
const [attachments, setAttachments] = useState<Attachment[]>([]);
const [loading, setLoading] = useState(true);
const dbContext = useDb();
/**
* Downloads an attachment file.
*/
const downloadAttachment = (attachment: Attachment): void => {
try {
// Convert Uint8Array or number[] to Uint8Array
const byteArray = attachment.Blob instanceof Uint8Array
? attachment.Blob
: new Uint8Array(attachment.Blob);
// Create blob and download
const blob = new Blob([byteArray as BlobPart]);
const url = URL.createObjectURL(blob);
// Create temporary download link
const a = document.createElement('a');
a.href = url;
a.download = attachment.Filename;
document.body.appendChild(a);
a.click();
// Cleanup
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading attachment:', error);
}
};
useEffect(() => {
/**
* Loads the attachments for the credential.
*/
const loadAttachments = async (): Promise<void> => {
if (!dbContext?.sqliteClient) {
return;
}
try {
const attachmentList = dbContext.sqliteClient.getAttachmentsForCredential(credentialId);
setAttachments(attachmentList);
} catch (error) {
console.error('Error loading attachments:', error);
} finally {
setLoading(false);
}
};
loadAttachments();
}, [credentialId, dbContext?.sqliteClient]);
if (loading) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">{t('common.attachments')}</h2>
{t('common.loadingAttachments')}
</div>
);
}
if (attachments.length === 0) {
return null;
}
return (
<div className="mb-4">
<div className="space-y-2">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('common.attachments')}</h2>
<div className="grid grid-cols-1 gap-2">
{attachments.map(attachment => (
<button
key={attachment.Id}
className="w-full text-left p-2 ps-3 pe-3 rounded bg-white dark:bg-gray-800 shadow hover:shadow-md transition-all border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700"
onClick={() => downloadAttachment(attachment)}
aria-label={`Download ${attachment.Filename}`}
>
<div className="flex justify-between items-center gap-2">
<div className="flex items-center flex-1">
<div className="flex flex-col">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">{attachment.Filename}</h4>
<span className="text-xs text-gray-500 dark:text-gray-400">
{new Date(attachment.CreatedAt).toLocaleDateString()}
</span>
</div>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V4" />
</svg>
</div>
</div>
</button>
))}
</div>
</div>
</div>
);
};
export default AttachmentBlock;

View File

@@ -0,0 +1,146 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { Attachment } from '@/utils/dist/shared/models/vault';
type AttachmentUploaderProps = {
attachments: Attachment[];
onAttachmentsChange: (attachments: Attachment[]) => void;
}
/**
* This component allows uploading and managing attachments for a credential.
*/
const AttachmentUploader: React.FC<AttachmentUploaderProps> = ({
attachments,
onAttachmentsChange
}) => {
const { t } = useTranslation();
const [statusMessage, setStatusMessage] = useState<string>('');
/**
* Handles file selection and upload.
*/
const handleFileSelection = async (event: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
const files = event.target.files;
if (!files || files.length === 0) {
return;
}
setStatusMessage('Uploading...');
try {
const newAttachments = [...attachments];
for (const file of Array.from(files)) {
const arrayBuffer = await file.arrayBuffer();
const byteArray = new Uint8Array(arrayBuffer);
const attachment: Attachment = {
Id: crypto.randomUUID(),
Filename: file.name,
Blob: byteArray,
CredentialId: '', // Will be set when saving credential
CreatedAt: new Date().toISOString(),
UpdatedAt: new Date().toISOString(),
IsDeleted: false,
};
newAttachments.push(attachment);
}
onAttachmentsChange(newAttachments);
setStatusMessage('Files uploaded successfully.');
// Clear status message after 3 seconds
setTimeout(() => setStatusMessage(''), 3000);
} catch (error) {
console.error('Error uploading files:', error);
setStatusMessage('Error uploading files.');
setTimeout(() => setStatusMessage(''), 3000);
}
// Reset file input
event.target.value = '';
};
/**
* Deletes an attachment.
*/
const deleteAttachment = (attachmentToDelete: Attachment): void => {
try {
const updatedAttachments = [...attachments];
// Remove attachment from array
const index = updatedAttachments.findIndex(a => a.Id === attachmentToDelete.Id);
if (index !== -1) {
updatedAttachments.splice(index, 1);
}
onAttachmentsChange(updatedAttachments);
setStatusMessage('Attachment deleted successfully.');
setTimeout(() => setStatusMessage(''), 3000);
} catch (error) {
console.error('Error deleting attachment:', error);
setStatusMessage('Error deleting attachment.');
setTimeout(() => setStatusMessage(''), 3000);
}
};
const activeAttachments = attachments.filter(a => !a.IsDeleted);
return (
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">{t('common.attachments')}</h2>
<div className="space-y-4">
<div>
<input
type="file"
multiple
onChange={handleFileSelection}
className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
/>
{statusMessage && (
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">{statusMessage}</p>
)}
</div>
{activeAttachments.length > 0 && (
<div>
<h4 className="mb-2 text-md font-medium text-gray-900 dark:text-white">Current attachments:</h4>
<div className="space-y-2">
{activeAttachments.map(attachment => (
<div
key={attachment.Id}
className="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-700 rounded border border-gray-200 dark:border-gray-600"
>
<div className="flex flex-col">
<span className="text-sm font-medium text-gray-900 dark:text-white">
{attachment.Filename}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400">
{new Date(attachment.CreatedAt).toLocaleDateString()}
</span>
</div>
<button
type="button"
onClick={() => deleteAttachment(attachment)}
className="text-red-500 hover:text-red-700 focus:outline-none"
aria-label={`Delete ${attachment.Filename}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default AttachmentUploader;

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { EmailPreview } from '@/entrypoints/popup/components/EmailPreview';
import { useDb } from '@/entrypoints/popup/context/DbContext';
type EmailBlockProps = {
email: string;
}
/**
* Render the email block.
*/
const EmailBlock: React.FC<EmailBlockProps> = ({ email }) => {
const dbContext = useDb();
/**
* Check if the email domain is supported.
*/
const isEmailDomainSupported = async (email: string): Promise<boolean> => {
const domain = email.split('@')[1]?.toLowerCase();
if (!domain) {
return false;
}
const vaultMetadata = await dbContext.getVaultMetadata();
const publicDomains = vaultMetadata?.publicEmailDomains ?? [];
const privateDomains = vaultMetadata?.privateEmailDomains ?? [];
return [...publicDomains, ...privateDomains].some(supportedDomain =>
domain === supportedDomain || domain.endsWith(`.${supportedDomain}`)
);
};
if (!isEmailDomainSupported(email)) {
return null;
}
return (
<>
{<EmailPreview email={email} />}
</>
);
};
export default EmailBlock;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import type { Credential } from '@/utils/dist/shared/models/vault';
import SqliteClient from '@/utils/SqliteClient';
type HeaderBlockProps = {
credential: Credential;
}
/**
* Render the header block.
*/
const HeaderBlock: React.FC<HeaderBlockProps> = ({ credential }) => (
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
src={SqliteClient.imgSrcFromBytes(credential.Logo)}
alt={credential.ServiceName}
className="w-12 h-12 rounded-lg mr-4"
/>
<div>
<h1 className="text-lg font-bold text-gray-900 dark:text-white">{credential.ServiceName}</h1>
{credential.ServiceUrl && (
<a
href={credential.ServiceUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 break-all"
>
{credential.ServiceUrl}
</a>
)}
</div>
</div>
</div>
);
export default HeaderBlock;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/FormInputCopyToClipboard';
import type { Credential } from '@/utils/dist/shared/models/vault';
type LoginCredentialsBlockProps = {
credential: Credential;
}
/**
* Render the login credentials block.
*/
const LoginCredentialsBlock: React.FC<LoginCredentialsBlockProps> = ({ credential }) => {
const { t } = useTranslation();
const email = credential.Alias?.Email?.trim();
const username = credential.Username?.trim();
const password = credential.Password?.trim();
if (!email && !username && !password) {
return null;
}
return (
<div className="space-y-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">{t('common.loginCredentials')}</h2>
{email && (
<FormInputCopyToClipboard
id="email"
label={t('common.email')}
value={email}
/>
)}
{username && (
<FormInputCopyToClipboard
id="username"
label={t('common.username')}
value={username}
/>
)}
{password && (
<FormInputCopyToClipboard
id="password"
label={t('common.password')}
value={password}
type="password"
/>
)}
</div>
);
};
export default LoginCredentialsBlock;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
type NotesBlockProps = {
notes: string | undefined;
}
/**
* Convert URLs in text to clickable links.
*/
const convertUrlsToLinks = (text: string): string => {
const urlPattern = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/g;
return text.replace(urlPattern, (url) => {
const href = url.startsWith('http') ? url : `http://${url}`;
return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">${url}</a>`;
});
};
/**
* Render the notes block.
*/
const NotesBlock: React.FC<NotesBlockProps> = ({ notes }) => {
const { t } = useTranslation();
if (!notes) {
return null;
}
const formattedNotes = convertUrlsToLinks(notes);
return (
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">{t('common.notes')}</h2>
<div className="p-4 bg-gray-50 rounded-lg dark:bg-gray-700">
<p
className="text-gray-900 dark:text-gray-100 whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: formattedNotes }}
/>
</div>
</div>
);
};
export default NotesBlock;

View File

@@ -1,16 +1,20 @@
import React, { useState, useEffect } from 'react';
import { useDb } from '../context/DbContext';
import { TotpCode } from '../../../utils/types/TotpCode';
import * as OTPAuth from 'otpauth';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
type TotpViewerProps = {
import { useDb } from '@/entrypoints/popup/context/DbContext';
import type { TotpCode } from '@/utils/dist/shared/models/vault';
type TotpBlockProps = {
credentialId: string;
}
/**
* This component shows TOTP codes for a credential.
*/
export const TotpViewer: React.FC<TotpViewerProps> = ({ credentialId }) => {
const TotpBlock: React.FC<TotpBlockProps> = ({ credentialId }) => {
const { t } = useTranslation();
const [totpCodes, setTotpCodes] = useState<TotpCode[]>([]);
const [loading, setLoading] = useState(true);
const [currentCodes, setCurrentCodes] = useState<Record<string, string>>({});
@@ -136,8 +140,8 @@ export const TotpViewer: React.FC<TotpViewerProps> = ({ credentialId }) => {
if (loading) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">Two-factor authentication</h2>
Loading TOTP codes...
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">{t('common.twoFactorAuthentication')}</h2>
{t('common.loadingTotpCodes')}
</div>
);
}
@@ -149,7 +153,7 @@ export const TotpViewer: React.FC<TotpViewerProps> = ({ credentialId }) => {
return (
<div className="mb-4">
<div className="space-y-2">
<h2 className="text-base font-semibold text-gray-900 dark:text-white">Two-factor authentication</h2>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('common.twoFactorAuthentication')}</h2>
<div className="grid grid-cols-1 gap-2">
{totpCodes.map(totpCode => (
<button
@@ -169,7 +173,7 @@ export const TotpViewer: React.FC<TotpViewerProps> = ({ credentialId }) => {
</span>
<div className="text-xs">
{copiedId === totpCode.Id ? (
<span className="text-green-600 dark:text-green-400">Copied!</span>
<span className="text-green-600 dark:text-green-400">{t('common.copied')}</span>
) : (
<span className="text-gray-500 dark:text-gray-400">{getRemainingSeconds()}s</span>
)}
@@ -189,4 +193,6 @@ export const TotpViewer: React.FC<TotpViewerProps> = ({ credentialId }) => {
</div>
</div>
);
};
};
export default TotpBlock;

View File

@@ -0,0 +1,17 @@
import AliasBlock from './AliasBlock';
import AttachmentBlock from './AttachmentBlock';
import EmailBlock from './EmailBlock';
import HeaderBlock from './HeaderBlock';
import LoginCredentialsBlock from './LoginCredentialsBlock';
import NotesBlock from './NotesBlock';
import TotpBlock from './TotpBlock';
export {
HeaderBlock,
EmailBlock,
TotpBlock,
LoginCredentialsBlock,
AliasBlock,
NotesBlock,
AttachmentBlock
};

View File

@@ -0,0 +1,286 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useWebApi } from '@/entrypoints/popup/context/WebApiContext';
import { AppInfo } from '@/utils/AppInfo';
import type { ApiErrorResponse, MailboxEmail } from '@/utils/dist/shared/models/webapi';
import { EncryptionUtility } from '@/utils/EncryptionUtility';
import { storage } from '#imports';
type EmailPreviewProps = {
email: string;
}
/**
* This component shows a preview of the latest emails in the inbox.
*/
export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
const { t } = useTranslation();
const [emails, setEmails] = useState<MailboxEmail[]>([]);
const [displayedEmails, setDisplayedEmails] = useState<MailboxEmail[]>([]);
const [loading, setLoading] = useState(true);
const [lastEmailId, setLastEmailId] = useState<number>(0);
const [isSpamOk, setIsSpamOk] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isSupportedDomain, setIsSupportedDomain] = useState(false);
const [displayedCount, setDisplayedCount] = useState(2);
const webApi = useWebApi();
const dbContext = useDb();
const emailsPerLoad = 3;
const canLoadMore = displayedCount < emails.length;
/**
* Updates the displayed emails based on the current count.
*/
const updateDisplayedEmails = (allEmails: MailboxEmail[], count: number) : void => {
const displayed = allEmails.slice(0, count);
setDisplayedEmails(displayed);
};
/**
* Loads more emails.
*/
const loadMoreEmails = (): void => {
const newCount = Math.min(displayedCount + emailsPerLoad, emails.length);
setDisplayedCount(newCount);
updateDisplayedEmails(emails, newCount);
};
/**
* Checks if the email is a public domain.
*/
const isPublicDomain = async (emailAddress: string): Promise<boolean> => {
// Get metadata from storage
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[] ?? [];
return publicEmailDomains.some(domain => emailAddress.toLowerCase().endsWith(domain));
};
/**
* Checks if the email is a private domain.
*/
const isPrivateDomain = async (emailAddress: string): Promise<boolean> => {
// Get metadata from storage
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[] ?? [];
return privateEmailDomains.some(domain => emailAddress.toLowerCase().endsWith(domain));
};
useEffect(() => {
/**
* Loads the latest emails from the server and decrypts them locally if needed.
*/
const loadEmails = async (): Promise<void> => {
try {
setError(null);
const isPublic = await isPublicDomain(email);
const isPrivate = await isPrivateDomain(email);
const isSupported = isPublic || isPrivate;
setIsSpamOk(isPublic);
setIsSupportedDomain(isSupported);
if (!isSupported) {
return;
}
if (isPublic) {
// For public domains (SpamOK), use the SpamOK API directly
const emailPrefix = email.split('@')[0];
const response = await fetch(`https://api.spamok.com/v2/EmailBox/${emailPrefix}`, {
headers: {
'X-Asdasd-Platform-Id': 'av-chrome',
'X-Asdasd-Platform-Version': AppInfo.VERSION,
}
});
if (!response.ok) {
setError(t('emails.errors.emailLoadError'));
return;
}
const data = await response.json();
// Store all emails, sorted by date
const allMails = data?.mails
?.toSorted((a: MailboxEmail, b: MailboxEmail) =>
new Date(b.dateSystem).getTime() - new Date(a.dateSystem).getTime()) ?? [];
if (loading && allMails.length > 0) {
setLastEmailId(allMails[0].id);
}
// Only update emails if they actually changed to preserve displayedCount
setEmails(prevEmails => {
const emailsChanged = JSON.stringify(prevEmails.map(e => e.id)) !== JSON.stringify(allMails.map(e => e.id));
if (emailsChanged) {
updateDisplayedEmails(allMails, displayedCount);
return allMails;
}
return prevEmails;
});
} else if (isPrivate) {
// For private domains, use existing encrypted email logic
try {
/**
* We use authFetch here because we don't want to the inner method to throw an error if HTTP status is not 200.
* Instead we want to catch the error ourselves.
*/
const response = await webApi.authFetch(`EmailBox/${email}`, { method: 'GET' }, true, false);
try {
const data = response as { mails: MailboxEmail[] };
// Store all emails, sorted by date
const allMails = data.mails
.toSorted((a, b) => new Date(b.dateSystem).getTime() - new Date(a.dateSystem).getTime());
if (allMails) {
// Loop through all emails and decrypt them locally
const decryptedEmails: MailboxEmail[] = await EncryptionUtility.decryptEmailList(
allMails,
dbContext.sqliteClient!.getAllEncryptionKeys()
);
if (loading && decryptedEmails.length > 0) {
setLastEmailId(decryptedEmails[0].id);
}
// Only update emails if they actually changed to preserve displayedCount
setEmails(prevEmails => {
const emailsChanged = JSON.stringify(prevEmails.map(e => e.id)) !== JSON.stringify(decryptedEmails.map(e => e.id));
if (emailsChanged) {
updateDisplayedEmails(decryptedEmails, displayedCount);
return decryptedEmails;
}
return prevEmails;
});
}
} catch {
// Try to parse as error response instead
const apiErrorResponse = response as ApiErrorResponse;
setError(t('emails.apiErrors.' + apiErrorResponse?.code));
return;
}
} catch {
setError(t('emails.errors.emailLoadError'));
return;
}
}
} catch (err) {
console.error('Error loading emails:', err);
setError(t('emails.errors.emailUnexpectedError'));
}
setLoading(false);
};
loadEmails();
// Set up auto-refresh interval
const interval = setInterval(loadEmails, 2000);
return () : void => clearInterval(interval);
}, [email, loading, webApi, dbContext, t, displayedCount]);
// Don't render anything if the domain is not supported
if (!isSupportedDomain) {
return null;
}
if (error) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('common.recentEmails')}</h2>
</div>
<div className="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
</div>
);
}
if (loading) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('common.recentEmails')}</h2>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
</div>
{t('common.loadingEmails')}
</div>
);
}
if (emails.length === 0) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('common.recentEmails')}</h2>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
</div>
{t('emails.noEmails')}
</div>
);
}
return (
<div className="space-y-2 mb-4">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">{t('common.recentEmails')}</h2>
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
</div>
{displayedEmails.map((mail) => (
isSpamOk ? (
<a
key={mail.id}
href={`https://spamok.com/${email.split('@')[0]}/${mail.id}`}
target="_blank"
rel="noopener noreferrer"
className={`flex justify-between items-center p-2 ps-3 pe-3 rounded cursor-pointer bg-white dark:bg-gray-800 shadow hover:shadow-md transition-all border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 ${
mail.id > lastEmailId ? 'bg-yellow-50 dark:bg-yellow-900/30' : ''
}`}
>
<div className="truncate flex-1">
<span className="text-sm text-gray-900 dark:text-white">
{mail.subject.substring(0, 30)}{mail.subject.length > 30 ? '...' : ''}
</span>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 ml-2">
{new Date(mail.dateSystem).toLocaleDateString()}
</div>
</a>
) : (
<Link
key={mail.id}
to={`/emails/${mail.id}`}
className={`flex justify-between items-center p-2 ps-3 pe-3 rounded cursor-pointer bg-white dark:bg-gray-800 shadow hover:shadow-md transition-all border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 ${
mail.id > lastEmailId ? 'bg-yellow-50 dark:bg-yellow-900/30' : ''
}`}
>
<span className="truncate flex-1">
<span className="text-sm text-gray-900 dark:text-white">
{mail.subject.substring(0, 30)}{mail.subject.length > 30 ? '...' : ''}
</span>
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2">
{new Date(mail.dateSystem).toLocaleDateString()}
</span>
</Link>
)
))}
{canLoadMore && (
<button
onClick={loadMoreEmails}
className="w-full mt-2 py-1 px-3 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-md transition-colors duration-200 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100 flex items-center justify-center gap-1"
>
<span>{t('common.loadMore')}</span>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
)}
</div>
);
};

View File

@@ -0,0 +1,180 @@
import React, { forwardRef } from 'react';
import { useTranslation } from 'react-i18next';
/**
* Button configuration for form input.
*/
type FormInputButton = {
icon: string;
onClick: () => void;
title?: string;
}
/**
* Icon component for form input buttons.
*/
const Icon: React.FC<{ name: string }> = ({ name }) => {
switch (name) {
case 'visibility':
return (
<>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</>
);
case 'visibility-off':
return (
<>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</>
);
case 'refresh':
return (
<>
<path d="M23 4v6h-6" />
<path d="M1 20v-6h6" />
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
</>
);
case 'settings':
return (
<>
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</>
);
default:
return null;
}
};
/**
* Form input props.
*/
type FormInputProps = {
id: string;
label: string;
value: string;
onChange: (value: string) => void;
type?: 'text' | 'password';
placeholder?: string;
required?: boolean;
multiline?: boolean;
rows?: number;
error?: string;
buttons?: FormInputButton[];
showPassword?: boolean;
onShowPasswordChange?: (show: boolean) => void;
}
/**
* Form input component.
*/
export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(({
id,
label,
value,
onChange,
type = 'text',
placeholder,
required = false,
multiline = false,
rows = 1,
error,
buttons = [],
showPassword: controlledShowPassword,
onShowPasswordChange
}, ref) => {
const { t } = useTranslation();
const [internalShowPassword, setInternalShowPassword] = React.useState(false);
/**
* Use controlled or uncontrolled showPassword state.
* If controlledShowPassword is provided, use that value and call onShowPasswordChange.
* Otherwise, use internal state.
*/
const showPassword = controlledShowPassword !== undefined ? controlledShowPassword : internalShowPassword;
/**
* Set the showPassword state.
* If controlledShowPassword is provided, use that value and call onShowPasswordChange.
* Otherwise, use internal state.
*/
const setShowPassword = (value: boolean): void => {
if (controlledShowPassword !== undefined) {
onShowPasswordChange?.(value);
} else {
setInternalShowPassword(value);
}
};
const inputClasses = `mt-1 block w-full rounded-md ${
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
} text-gray-900 sm:text-sm rounded-lg shadow-sm border focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400 py-2 px-3`;
// Add password visibility button if type is password
const allButtons = type === 'password'
? [...buttons, {
icon: showPassword ? 'visibility-off' : 'visibility',
/**
* Toggle password visibility.
*/
onClick: (): void => setShowPassword(!showPassword),
title: showPassword ? t('common.hidePassword') : t('common.showPassword')
}]
: buttons;
return (
<div>
<label htmlFor={id} className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<div className="relative">
{multiline ? (
<textarea
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
rows={rows}
placeholder={placeholder}
className={inputClasses}
/>
) : (
<input
ref={ref}
type={type === 'password' && !showPassword ? 'password' : 'text'}
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className={inputClasses}
/>
)}
{allButtons.length > 0 && (
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2">
{allButtons.map((button, index) => (
<button
type="button"
key={index}
onClick={button.onClick}
title={button.title}
className="p-1 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors duration-200"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<Icon name={button.icon} />
</svg>
</button>
))}
</div>
)}
</div>
{error && (
<p className="mt-1 text-sm text-red-500">{error}</p>
)}
</div>
);
});
FormInput.displayName = 'FormInput';

View File

@@ -0,0 +1,151 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ClipboardCopyService } from '@/entrypoints/popup/utils/ClipboardCopyService';
/**
* Form input copy to clipboard props.
*/
type FormInputCopyToClipboardProps = {
id: string;
label: string;
value: string;
type?: 'text' | 'password';
}
const clipboardService = new ClipboardCopyService();
/**
* Icon component for form input buttons.
*/
const Icon: React.FC<{ name: string }> = ({ name }) => {
switch (name) {
case 'visibility':
return (
<>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</>
);
case 'visibility-off':
return (
<>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</>
);
case 'copy':
return (
<>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</>
);
case 'check':
return (
<>
<polyline points="20 6 9 17 4 12" />
</>
);
default:
return null;
}
};
/**
* Form input copy to clipboard component.
*/
export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> = ({
id,
label,
value,
type = 'text'
}) => {
const { t } = useTranslation();
const [showPassword, setShowPassword] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => {
const unsubscribe = clipboardService.subscribe((copiedId) : void => {
setCopied(copiedId === id);
});
return () : void => {
unsubscribe();
};
}, [id]);
/**
* Copy to clipboard.
*/
const copyToClipboard = async () : Promise<void> => {
try {
await navigator.clipboard.writeText(value);
clipboardService.setCopied(id);
// Reset copied state after 2 seconds
setTimeout(() => {
if (clipboardService.getCopiedId() === id) {
clipboardService.setCopied('');
}
}, 2000);
} catch (err) {
console.error('Failed to copy text:', err);
}
};
return (
<div>
<label htmlFor={id} className="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
{label}
</label>
<div className="relative">
<input
type={type === 'password' && !showPassword ? 'password' : 'text'}
id={id}
readOnly
value={value}
onClick={copyToClipboard}
className={`w-full px-3 py-2.5 bg-white border ${
copied ? 'border-green-500 border-2' : 'border-gray-300'
} text-gray-900 sm:text-sm rounded-lg shadow-sm focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400`}
/>
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-2">
{copied ? (
<button
type="button"
className="p-1 text-green-500 dark:text-green-400 transition-colors duration-200"
title={t('common.copied')}
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<Icon name="check" />
</svg>
</button>
) : (
<button
type="button"
onClick={copyToClipboard}
className="p-1 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors duration-200"
title={t('common.copyToClipboard')}
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<Icon name="copy" />
</svg>
</button>
)}
{type === 'password' && (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="p-1 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors duration-200"
title={showPassword ? t('common.hidePassword') : t('common.showPassword')}
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<Icon name={showPassword ? 'visibility-off' : 'visibility'} />
</svg>
</button>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { HeaderIcon, HeaderIconType } from '@/entrypoints/popup/components/Icons/HeaderIcons';
type HeaderButtonProps = {
onClick: () => void;
title: string;
iconType: HeaderIconType;
variant?: 'default' | 'primary' | 'danger';
};
/**
* Header button component for consistent header button styling
*/
const HeaderButton: React.FC<HeaderButtonProps> = ({
onClick,
title,
iconType,
variant = 'default'
}) => {
const colorClasses = {
default: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700',
primary: 'text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-900/20',
danger: 'text-red-500 hover:text-red-600 hover:bg-red-100 dark:hover:bg-red-900/20'
};
return (
<button
onClick={onClick}
className={`p-2 rounded-lg ${colorClasses[variant]}`}
title={title}
>
<HeaderIcon type={iconType} />
</button>
);
};
export default HeaderButton;

View File

@@ -0,0 +1,186 @@
import React from 'react';
export enum HeaderIconType {
EXPAND = 'expand',
EDIT = 'edit',
DELETE = 'delete',
SETTINGS = 'settings',
RELOAD = 'reload',
EXTERNAL_LINK = 'external_link',
SAVE = 'save',
PLUS = 'plus',
TAB = 'tab'
}
type HeaderIconProps = {
type: HeaderIconType;
className?: string;
};
/**
* Component to render header icons
*/
export const HeaderIcon: React.FC<HeaderIconProps> = ({ type, className = 'w-5 h-5' }) => {
const icons = {
[HeaderIconType.EXPAND]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
/>
</svg>
),
[HeaderIconType.EDIT]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
),
[HeaderIconType.DELETE]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
),
[HeaderIconType.SETTINGS]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
),
[HeaderIconType.RELOAD]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
),
[HeaderIconType.EXTERNAL_LINK]: (
<svg
className={className}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" />
</svg>
),
[HeaderIconType.SAVE]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V7l-4-4z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 3v5h10"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 12a2 2 0 100 4 2 2 0 000-4z"
/>
</svg>
),
[HeaderIconType.PLUS]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
),
[HeaderIconType.TAB]: (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z"
/>
</svg>
)
};
return icons[type] || null;
};

View File

@@ -0,0 +1,131 @@
import React, { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { AVAILABLE_LANGUAGES, getLanguageConfig, ILanguageConfig } from '../../../i18n/config';
import { storage } from '#imports';
type LanguageSwitcherProps = {
variant?: 'dropdown' | 'buttons';
size?: 'sm' | 'md';
};
/**
* Language switcher component that allows users to switch between supported languages
* @param props - Component props including variant and size
* @returns JSX element for the language switcher
*/
const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
variant = 'dropdown',
size = 'md'
}): React.JSX.Element => {
const { i18n } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const currentLanguage = getLanguageConfig(i18n.language) || AVAILABLE_LANGUAGES[0];
// Close dropdown when clicking outside
useEffect((): (() => void) => {
/**
* Handle clicks outside the dropdown to close it
* @param event - Mouse event
*/
const handleClickOutside = (event: MouseEvent): void => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
/**
* Change the application language
* @param lng - Language code to switch to
*/
const changeLanguage = async (lng: string): Promise<void> => {
await i18n.changeLanguage(lng);
await storage.setItem('local:language', lng);
setIsOpen(false);
// Force immediate re-render by dispatching the event that react-i18next listens to
i18n.emit('languageChanged', lng);
};
if (variant === 'buttons') {
return (
<div className="flex space-x-2">
{AVAILABLE_LANGUAGES.map((lang: ILanguageConfig) => (
<button
key={lang.code}
onClick={() => changeLanguage(lang.code)}
className={`flex items-center space-x-1 px-2 py-1 text-xs rounded transition-colors ${
i18n.language === lang.code
? 'bg-primary-500 text-white'
: 'bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-500'
}`}
title={lang.nativeName}
>
<span className="text-sm">{lang.flag}</span>
<span>{lang.code.toUpperCase()}</span>
</button>
))}
</div>
);
}
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className={`w-full flex items-center justify-between px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors ${
size === 'sm' ? 'text-sm' : 'text-base'
}`}
>
<div className="flex items-center space-x-2">
<span className="text-lg">{currentLanguage.flag}</span>
<span>{currentLanguage.nativeName}</span>
</div>
<svg
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-1 w-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-50">
{AVAILABLE_LANGUAGES.map((lang: ILanguageConfig) => (
<button
key={lang.code}
onClick={() => changeLanguage(lang.code)}
className={`w-full flex items-center justify-between px-3 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors first:rounded-t-lg last:rounded-b-lg ${
size === 'sm' ? 'text-sm' : 'text-base'
}`}
>
<div className="flex items-center space-x-2">
<span className="text-lg">{lang.flag}</span>
<span className="text-gray-700 dark:text-gray-200">{lang.nativeName}</span>
</div>
{i18n.language === lang.code && (
<svg className="w-4 h-4 text-primary-500" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</button>
))}
</div>
)}
</div>
);
};
export default LanguageSwitcher;

View File

@@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import { useDb } from '../../context/DbContext';
type TabName = 'credentials' | 'emails' | 'settings';
@@ -9,17 +8,20 @@ type TabName = 'credentials' | 'emails' | 'settings';
* Bottom nav component.
*/
const BottomNav: React.FC = () => {
const authContext = useAuth();
const dbContext = useDb();
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const [currentTab, setCurrentTab] = useState<TabName>('credentials');
// Add effect to update currentTab based on route
useEffect(() => {
const path = location.pathname.substring(1) as TabName;
if (['credentials', 'emails', 'settings'].includes(path)) {
setCurrentTab(path);
const path = location.pathname.substring(1); // Remove leading slash
const tabNames: TabName[] = ['credentials', 'emails', 'settings'];
// Find the first tab name that matches the start of the path
const matchingTab = tabNames.find(tab => path === tab || path.startsWith(`${tab}/`));
if (matchingTab) {
setCurrentTab(matchingTab);
}
}, [location]);
@@ -31,7 +33,11 @@ const BottomNav: React.FC = () => {
navigate(`/${tab}`);
};
if (!authContext.isLoggedIn || !dbContext.dbAvailable) {
// Auth pages that don't show bottom navigation but still show header
const authPages = ['/login', '/auth-settings', '/unlock', '/unlock-success', '/upgrade'];
const isAuthPage = authPages.includes(location.pathname);
if (isAuthPage) {
return null;
}
@@ -56,7 +62,7 @@ const BottomNav: React.FC = () => {
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
<span className="text-xs mt-1">Credentials</span>
<span className="text-xs mt-1">{t('menu.credentials')}</span>
</button>
<button
onClick={() => handleTabChange('emails')}
@@ -67,7 +73,7 @@ const BottomNav: React.FC = () => {
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
<span className="text-xs mt-1">Emails</span>
<span className="text-xs mt-1">{t('menu.emails')}</span>
</button>
<button
onClick={() => handleTabChange('settings')}
@@ -79,7 +85,7 @@ const BottomNav: React.FC = () => {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span className="text-xs mt-1">Settings</span>
<span className="text-xs mt-1">{t('menu.settings')}</span>
</button>
</div>
</div>

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