Compare commits

...

1257 Commits
0.19.2 ... main

Author SHA1 Message Date
Leendert de Borst
e5d342b961 Add db-export duration timer 2025-12-02 13:59:36 +01:00
Leendert de Borst
a58426abcb Use postgres dockerfile for dev db instance 2025-12-02 13:52:37 +01:00
Leendert de Borst
819385bc0a Use stream instead of temp file for db-export 2025-12-02 13:52:13 +01:00
Leendert de Borst
c0cbc0be7b Only include successful attempts in statistics (#1420) 2025-12-01 23:08:38 +01:00
Leendert de Borst
40686f97e0 Show last 30 days instead of 72h for deletion requests (#1420) 2025-12-01 20:17:31 +01:00
Leendert de Borst
f10fb989ce Update recent usage page to include more info (#1420) 2025-12-01 16:25:59 +00:00
Leendert de Borst
ca85c04c75 Add review-versions.sh helper script 2025-12-01 11:51:21 +01:00
Leendert de Borst
fd9eb9d653 Bump version to 0.26.0-alpha 2025-11-30 17:49:52 +01:00
Leendert de Borst
0a70902d69 Bump version to 0.25.1 for mobile app (unaffected by 0.25.2 release) 2025-11-30 17:47:58 +01:00
Leendert de Borst
eee41df9a4 Bump version to 0.25.2 2025-11-30 17:30:32 +01:00
Leendert de Borst
d563d6d448 Improve browser extension vault cache (#1413) 2025-11-30 17:26:23 +01:00
Leendert de Borst
db1474397c Add cascade delete to MobileLoginRequests (#1415) 2025-11-30 15:38:49 +00:00
Leendert de Borst
e881f9486a Add parallel support to db-export command (#1415) 2025-11-30 15:12:55 +00:00
Leendert de Borst
645fd605e6 Update PasswordGenerator.test.ts (#1413) 2025-11-30 12:08:22 +00:00
Leendert de Borst
254f0a1212 Improve browser extension autofill suggestion performance (#1413) 2025-11-30 12:08:22 +00:00
Leendert de Borst
64d29ebcd4 Update admin users list to show correct amount of email claims (#1411) 2025-11-30 11:17:16 +00:00
Leendert de Borst
df0d74595f Bump version to 0.26.0-alpha 2025-11-28 20:16:39 +01:00
Leendert de Borst
2131e4922c Merge branch 'main' of https://github.com/aliasvault/aliasvault
* 'main' of https://github.com/aliasvault/aliasvault:
  New Crowdin updates (#1397)
  Tweak native QR code scanner to only react on AliasVault prefixes (#1405)
  Add native iOS QR code scanner (#1405)
  Update net.aliasvault.app.yml.template (#1405)
  Add native Android QR code scanner ZXing implementation (#1405)
  Update run.sh to generate net.aliasvault.app.yml with latest version and branch for proper F-Droid build (#1405)
  Update package.json (#1405)
  Update F-Droid local build scripts (#1405)
  Replace expo-camera which uses non-FOSS libs with react-native-vision-camera (#1405)
  Add expo-camera to scanignore to prevent it being deleted by F-Droid (#1405)
  Add sign-apk.sh helper script (#1405)
  Update F-Droid local build flow to capture APK outputs (#1405)
2025-11-28 18:50:54 +01:00
Leendert de Borst
d846825b84 Update FormFiller logic to improve browser extension autofill reliability 2025-11-28 18:50:40 +01:00
Leendert de Borst
2a902eeb97 Bump version to 0.25.1 2025-11-28 18:37:14 +01:00
Leendert de Borst
d9a6dfab03 New Crowdin updates (#1397)
* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]
2025-11-28 18:26:11 +01:00
Leendert de Borst
3da99ed4b1 Tweak native QR code scanner to only react on AliasVault prefixes (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
5414f40c98 Add native iOS QR code scanner (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
6c561e8ece Update net.aliasvault.app.yml.template (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
3654b12cd7 Add native Android QR code scanner ZXing implementation (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
266e7b36d4 Update run.sh to generate net.aliasvault.app.yml with latest version and branch for proper F-Droid build (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
cbe9978367 Update package.json (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
6b949bcb2f Update F-Droid local build scripts (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
6a4fbb9193 Replace expo-camera which uses non-FOSS libs with react-native-vision-camera (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
c459a48927 Add expo-camera to scanignore to prevent it being deleted by F-Droid (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
d3f132df63 Add sign-apk.sh helper script (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
b5edc6ef76 Update F-Droid local build flow to capture APK outputs (#1405) 2025-11-28 17:23:11 +00:00
Leendert de Borst
4e0db87bc3 Update password generator with non-ambigious char improvement (#1398) 2025-11-27 10:10:24 +01:00
Leendert de Borst
62cc0e7c2b Improve password generator non-ambigious chars option (#1398) 2025-11-27 09:08:23 +00:00
Leendert de Borst
dad3a6fa2c Make AuthController.cs more robust and do not log invalid tokens as server errors (#1408) 2025-11-27 09:08:07 +00:00
dependabot[bot]
9560d550e4 Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [node-forge](https://github.com/digitalbazaar/forge).
Bumps the npm_and_yarn group with 1 update in the /apps/mobile-app directory: [node-forge](https://github.com/digitalbazaar/forge).


Updates `node-forge` from 1.3.1 to 1.3.2
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2)

Updates `node-forge` from 1.3.1 to 1.3.2
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 06:50:16 +00:00
Leendert de Borst
0930ae03cd Remove loading animation from web app generate random alias button (#1402) 2025-11-26 22:37:44 +00:00
Leendert de Borst
23c9bf2fc9 Fix related users navigation refresh in admin (#1400) 2025-11-26 10:20:12 +00:00
Leendert de Borst
6ebaf8e1b8 Bump working version to 0.26.0-alpha 2025-11-26 11:11:52 +01:00
Leendert de Borst
aa630984e3 New Crowdin updates (#1396)
* New translations en.json (German)
Update translations from Crowdin [ci skip]

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

* New translations en.json (German)
Update translations from Crowdin [ci skip]
2025-11-24 19:49:39 +01:00
Leendert de Borst
b894338869 Bump build numbers 2025-11-24 18:00:43 +01:00
Leendert de Borst
d7ec6583f0 New Crowdin updates (#1380)
* New translations en.json (German)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations mobilelogin.en.resx (German)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Russian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations sharedresources.en.resx (Polish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

* New translations general.en.resx (Polish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations mobileunlockmodal.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Polish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

* New translations mobileunlockmodal.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]
2025-11-24 17:59:25 +01:00
Leendert de Borst
836fbc1941 Fix build-and-submit.sh for Safari browser extension 2025-11-24 15:24:34 +01:00
Leendert de Borst
c531096a98 Tweak install.sh temp file cleanup, bump version to 0.25.0 (#1393) 2025-11-24 13:15:17 +00:00
Leendert de Borst
b78a757728 Bump version to 0.25.0 (#1393) 2025-11-24 13:15:17 +00:00
Leendert de Borst
f676fba980 Add extra sanity check to mobile app vault upgrade to prevent potential errors (#1382) 2025-11-24 11:39:38 +00:00
Leendert de Borst
003e3e4d1d Update en.json 2025-11-24 12:38:22 +01:00
Leendert de Borst
637362856a Update en.json 2025-11-24 12:37:49 +01:00
Leendert de Borst
b855896108 Add 2FA TOTP code editor to mobile app (#1391) 2025-11-24 10:05:39 +00:00
Leendert de Borst
a92bbef41a Add 2FA TOTP code editor to browser extension (#1391) 2025-11-24 10:05:39 +00:00
Leendert de Borst
dccbda7515 Tweak browser extension passkey interceptor to only intercept automatic requests if there is a matching credential (#1358) 2025-11-24 09:12:28 +00:00
Leendert de Borst
a45a468e35 Tweak user stats display (#1387) 2025-11-23 18:41:35 +00:00
Leendert de Borst
97dc5f3570 Add persistent email received counter for a user (#1387) 2025-11-23 18:41:35 +00:00
Leendert de Borst
425a977af9 Update RefreshTokenTable.razor (#1385) 2025-11-22 21:34:48 +00:00
Leendert de Borst
30635d9714 Improve statistics query performance (#1385) 2025-11-22 21:34:48 +00:00
Leendert de Borst
cb2aa833bc Order by vault.Revision instead of vault.Version (#1385) 2025-11-22 21:34:48 +00:00
Leendert de Borst
f7b66ed307 Update translation contributing docs (#1383) 2025-11-22 21:33:31 +00:00
Leendert de Borst
85e33a9fcd Use current UI language as default identity generator language (#1383) 2025-11-22 21:33:31 +00:00
Leendert de Borst
51dc4d2844 Add langcode definitions to identitty-generator languages (#1383) 2025-11-22 21:33:31 +00:00
Leendert de Borst
b1d12af7dd Add german identity generator option (#1383) 2025-11-22 21:33:31 +00:00
Leendert de Borst
ae4fc13330 Cleanup (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
e1c5b5f753 Tweak discard changes logic on explicit cancel button press (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
9ff7c6c23b Update identity generator implementation for browser extension (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
40fdb4e21a Update identity generator implementation for mobile app (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
72254f38ff Refactor identity-generator (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
274cb70d4b Expose supported identity language options via identity-generator shared lib (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
a30e68e0f8 Update General.razor (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
fe0678f217 Update identity-generator lib (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
aab69ab1b4 Add identity generator age setting to AliasVault.Client (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
02575d7366 Add AgeRange options (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
b218ebf407 Add DefaultIdentityAgeRange settings param (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
2043e94a91 Add first name by decade range and age preference scaffolding (#1379) 2025-11-22 04:46:58 +00:00
Leendert de Borst
e6bc3ea652 Download correct s6-overlay binaries for arm64 arch (#1364) 2025-11-21 06:45:06 +01:00
Leendert de Borst
92b072868e Create docker-compose.all-in-one.dev.yml (#1364) 2025-11-21 06:45:06 +01:00
Leendert de Borst
aab7b475cc New Crowdin updates (#1356)
* New translations strings.xml (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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations localizable.strings (Russian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations clipboardcountdownbar.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

* New translations home.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Finnish)
Update translations from Crowdin [ci skip]

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

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

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

* New translations mobileunlockmodal.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

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

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

* New translations mobileunlockmodal.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Italian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Polish)
Update translations from Crowdin [ci skip]

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

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

* New translations mobileunlockmodal.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Russian)
Update translations from Crowdin [ci skip]

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

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

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

* New translations mobileunlockmodal.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Swedish)
Update translations from Crowdin [ci skip]

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

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

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

* New translations mobileunlockmodal.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Turkish)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

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

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

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

* New translations mobileunlockmodal.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Portuguese, Brazilian)
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]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations mobileunlockmodal.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

* New translations mobileunlockmodal.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

* New translations mobileunlockmodal.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations mobilelogin.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Finnish)
Update translations from Crowdin [ci skip]
2025-11-21 06:45:06 +01:00
Leendert de Borst
1e75d3806b Fix case sensitive issue in recent email block in emailbox API retrieve call (#1370) 2025-11-21 06:45:06 +01:00
Leendert de Borst
e9bd073bac Update net.aliasvault.app.yml 2025-11-21 06:45:06 +01:00
Leendert de Borst
da496b31a1 Update default email domain logic (#1371) 2025-11-21 06:45:05 +01:00
Leendert de Borst
2e34e64c6c Update default email domain selection and metadata retrieval (#1371) 2025-11-21 06:45:05 +01:00
Leendert de Borst
0da8661d6c Update EditEmailFormRow.razor (#1371) 2025-11-21 06:45:05 +01:00
Leendert de Borst
1797ed9ec6 Refactor (#1371) 2025-11-21 06:45:05 +01:00
Leendert de Borst
4d613175ed Update env variables and refactor metadata storage (#1371) 2025-11-21 06:45:05 +01:00
Leendert de Borst
a937098315 Update tests 2025-11-20 16:38:26 +01:00
Leendert de Borst
c3be660c1e Update AAGUID docs 2025-11-20 16:33:57 +01:00
Leendert de Borst
9b622c8fb4 Update translation key 2025-11-20 07:58:36 +01:00
Leendert de Borst
986c028d82 Merge pull request #1366 from aliasvault/1347-feature-request-unlock-vault-with-mobile-device
Add "unlock with mobile" option to web app and browser extension
2025-11-20 05:11:25 +00:00
Leendert de Borst
428c715ec2 Refactor unlock and centralize logic (#1347) 2025-11-19 20:32:22 +01:00
Leendert de Borst
4ae8839d9b Update PIN unlock flow (#1347) 2025-11-19 20:01:48 +01:00
Leendert de Borst
a199b9e8da Fix Android manual PIN verification flow (#1347) 2025-11-19 19:56:56 +01:00
Leendert de Borst
ae7eb2ca1a Update browser extension tests (#1347) 2025-11-19 19:51:55 +01:00
Leendert de Borst
06b510c496 Update routing logic and add NavigationContext (#1347) 2025-11-19 19:51:04 +01:00
Leendert de Borst
020e83d40f Update docs (#1347) 2025-11-19 15:41:06 +01:00
Leendert de Borst
3b14bbcca4 Update packages (#1347) 2025-11-19 15:23:28 +01:00
Leendert de Borst
e97bf6d168 Implement new methods in Kotlin NativeVaultManager layer (#1347) 2025-11-19 13:48:09 +01:00
Leendert de Borst
76b829eb3d Refactor (#1347) 2025-11-19 12:02:46 +01:00
Leendert de Borst
07b6097d31 Update tests (#1347) 2025-11-19 11:25:03 +01:00
dependabot[bot]
81b6479682 Bump the npm_and_yarn group across 5 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /shared/identity-generator directory: [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /shared/models directory: [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /shared/password-generator directory: [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /shared/vault-sql directory: [glob](https://github.com/isaacs/node-glob).


Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 10:06:32 +00:00
dependabot[bot]
9016a4b0b8 Bump the npm_and_yarn group across 3 directories with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /apps/mobile-app directory: [js-yaml](https://github.com/nodeca/js-yaml) and [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /apps/server/AliasVault.Admin directory: [glob](https://github.com/isaacs/node-glob).
Bumps the npm_and_yarn group with 1 update in the /apps/server/AliasVault.Client directory: [glob](https://github.com/isaacs/node-glob).


Updates `js-yaml` from 3.14.1 to 3.14.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2)

Updates `glob` from 10.4.5 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.1 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

Updates `glob` from 10.4.1 to 10.5.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 09:51:14 +00:00
Leendert de Borst
786bf655d0 Update TaskRunnerTests.cs (#1347) 2025-11-18 23:12:18 +01:00
Leendert de Borst
bdfea51319 UX flow tweaks (#1347) 2025-11-18 23:05:45 +01:00
Leendert de Borst
8ce636a5c1 Add PIN unlock awareness to reinitialize for vault locked flow due to timeout (#1347) 2025-11-18 22:44:50 +01:00
Leendert de Borst
9d4ceff4ba Update AuthController.cs (#1347) 2025-11-18 22:11:03 +01:00
Leendert de Borst
d562b183c5 Update web app translations (#1347) 2025-11-18 22:10:33 +01:00
Leendert de Borst
3e7848bb3b Update browser extension translations (#1347) 2025-11-18 22:08:28 +01:00
Leendert de Borst
e4614c8034 Cleanup (#1347) 2025-11-18 22:02:13 +01:00
Leendert de Borst
c404fa807f Add mobile login feature to architecture docs (#1347) 2025-11-18 21:54:45 +01:00
Leendert de Borst
fa366cf2e6 Refactor (#1347) 2025-11-18 21:31:15 +01:00
Leendert de Borst
3653ec3d55 Tweak browser extension unlock vault button placement (#1347) 2025-11-18 21:21:08 +01:00
Leendert de Borst
4d74504882 Refactor MobileLogin models to use class structure instead of record for readability (#1347) 2025-11-18 21:18:38 +01:00
Leendert de Borst
29c7644b53 Tweak app scheme to shorthand aliasvault (#1347) 2025-11-18 21:01:14 +01:00
Leendert de Borst
648fe0598d Update AuthController timeout to add a API buffer for better UX (#1347) 2025-11-18 21:00:50 +01:00
Leendert de Borst
2a3a35f562 Tweak app scheme to shorthand aliasvault (#1347) 2025-11-18 21:00:25 +01:00
Leendert de Borst
359f911057 Refactor deep linking to work better with vault lock flow (#1347) 2025-11-18 20:42:03 +01:00
Leendert de Borst
267f2d3d17 Refactor mobile app deep linking to support both cold and warm app opens (#1347) 2025-11-18 15:50:45 +01:00
Leendert de Borst
80abfecd2e Move QR code scanner to FAB button for easier access (#1347) 2025-11-18 15:27:35 +01:00
Leendert de Borst
42524d1412 Add mobile unlock modal to web app allowing use from both login and unlock screens (#1347) 2025-11-18 13:34:31 +01:00
Leendert de Borst
81750c4878 Update browser extension login UI (#1347) 2025-11-18 13:14:30 +01:00
Leendert de Borst
5c9d9c6933 Update AuthHelper device identifier to make it more unique (#1347) 2025-11-18 13:08:16 +01:00
Leendert de Borst
ec8cb7836a Add mobile login option to browser extension unlock page too (#1347) 2025-11-18 11:20:19 +01:00
Leendert de Borst
a64f7d97e5 Refactor browser extension MobileLoginUtility flow (#1347) 2025-11-17 23:59:08 +01:00
Leendert de Borst
32fe2156f1 Refactor web app MobileLoginUtility flow, add helper model (#1347) 2025-11-17 23:44:33 +01:00
Leendert de Borst
6aa43bb1a2 Simplify qr-confirm.tsx (#1347) 2025-11-17 23:31:27 +01:00
Leendert de Borst
f9d7918e0a Update shared models and update browser extension MobileLoginUtility (#1347) 2025-11-17 23:31:05 +01:00
Leendert de Borst
076060e7f3 Remove redundant fields from MobileLoginRecord structure (#1347) 2025-11-17 23:15:37 +01:00
Leendert de Borst
4d7d061e07 Update AuthController.cs (#1347) 2025-11-17 22:12:48 +01:00
Leendert de Borst
582ab7d20a Add mobile app login request clear task to task runner (#1347) 2025-11-17 21:58:21 +01:00
Leendert de Borst
bcd1353cf7 Add mobile login requests to admin dashboard, update migration (#1347) 2025-11-17 21:08:11 +01:00
Leendert de Borst
eaa348bb23 Add mobile login auth log type (#1347) 2025-11-17 18:41:03 +01:00
Leendert de Borst
0db3e2dbf4 Refactor mobile unlock to mobile login naming, update migrations (#1347) 2025-11-17 18:24:45 +01:00
Leendert de Borst
728af0bff6 Tweak browser extension login UI (#1347) 2025-11-17 17:11:09 +01:00
Leendert de Borst
7923c16c51 Tweak login UI and translations (#1347) 2025-11-17 17:03:14 +01:00
Leendert de Borst
18a5e062a5 Add mobile unlock scaffolding to AliasVault.client web app (#1347) 2025-11-17 16:32:09 +01:00
Leendert de Borst
1097218ee1 Add min server supported native vault method, add user authenticate method with custom prompt (#1347) 2025-11-17 15:23:27 +01:00
Leendert de Borst
0a8722226b Refactor API to use constant for mobile app unlock timeout (#1347) 2025-11-17 14:17:34 +01:00
Leendert de Borst
63cc511a9f Tweak re-authenticate flow with custom title/subtitle (#1347) 2025-11-17 10:54:54 +01:00
Leendert de Borst
5367c5eb34 Tweak QR code translations (#1347) 2025-11-17 09:33:11 +01:00
Leendert de Borst
f7b0084eba Refactor (#1347) 2025-11-16 22:38:06 +01:00
Leendert de Borst
09d4ba46fa Update qr-scanner.tsx UX flow (#1347) 2025-11-16 22:18:52 +01:00
Leendert de Borst
fb33e688df Add mobile app unlock flow (#1347) 2025-11-16 21:31:49 +01:00
Leendert de Borst
9017d0b642 Update mobile unlock endpoints (#1347) 2025-11-16 20:24:04 +01:00
Leendert de Borst
f50fe913fb Add login with mobile QR code client side logic (#1347) 2025-11-16 20:23:50 +01:00
Leendert de Borst
7b78552651 Add mobile unlock models (#1347) 2025-11-16 20:21:50 +01:00
Leendert de Borst
e7d7d9fe54 Merge branch '1347-feature-request-unlock-vault-with-mobile-device' of https://github.com/aliasvault/aliasvault into 1347-feature-request-unlock-vault-with-mobile-device
* '1347-feature-request-unlock-vault-with-mobile-device' of https://github.com/aliasvault/aliasvault:
  Add mobile unlock request database and API scaffolding (#1347)
2025-11-16 17:12:12 +01:00
Leendert de Borst
fdfe4b0aa8 Add expo-camera package for QR code scanner (#1347) 2025-11-16 17:11:00 +01:00
dependabot[bot]
6b2737eec5 Bump the npm_and_yarn group across 5 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 1 update in the /shared/identity-generator directory: [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 1 update in the /shared/models directory: [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 1 update in the /shared/password-generator directory: [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 1 update in the /shared/vault-sql directory: [js-yaml](https://github.com/nodeca/js-yaml).


Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-16 14:57:59 +00:00
Leendert de Borst
79f1bca7a2 Add mobile unlock request database and API scaffolding (#1347) 2025-11-15 13:31:57 +01:00
Leendert de Borst
224e4ee741 Add mobile unlock request database and API scaffolding (#1347) 2025-11-15 11:50:23 +01:00
Leendert de Borst
9a453a1fab Cleanup unused ApiError codes, update EF docs 2025-11-15 11:45:42 +01:00
Leendert de Borst
4cb7966492 Add discard changes check to credential add-edit modal (#1360) 2025-11-14 18:04:44 +00:00
Leendert de Borst
dbfee0f5b6 Return proper expected error codes in Android NativeVaultManager (#1360) 2025-11-14 18:04:44 +00:00
Leendert de Borst
94bad91411 Update zero-knowledge architecture docs 2025-11-14 18:06:47 +01:00
Leendert de Borst
9dc9ed9ba1 Cleanup translations 2025-11-14 12:23:20 +01:00
Leendert de Borst
686ea56556 Update en.json 2025-11-14 11:58:23 +01:00
Leendert de Borst
73f95b3a77 Update en.json 2025-11-14 11:53:12 +01:00
Leendert de Borst
198fc57d93 Add explicit apps/server workdir for wasm tool install invocations (#1355) 2025-11-13 22:14:23 +00:00
Leendert de Borst
fd64ea8647 Cleanup unused translations in mobile app (#1355) 2025-11-13 22:14:23 +00:00
Leendert de Borst
4b9e2ba2e3 Cleanup unused translations in browser extension (#1355) 2025-11-13 22:14:23 +00:00
Leendert de Borst
e849762985 New Crowdin updates (#1336)
* 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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations clipboardcountdownbar.en.resx (Italian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.xml (Russian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

* New translations en.json (Russian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

* New translations strings.xml (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

* New translations localizable.strings (Portuguese, Brazilian)
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 (French)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations localizable.strings (Ukrainian)
Update translations from Crowdin [ci skip]
2025-11-13 22:15:35 +01:00
Leendert de Borst
868e708957 Update dotnet-e2e-tests.yml with explicit working-directory for all jobs 2025-11-13 21:10:18 +01:00
Leendert de Borst
49fa36eedb Update dotnet-e2e-tests.yml 2025-11-13 21:06:41 +01:00
Leendert de Borst
f049399d9e Create global.json to lock .NET SDK version for stability 2025-11-13 21:01:08 +01:00
Leendert de Borst
b00e7c3ac5 Tweak pin unlock layout for smaller screens (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
31c7832745 Cleanup Kotlin/Swift translations (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
3cc8c9f5de Remove redundant NotConfigured error case (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
ccf923bc98 Clear PIN data on logout (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
039e63f5c8 Update browser extension to min 6 digit pin (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
52b60e07d2 Cleanup NativeVaultManager bridge (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
95a5391589 Cleanup translations (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
c8277be56f Update swift theme color usage (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
66115496fb Simplify react native pin unlock components (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
6f89be6980 Cleanup color constant usage (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
da36af15ae Add swift pin configure flow (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
aa218f4f8f Update project.pbxpoj (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
558d39ec96 Refactor pin setup in Android to use native view (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
4b59776b86 Add UnlockCoordinator.kt implementation (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
4a0c6d9499 Refactor (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
f2bd892a5b Cleanup unlock.tsx (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
dd1d6e64e1 Tweak pin unlock flow for Android (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
73ae2a7b62 Update PinNumpad style (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
d9c914d09e Android scaffolding (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
74fd6c1656 Refactor iOS module dependency order (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
f4cd3ae87f Update translations (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
563941f913 Simplify pin unlock reject flow (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
1751a4c242 Refactor (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
7b6170e927 Tweak iOS native pin unlock view flow (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
e5ed8d380f Update PinUnlockView.swift (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
30f03884c8 Update swift native pin unlock flow (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
0ddd24c40e Mobile app pin unlock scaffolding (#1340) 2025-11-13 18:43:54 +00:00
Leendert de Borst
232245fd76 Update en.json (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
bb1549458f Refactor success/failed message component (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
c63b7ceac4 Refactor (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
987de6625f Reorder settings menu (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
9efe878397 Update Reinitialize.tsx (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
ec90890870 Make lock vault reuse clear vault logic (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
bdc405a836 Refactor (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
27e411f485 Make PIN unlock errors translatable (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
108ec1869c Refactor storage api usage (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
e1b05b611e Use Argon2id for pin unlock (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
7d2630e197 Update VaultUnlockSettings.tsx (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
9df5f6c81a Update Unlock.tsx (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
93adb6d60f Fix vault unlock sequence from content script (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
6abce9e9cf Update webauthn.ts console logs 2025-11-11 15:34:04 +00:00
Leendert de Borst
534d82990d Refactor structure and cleanup unused translations (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
fb28827f15 Update vault unlock settings and pin unlock UI (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
b14f22f9ad Add browser extension pin unlock scaffolding (#1338) 2025-11-11 15:34:04 +00:00
Leendert de Borst
d5dee592ab Bump version to 0.25.0-alpha 2025-11-11 15:36:09 +01:00
Leendert de Borst
b0df4c410a Improve browser extension autofill by filling in fields sequentially to prevent race condition issues on some websites (#1335) 2025-11-10 20:56:21 +01:00
Leendert de Borst
f09ce7ffcf Update swift CredentialMatcher tests (#1335) 2025-11-10 20:56:21 +01:00
Leendert de Borst
b6609706e8 Make non-http URL field readable in dark mode (#1335) 2025-11-10 20:56:21 +01:00
Leendert de Borst
19620bff8e Streamline autofill credential matching in all platforms (#1335) 2025-11-10 20:56:21 +01:00
Leendert de Borst
9da243fdac Update README.md 2025-11-10 19:23:35 +01:00
Leendert de Borst
4030387ead Show disabled email claim amount in user edit page in admin 2025-11-06 16:36:57 +01:00
Leendert de Borst
0240f008ce Update android-autofill.tsx 2025-11-06 12:45:33 +01:00
Leendert de Borst
bad4f46a82 Bump Android app version to include new autofill fixes (#1332) 2025-11-06 12:30:41 +01:00
Leendert de Borst
8ec5fab5e0 Improve android autofill matching logic for common usecases (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
85bbb0ab78 Add new tests to all autofill credential match/filter logic methods (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
343b1baedb Tweak android autofill matching logic so all tests pass (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
fb5d4dfeca Improve Android autofill matching to prevent android packages resulting in false positives (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
661f0574c5 Add show search title option to Android autofill (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
a4a1c0b097 Update Android autofill to properly detect email type fiels (#1332) 2025-11-06 12:30:40 +01:00
Leendert de Borst
02eae4c04f Merge branch 'main' of https://github.com/aliasvault/aliasvault
* 'main' of https://github.com/aliasvault/aliasvault:
  Update Android credential provider label (#1332)
  Add Android build script (#1332)
  Bump app build number for unlock screen animation fix (#1332)
  Update unlock loading animation position (#1332)
  Update GitHub workflow Android gradlew memory (#1332)
  Update build-and-submit scripts (#1332)
  Add iOS fastlane CLI build and submit script (#1332)
  Bump version to 0.24.0 stable (#1332)
  New Crowdin updates (#1323)
2025-11-06 12:26:39 +01:00
Leendert de Borst
d7d9d2d99f Update Android credential provider label (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
40b368bc7e Add Android build script (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
360ce0c9eb Bump app build number for unlock screen animation fix (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
074b2e48fa Update unlock loading animation position (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
ae4ea3cb80 Update GitHub workflow Android gradlew memory (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
a8a51f65c3 Update build-and-submit scripts (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
b5264eae69 Add iOS fastlane CLI build and submit script (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
d380ce7946 Bump version to 0.24.0 stable (#1332) 2025-11-05 22:34:27 +01:00
Leendert de Borst
75797fe829 New Crowdin updates (#1323)
* 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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations clipboardcountdownbar.en.resx (Russian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations pendingmigrations.en.resx (Polish)
Update translations from Crowdin [ci skip]

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

* New translations localizable.strings (Russian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations pendingmigrations.en.resx (Polish)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Polish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations quickvaultunlocksection.en.resx (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

* New translations strings.xml (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

* New translations strings.xml (Chinese Simplified)
Update translations from Crowdin [ci skip]
2025-11-03 22:13:49 +01:00
Leendert de Borst
3fd279e032 Update F-Droid README.md 2025-11-03 21:24:23 +01:00
Leendert de Borst
df50a1ad47 Update fdroid test build version 2025-11-03 21:17:32 +01:00
Leendert de Borst
5d96c44ea9 Update fdroid docker compose config 2025-11-03 18:31:23 +01:00
Leendert de Borst
e7baadda9f Add fdroid build script helpers 2025-11-03 17:55:01 +01:00
Leendert de Borst
376d38ef07 Add f-droid local build scripts for debugging 2025-11-03 17:46:59 +01:00
Leendert de Borst
97d8d4d15d Remove react-native-keyboard-controller package as it conflicts with F-droid build 2025-11-02 22:16:09 +01:00
Leendert de Borst
4010631d73 Remove credential play services as we don't support < Android 14 autofill 2025-11-02 21:05:09 +01:00
Leendert de Borst
03d8e15eeb Improve iOS quick passkey autofill to work on iOS 18+ 2025-11-02 20:41:13 +01:00
Leendert de Borst
7f01e2a9a0 Bump app build versions 2025-11-02 00:13:02 +01:00
Leendert de Borst
d0334e9033 Add version artifacts for build processes 2025-11-01 22:59:34 +01:00
Leendert de Borst
0aa99572e3 Fix iOS dependency tree 2025-11-01 22:39:54 +01:00
Leendert de Borst
51f666d238 Update activity_loading.xml 2025-11-01 20:11:24 +01:00
Leendert de Borst
fc60426e0f Tweak alert dialogs and app startup migration 2025-11-01 20:09:38 +01:00
Leendert de Borst
520a6ef4b2 Update credential_provider_config.xml 2025-11-01 19:43:23 +01:00
Leendert de Borst
deacb9ada9 Bump version to 0.24.0-beta 2025-11-01 19:26:49 +01:00
Leendert de Borst
25383dd615 Update Android native loading view to be off-center like iOS 2025-11-01 19:24:37 +01:00
Leendert de Borst
6daed9b31b Update ARCHITECTURE.md 2025-11-01 18:07:18 +01:00
Leendert de Borst
8c40c786f7 Add passkey operations to security diagram 2025-11-01 17:52:49 +01:00
Leendert de Borst
a5025d3262 Update security architecture diagram 2025-11-01 16:53:58 +01:00
Leendert de Borst
c932a24f21 Browser extension webauthn tweaks 2025-11-01 15:41:17 +01:00
Leendert de Borst
0ebc75dcea Update project.pbxproj, add missing static files 2025-11-01 13:42:41 +01:00
Leendert de Borst
0d62b4af55 Improve webauthn popup close robustness 2025-11-01 13:42:22 +01:00
Leendert de Borst
9de879a387 Prevent WebAuthn interception during prefetch for Safari 2025-11-01 12:36:56 +01:00
Leendert de Borst
519fe9ba30 Fix browser extension linting 2025-11-01 09:04:07 +01:00
Leendert de Borst
6aaca60049 Update WebAuthn implementation to be compatible with Firefox 2025-11-01 09:02:53 +01:00
Leendert de Borst
17a248d0d7 Update browser extension passkey title/subtitle to match mobile apps 2025-11-01 08:39:00 +01:00
Leendert de Borst
c8b42aecc1 Update kotlin insert query for passkeys 2025-11-01 08:31:25 +01:00
Leendert de Borst
577c452c88 Tweak add edit popup button margins for iOS 26+ 2025-11-01 08:03:28 +01:00
Leendert de Borst
6a3e294aae Make web app JsInterop more robust to prevent race conditions 2025-10-31 22:52:37 +01:00
Leendert de Borst
81ad1ec5e7 Update quick vault unlock explanation text 2025-10-31 22:42:20 +01:00
Leendert de Borst
8c3007b6f4 Update VaultStoreTest.kt (#1286) 2025-10-31 22:41:24 +01:00
Leendert de Borst
e4cd9fe6ed Update filepreview modal to support image panning/zooming (#1286) 2025-10-31 22:41:24 +01:00
Leendert de Borst
6dc5e4806b Fix multi private domain encoding issue with all-in-one docker container (#1287) 2025-10-31 21:59:10 +01:00
Leendert de Borst
7a72416e83 Fix email domain field issues that did not properly show multiple domains (#1287) 2025-10-31 21:59:10 +01:00
Leendert de Borst
727d7e6025 Update LoadingOverlayOverview.swift to avoid obstructing face id (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
506bc37eac Move initialize status to off center to prevent faceid occlusion (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
a69b1049a6 Improve sqlite flow in browser extension (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
7f3508030e Refactor (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
0b2fd61fd0 Tweak mobile app credential save animation (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
b76654c9d2 Update kotlin sqlite implementation (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
68c7453c08 Use Swift sqlite backup API instead of manual cursor transfer (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
dbbc6a96db Improve persist db to encrypted storage Kotlin flow (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
f6ad5667ef Update Vaultstore+Query.swift to use proper vacuum for persist instead of raw table copy to preserve FK etc. (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
ed8642de41 Refactor vault persist to separate method (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
bcd3673a00 Remove expo sqlite lib, update iOS pods, fix iOS quick autofill sanity checks (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
c180fdf505 Tweak mobile app logout flow to suppress session expired warnings (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
3664f5bc20 Tweak browser extension logout flow to suppress session errors (#1325) 2025-10-31 18:37:30 +01:00
Leendert de Borst
c134c2642a Improve light/dark mode switcher, remove duplicate notes label 2025-10-30 09:21:35 +01:00
Leendert de Borst
003ef1f096 Update Android passkey layout merge issue 2025-10-29 12:08:02 +01:00
Leendert de Borst
386da4b227 Add Polish language option to all apps (#1321) 2025-10-29 10:33:12 +01:00
Leendert de Borst
7ca816a60e Fix mobile app translation file syntax 2025-10-29 10:23:31 +01:00
Leendert de Borst
932d79fd85 New Crowdin updates
* New translations activesessionssection.en.resx (Finnish)
Update translations from Crowdin [ci skip]

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

* New translations quickvaultunlocksection.en.resx (Finnish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations welcome.en.resx (Finnish)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations clipboardcountdownbar.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations pendingmigrations.en.resx (Polish)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations clipboardcountdownbar.en.resx (Polish)
Update translations from Crowdin [ci skip]

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

* New translations general.en.resx (Finnish)
Update translations from Crowdin [ci skip]
2025-10-29 10:02:12 +01:00
Leendert de Borst
d8ef99207f Update login page logo margins for Android to prevent pill occlusion (#1319) 2025-10-29 00:14:50 +01:00
Leendert de Borst
c7182e7a21 Tweak app layout margins for iOS 26+ (#1319) 2025-10-29 00:14:50 +01:00
Leendert de Borst
fa451dc2cc Add passkey architecture documentation 2025-10-28 14:17:28 +01:00
Leendert de Borst
85d89b2b2c Bump wxt version (#1316) 2025-10-28 13:47:05 +01:00
Leendert de Borst
7d22bc34a7 Remove old argon2 types in mobile app 2025-10-28 13:23:21 +01:00
Leendert de Borst
b1a06cb2da Update docs (#1313) 2025-10-28 13:03:36 +01:00
Leendert de Borst
e5a15b2486 Update VersionCompatibility tests (#1313) 2025-10-28 13:03:36 +01:00
Leendert de Borst
c1e8a9b44e Add semantic versioning checks to vault SQL migrations to allow backwards compatible changes (#1313) 2025-10-28 13:03:36 +01:00
Leendert de Borst
d628e9cc4c New Crowdin updates (#1297)
* New translations creating.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations validationmessages.en.resx (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

* New translations infoplist.strings (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.xml (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

* New translations pendingmigrations.en.resx (Catalan)
Update translations from Crowdin [ci skip]

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

* New translations pendingmigrations.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Italian)
Update translations from Crowdin [ci skip]

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

* New translations pendingmigrations.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Turkish)
Update translations from Crowdin [ci skip]

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

* New translations pendingmigrations.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Portuguese, Brazilian)
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 (Catalan)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

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

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

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

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

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

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

* New translations emailmodal.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Finnish)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Italian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Italian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Italian)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Dutch)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Dutch)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Finnish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations clipboardcountdownbar.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Polish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Polish)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Hebrew)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Hebrew)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Hebrew)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Hebrew)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Hebrew)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Polish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Polish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Polish)
Update translations from Crowdin [ci skip]

* New translations pendingmigrations.en.resx (Finnish)
Update translations from Crowdin [ci skip]
2025-10-28 11:22:04 +01:00
Leendert de Borst
3a50b6e85b Persist custom API url during logout on Android (#1311) 2025-10-28 11:21:43 +01:00
Leendert de Borst
9641514b3b Add attachments credential filter to all clients (#1309) 2025-10-28 11:21:33 +01:00
Leendert de Borst
975ae9bd74 Pass information from JSInterop as base64 strings instead of byte arrays to bypass .NET issue (#1307) 2025-10-27 22:15:42 +01:00
Leendert de Borst
3bead0bbfc Merge branch 'main' of https://github.com/aliasvault/aliasvault
* 'main' of https://github.com/aliasvault/aliasvault:
  Improve FormDetector.ts to avoid overwriting already filled in fields (#1305)
  Fix private email domain check by doing exact comparison instead of wildcard (#1303)
2025-10-27 15:25:38 +01:00
Leendert de Borst
a77417c990 Cleanup mobile app translations 2025-10-27 15:23:53 +01:00
Leendert de Borst
dc48ac23dd Improve FormDetector.ts to avoid overwriting already filled in fields (#1305) 2025-10-27 15:19:08 +01:00
Leendert de Borst
4428f428dc Fix private email domain check by doing exact comparison instead of wildcard (#1303) 2025-10-27 15:08:49 +01:00
Leendert de Borst
5a6d317e31 Add manual clipboard clear button if automatic clipboard clear fails (#1301) 2025-10-27 14:53:57 +01:00
Leendert de Borst
6f24fd6453 Remove .map files from JS dist libs 2025-10-27 13:32:21 +01:00
Leendert de Borst
af60b2e22d Merge branch 'main' of https://github.com/aliasvault/aliasvault
* 'main' of https://github.com/aliasvault/aliasvault:
  Bump the npm_and_yarn group across 2 directories with 1 update
  Update native iOS search filter to use AND/OR (#1298)
  Improve credential search to use and/or in browser extension and mobile app (#1298)
2025-10-27 13:15:07 +01:00
Leendert de Borst
85642eab64 Update Docker static asset caching configuration 2025-10-27 13:15:00 +01:00
dependabot[bot]
8aad6f845e Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /shared/password-generator directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /shared/vault-sql directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.6 to 6.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

Updates `vite` from 7.1.5 to 7.1.12
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 7.1.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 13:10:29 +01:00
Leendert de Borst
4ba2c8e6ab Update native iOS search filter to use AND/OR (#1298) 2025-10-27 13:10:15 +01:00
Leendert de Borst
9da88cc7e7 Improve credential search to use and/or in browser extension and mobile app (#1298) 2025-10-27 13:10:15 +01:00
Leendert de Borst
e67fce5e39 Add cache busting to AliasVault.Client dynamically loaded JS dist libs 2025-10-27 13:10:07 +01:00
Leendert de Borst
3c94eb873d Improve browser extension auth settings UI (#1293) 2025-10-27 12:02:24 +01:00
Leendert de Borst
16418e1513 Update hyperlinks to be relative in admin (#1295) 2025-10-27 12:02:09 +01:00
Leendert de Borst
7ddb035f1a Merge pull request #1277 from aliasvault/520-feature-request-add-support-for-passkeys
Add support for passkeys
2025-10-27 11:52:11 +01:00
Leendert de Borst
f5c88639a6 Rebuild shared libraries (#520) 2025-10-27 11:32:55 +01:00
Leendert de Borst
d0baf8b6e0 Merge pull request #1292 from aliasvault/1257-add-russian-language-option
Add Russian language to apps
2025-10-27 11:29:26 +01:00
Leendert de Borst
6269b7ec7c Merge branch 'main' into 1257-add-russian-language-option 2025-10-27 11:26:55 +01:00
Leendert de Borst
5ee8d7a8f4 Add Portugese (Brazilian) as language option to apps (#1262) 2025-10-27 11:24:17 +01:00
Leendert de Borst
c1d41b3d8d Update IdentityHelperUtils.test.ts (#520) 2025-10-27 11:22:23 +01:00
Leendert de Borst
5fddf753f8 Merge branch '520-feature-request-add-support-for-passkeys' of https://github.com/aliasvault/aliasvault into 520-feature-request-add-support-for-passkeys
* '520-feature-request-add-support-for-passkeys' of https://github.com/aliasvault/aliasvault:
  Bump the npm_and_yarn group across 3 directories with 1 update
  Bump vite
  Update installation docs (#1280)
2025-10-27 11:12:39 +01:00
Leendert de Borst
712a9a0182 Update IdentityHelperUtils.ts (#520) 2025-10-27 11:12:26 +01:00
Leendert de Borst
f43f3cc51f Merge branch 'main' into 520-feature-request-add-support-for-passkeys 2025-10-27 10:57:40 +01:00
Leendert de Borst
99dc808de4 Clear CredentialIdentityStore contents on logout (#520) 2025-10-27 10:48:58 +01:00
Leendert de Borst
f97efea681 Add backup rules for CredentialIdentityStore kotlin implementation (#520) 2025-10-27 10:28:52 +01:00
Leendert de Borst
9ec245c102 Add initial credential identity store sync for iOS if store is empty (#520) 2025-10-27 10:25:18 +01:00
Leendert de Borst
fc9c59b077 Update iOS autofill setup setting link to general settings page (#520) 2025-10-27 10:10:16 +01:00
Leendert de Borst
5fe2c3ab4c Update PasskeyAuthenticator.ts (#520) 2025-10-27 09:35:38 +01:00
Leendert de Borst
2c4af6c85b Update AliasVaultPasskeyProvider.test.ts (#520) 2025-10-26 21:23:40 +01:00
Leendert de Borst
99a24c23e4 Cleanup PasskeyAuthenticator.ts (#520) 2025-10-26 21:20:08 +01:00
Leendert de Borst
1427693c1d Cleanup log statements (#520) 2025-10-26 21:05:41 +01:00
Leendert de Borst
619f402ca0 Refactor webauthn.ts to use proper response type (#520) 2025-10-26 21:03:02 +01:00
Leendert de Borst
71ddbbe3d2 Streamline passkey display name creation (#520) 2025-10-26 20:51:52 +01:00
Leendert de Borst
ad086689dd Add passkey indicator to browser extension autofill popup (#520) 2025-10-26 17:06:04 +01:00
Leendert de Borst
dc114c6bfa Add mobile app login flow abort when manually skipped flow (#520) 2025-10-26 15:15:59 +01:00
Leendert de Borst
9843142419 Add passkey origin fallback for native apps (#520) 2025-10-26 14:50:06 +01:00
dependabot[bot]
9ba698bb74 Bump the npm_and_yarn group across 3 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /shared/identity-generator directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /shared/password-generator directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.6 to 6.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

Updates `vite` from 6.3.6 to 6.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

Updates `vite` from 6.3.6 to 6.4.1
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.4.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 6.4.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-26 00:00:39 +02:00
Leendert de Borst
5185dfa41d Refactor CredentialIdentityStore scaffolding (#520) 2025-10-25 17:06:34 +02:00
Leendert de Borst
ea4d72ceca Update NativeVaultManager.kt to offload register credentials in background task (#520) 2025-10-25 16:41:47 +02:00
Leendert de Borst
b2206cae8f Refactor VaultStore kotlin to separate components (#520) 2025-10-25 16:39:16 +02:00
Leendert de Borst
1f8fb2ea39 Add DateHelpers to Kotlin passkey flow (#520) 2025-10-25 15:37:52 +02:00
Leendert de Borst
b2476ab5c5 Add date normalization to all clients (#520) 2025-10-24 23:49:54 +02:00
Leendert de Borst
866c8e7834 Update authenticatorAttachment setting (#520) 2025-10-24 21:37:02 +02:00
Leendert de Borst
fb01b75f3d Persist encryption key when enabling biometrics on Android (#520) 2025-10-24 21:25:04 +02:00
Leendert de Borst
8b05d2aafa Update initialize.tsx to redirect if no faceid (#520) 2025-10-24 20:43:20 +02:00
Leendert de Borst
4d54649c3a Add sanity check warning if biometric auth is not enabled (#520) 2025-10-24 17:30:45 +02:00
Leendert de Borst
a5c8ff91b5 Cleanup (#520) 2025-10-24 17:05:05 +02:00
Leendert de Borst
5164c705c2 Only show skip button during skippable phases (#520) 2025-10-24 16:24:00 +02:00
Leendert de Borst
c00088d955 Add client server version check to Android sync (#520) 2025-10-24 15:36:40 +02:00
Leendert de Borst
6698771fc4 Add explicit biometric auth for passkey create and authenticate flows (#520) 2025-10-24 15:28:20 +02:00
Leendert de Borst
665662982c Cleanup todos and refactor detekt issues (#520) 2025-10-23 19:04:46 +02:00
Leendert de Borst
c7d3a9ea1e Update Android credential filter to only include entries with username and pass (#520) 2025-10-22 22:30:24 +02:00
Leendert de Borst
c24598c151 Update autofill settings docs (#520) 2025-10-22 22:14:54 +02:00
Leendert de Borst
b995ec728c Update colors.xml (#520) 2025-10-22 22:02:17 +02:00
dependabot[bot]
234193e99b Bump vite
Bumps the npm_and_yarn group with 1 update in the /shared/vault-sql directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 7.1.5 to 7.1.11
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-22 16:41:57 +02:00
Leendert de Borst
af06bbfd12 Update Android light/dark mode system bar theme (#520) 2025-10-21 22:33:15 +02:00
Leendert de Borst
646416c069 Improve passkey replace flow UI and navigation structure (#520) 2025-10-21 22:06:23 +02:00
Leendert de Borst
219bc88e30 Add passkey replace flow (#520) 2025-10-21 16:42:54 +02:00
Leendert de Borst
020f11d3a4 Fix passkey create handshake return type for Chrome CredMan (#520)
WIP
2025-10-21 16:42:53 +02:00
Leendert de Borst
4cea8aae5e Fix passkey create in Firefox Android (#520) 2025-10-21 15:28:27 +02:00
Leendert de Borst
1db63bbc6b Add loading animation to Android as separate template (#520) 2025-10-20 22:05:02 +02:00
Leendert de Borst
00c230a92e Update iOS passkey create flow to prevent 25308 error (#520) 2025-10-20 21:48:57 +02:00
Leendert de Borst
868bdc9aa2 Add theme colors (#520) 2025-10-20 20:13:41 +02:00
Leendert de Borst
4c9de1fc2f Add passkey create activity view (#520) 2025-10-20 19:58:05 +02:00
Leendert de Borst
3adc796295 Update favicon extraction to properly detect nulls (#520) 2025-10-20 19:06:43 +02:00
Leendert de Borst
30d223aba6 Commit created passkey to vault (#520) 2025-10-20 18:50:34 +02:00
Leendert de Borst
6eb43c4f8b Add Android passkey registration scaffolding (#520) 2025-10-20 15:35:02 +02:00
Leendert de Borst
f0260622fd Refactor PasskeyAuthenticationActivity.kt (#520) 2025-10-20 13:24:37 +02:00
Leendert de Borst
a0269f90f3 Tweak initialize timeout skip button (#520) 2025-10-20 11:18:44 +02:00
Leendert de Borst
11ea12499b Simplify PasskeyAuthenticationActivity.kt (#520) 2025-10-20 10:44:05 +02:00
Leendert de Borst
4cff77b927 Update connection skip icon and title (#520) 2025-10-20 10:44:05 +02:00
Leendert de Borst
fa517c38c0 Cleanup (#520) 2025-10-20 10:44:05 +02:00
Leendert de Borst
5e1f899a5e Refactor Android credential manager passkey implementation to conform to spec (#520) 2025-10-20 10:44:05 +02:00
Leendert de Borst
e1318e2147 Add quick unlock type enum to show custom label in view (#520) 2025-10-19 15:23:40 +02:00
Leendert de Borst
ee9f3ca0f9 Tweak quick autofill flow on iOS with explicit loading view (#520) 2025-10-18 20:53:07 +02:00
Leendert de Borst
026cfb91e9 Tweak Android passkey authentication scaffolding (#520)
WIP
2025-10-18 20:53:07 +02:00
Leendert de Borst
0b78e5fa77 Fix UUID lookup casing (#520) 2025-10-17 16:18:04 +02:00
Leendert de Borst
d5b11cc34c Add passkey authentication scaffolding (#520) 2025-10-17 16:07:36 +02:00
Leendert de Borst
ddf34a2d30 Fix first time login authorization header overwrite bug (#520) 2025-10-17 15:33:07 +02:00
Leendert de Borst
37acd87c44 Fix context menu translations which prevented clickhandler from working (#520) 2025-10-17 13:55:48 +02:00
Leendert de Borst
efaa7962cb Tweak if available iOS flags (#520) 2025-10-17 13:04:35 +02:00
Leendert de Borst
d4f0579eea Update comments (#520) 2025-10-17 11:36:27 +02:00
Leendert de Borst
ac78bb1afc Update UI (#520) 2025-10-17 11:08:56 +02:00
Leendert de Borst
8d3034676b Tweak native vault sync flow called from React Native (#520) 2025-10-16 23:09:27 +02:00
Leendert de Borst
d9588acf00 Refactor shared methods to VaultUtils framework, cleanup unused methods (#520) 2025-10-16 22:29:33 +02:00
Leendert de Borst
f213b1ac57 Refactor todos (#520) 2025-10-16 21:15:09 +02:00
Leendert de Borst
5f49013235 Make iOS vault init more robust to prevent cold boot errors (#520) 2025-10-16 17:34:00 +02:00
Leendert de Borst
bb0bee7870 Refresh iOS autofill identities on every vault mutation (#520) 2025-10-16 11:26:11 +02:00
Leendert de Borst
7c64e656ff Refactor (#520) 2025-10-16 11:24:04 +02:00
Leendert de Borst
90e846674e Cleanup (#520) 2025-10-16 11:05:29 +02:00
Leendert de Borst
3d684e59ea Use displayname override for credential title instead of passkey displayname (#520) 2025-10-15 21:22:36 +02:00
Leendert de Borst
a4d728c9e5 Update SqliteClient.tsx to also mark passkey as soft deleted when credential is deleted (#520) 2025-10-15 21:12:46 +02:00
Leendert de Borst
74e8f1b840 Add passkey to credential view and AddEdit page (#520) 2025-10-15 21:10:41 +02:00
Leendert de Borst
774afaf522 Add credential filter and passkey recognition to web app (#520) 2025-10-15 18:21:54 +02:00
Leendert de Borst
92623493e8 Tweak UI (#520) 2025-10-14 21:53:38 +02:00
Leendert de Borst
53c4242342 Add passkey instructions to iOS autofill settings page (#520) 2025-10-14 21:05:44 +02:00
Leendert de Borst
ed5c436084 Refactor (#520) 2025-10-14 19:43:40 +02:00
Leendert de Borst
dd2b08a4a3 Add react native credential filter and passkey indicators (#520) 2025-10-14 19:00:52 +02:00
Leendert de Borst
dad709fc20 Refactor passkey logic implementation (#520) 2025-10-14 17:01:36 +02:00
Leendert de Borst
8964b1080d Update passkey schema (#520) 2025-10-14 15:32:57 +02:00
Leendert de Borst
5ec9e53449 Cleanup (#520) 2025-10-14 13:08:22 +02:00
Leendert de Borst
18182cdda2 Refresh credential list after credential delete (#520) 2025-10-14 12:49:32 +02:00
Leendert de Borst
33ed79e951 Add server min version supported check to native iOS sync implementation (#520) 2025-10-14 12:42:10 +02:00
Leendert de Borst
c044a27a3f Add error code throw and detection to native vault sync logic implementation (#520) 2025-10-14 11:59:16 +02:00
Leendert de Borst
95753e3fa9 Add explicit server offline error message to passkey create flow (#520) 2025-10-13 21:04:48 +02:00
Leendert de Borst
9a3df923b5 Update passkey registration UI, refactor folder structure (#520) 2025-10-13 20:49:26 +02:00
Leendert de Borst
c41bf8a921 Add passkey replace flow (#520) 2025-10-13 15:20:01 +02:00
Leendert de Borst
d93ec10cc9 Add title input field to passkey create screen (#520) 2025-10-13 14:24:50 +02:00
Leendert de Borst
385ee841dd Update terminology (#520) 2025-10-13 14:18:19 +02:00
Leendert de Borst
7c533de8f3 Add PRF evaluation support on passkey registration (#520) 2025-10-13 13:55:57 +02:00
Leendert de Borst
92fe915d0f Refactor (#520) 2025-10-12 23:26:18 +02:00
Leendert de Borst
1905078bdc Refactor PRF (#520) 2025-10-12 22:55:36 +02:00
Leendert de Borst
974315ed8c Add PRF support to iOS passkey mechanism (#520) 2025-10-12 20:06:12 +02:00
Leendert de Borst
d8b8fc7922 Update unlock error message margins (#520) 2025-10-12 17:14:59 +02:00
Leendert de Borst
795adab0dc Update passkey provider selection UI (#520) 2025-10-12 14:29:54 +02:00
Leendert de Borst
020d1bcfa1 Fix credential card selection popup positioning (#520) 2025-10-12 14:27:00 +02:00
Leendert de Borst
1efc06eaac Add SwiftUI translations into VaultUI project directly (#520) 2025-10-12 13:55:44 +02:00
Leendert de Borst
19c7da5dc6 Update passkey create UI (#520) 2025-10-11 19:59:34 +02:00
Leendert de Borst
e85a3cab7f Update passkey registration UI (#520) 2025-10-11 18:32:58 +02:00
Leendert de Borst
0ab5ca9377 Update loading indicator feedback (#520) 2025-10-11 17:15:07 +02:00
Leendert de Borst
48000b76eb Update swift loading animation (#520) 2025-10-10 23:16:50 +02:00
Leendert de Borst
c27300bcb3 Fix favicon extraction in passkey create flow (#520) 2025-10-10 22:03:42 +02:00
Leendert de Borst
48acb81492 Implement Swift passkey create persist flow (#520) 2025-10-10 18:33:49 +02:00
Leendert de Borst
09f61bd7a2 Cleanup RN AsyncStorage calls (#520) 2025-10-10 16:35:46 +02:00
Leendert de Borst
4bfe69750c Implement working vault mutate native flow (#520) 2025-10-10 13:05:05 +02:00
Leendert de Borst
afab20f59b Move vault sync/mutate to swift/kotlin layer (#520) 2025-10-10 12:50:24 +02:00
Leendert de Borst
3bc3c165f6 Move webapi calls to native swift/kotlin layer (#520) 2025-10-10 10:26:58 +02:00
Leendert de Borst
bc6f492208 Update local passkey create logic with proper date formatting (#520) 2025-10-09 16:20:34 +02:00
Leendert de Borst
fa4c80858c Implement swift passkey create logic and unittest (#520) 2025-10-09 15:52:38 +02:00
Leendert de Borst
6c94ed5193 Add passkey registration screen detection (#520) 2025-10-09 14:34:48 +02:00
Leendert de Borst
3658b606c2 Sync iOS CredentialIdentityStore via React Native callback (#520) 2025-10-09 13:37:18 +02:00
Leendert de Borst
01eee844de Implement iOS passkey selection callback (#520) 2025-10-08 19:16:25 +02:00
Leendert de Borst
ac7ea057d4 Show passkey specific credential view list on "show more" (#520) 2025-10-08 18:49:22 +02:00
Leendert de Borst
00023ea944 Make passkey authentication work on iOS (#520) 2025-10-08 16:43:15 +02:00
Leendert de Borst
bd78cfe778 Make webauthn quick fill suggestion work (#520) 2025-10-08 15:45:10 +02:00
Leendert de Borst
c2b6e8af1e Fix iOS passkey data type parsing (#520) 2025-10-08 15:30:05 +02:00
Leendert de Borst
f0fdfcdf19 Add passkeys to credential store for quicktype (#520) 2025-10-08 13:56:28 +02:00
Leendert de Borst
479e32ddac Enable iOS QuickType password autofill for iOS 26+ (#520) 2025-10-08 13:06:41 +02:00
Leendert de Borst
4661e36ef4 Add iOS passkey scaffolding (#520) 2025-10-08 12:43:59 +02:00
Leendert de Borst
26eb965b1d Add React Native passkey scaffolding (#520) 2025-10-08 12:26:43 +02:00
Leendert de Borst
ae4aeb6f45 Create fido_metadata.json (#520) 2025-10-08 10:36:04 +02:00
Leendert de Borst
5b62b035ee Add iOS passkey logic scaffolding (#520) 2025-10-07 16:46:11 +02:00
Leendert de Borst
8416c7c15f Store PRF secret in separate column (#520) 2025-10-07 13:27:45 +02:00
Leendert de Borst
1a9e1967ed Add FK repair script to migration to fix older vaults (#520) 2025-10-07 13:16:16 +02:00
Leendert de Borst
9156923f92 Add separate PrfKey column, recreate migrations (#520) 2025-10-07 10:44:46 +02:00
Leendert de Borst
b8a15930cd Fix passkey IsDeleted flag when deleting credential, fix favicon null handling (#520) 2025-10-07 09:45:28 +02:00
Leendert de Borst
544fea83b0 Refactor browser extension component directories (#520) 2025-10-06 23:41:16 +02:00
Leendert de Borst
032417aeec Tweak passkey card display (#520) 2025-10-06 23:35:28 +02:00
Leendert de Borst
30e213919d Tweak passkey create/authenticate screen UI (#520) 2025-10-06 23:25:30 +02:00
Leendert de Borst
98e52b8756 Add PRF extension support to webauthn passkey implementation (#520) 2025-10-06 18:43:27 +02:00
Leendert de Borst
240a0854be Clear pending redirect when opening main popup without redirect (#520) 2025-10-05 14:41:25 +02:00
Leendert de Borst
57f6ec1be7 Add passkey provider enable/disable toggle for specific website (#520) 2025-10-05 14:01:24 +02:00
Leendert de Borst
df9eacdf13 Remove passkeys list page (#520) 2025-10-05 12:36:27 +02:00
Leendert de Borst
eebf7aff41 Add filter to credential list (#520) 2025-10-05 12:28:26 +02:00
Leendert de Borst
10c9478238 Update credential card / details / add-edit to include passkeys (#520) 2025-10-05 11:12:38 +02:00
Leendert de Borst
3b1199d2db Cleanup passkey create and authenticate flows (#520) 2025-10-05 10:05:53 +02:00
Leendert de Borst
405b44383f Update passkey create flow to support replacing existing entries (#520) 2025-10-05 09:59:24 +02:00
Leendert de Borst
cf90721197 Update installation docs (#1280) 2025-10-04 13:50:41 +02:00
Leendert de Borst
b62078f97e Add passkey settings page (#520) 2025-10-03 15:55:49 +02:00
Leendert de Borst
74f4bc0ee9 Add modal layout for passkey popup actions (#520) 2025-10-03 14:17:10 +02:00
Leendert de Borst
7a65678ba2 Add unlock redirect hook with path restore (#520) 2025-10-03 13:41:30 +02:00
Leendert de Borst
2a208b5cff Refactoring (#520) 2025-10-03 12:48:13 +02:00
Leendert de Borst
6a0e8fc5ca Add AAGUID (#520) 2025-10-03 11:47:57 +02:00
Leendert de Borst
dad476548e Refactor base64url usage (#520) 2025-10-03 11:39:23 +02:00
Leendert de Borst
1cf49eed7e Integrate passkey create/get with vault storage (#520) 2025-10-03 09:34:53 +02:00
Leendert de Borst
04dfd41281 Update vault sql passkey model (#520) 2025-10-03 07:03:44 +02:00
Leendert de Borst
b31c94c582 Refactor WebAuthnInterceptor.ts (#520) 2025-10-02 17:39:41 +02:00
Leendert de Borst
5569202b9a Refactor AliasVaultPasskeyProvider.ts (#520) 2025-10-02 17:33:27 +02:00
Leendert de Borst
0ffb14ba0a Refactor (#520) 2025-10-02 16:21:39 +02:00
Leendert de Borst
db227894b6 Add webauthn types and return all required metadata fields (#520) 2025-10-02 13:44:03 +02:00
Leendert de Borst
46e217f523 Add response types and more unit tests (#520) 2025-10-02 11:16:19 +02:00
Leendert de Borst
d40d2d9c43 Do not close passkey popup windows for testing purposes (#520) 2025-10-02 10:52:15 +02:00
Leendert de Borst
1a5ed775de Update PasskeyHandler.ts (#520) 2025-10-02 10:42:29 +02:00
Leendert de Borst
a16d773686 Update passkey selection UI (#520) 2025-10-02 10:26:19 +02:00
Leendert de Borst
4ebb02795a Refactor passkey creation and retrieval to dedicated class (#520) 2025-10-02 07:18:35 +02:00
Leendert de Borst
5a70e7e20e Merge branch 'main' into 520-feature-request-add-support-for-passkeys
* main:
  Tweak browser extension server connection error flow
  Update FormDetector with additional birthdate field names (#1278)
2025-10-01 14:20:17 +02:00
Leendert de Borst
18ee97f6e5 Tweak browser extension server connection error flow 2025-10-01 14:20:02 +02:00
Leendert de Borst
4ffac949ee Set signCount to 0 (#520) 2025-10-01 13:33:45 +02:00
Leendert de Borst
db15c9ab25 Update FormDetector with additional birthdate field names (#1278) 2025-10-01 13:14:02 +02:00
Leendert de Borst
0ca4a7b8c7 Add signCount increment flow (#520) 2025-09-30 21:33:23 +02:00
Leendert de Borst
364093e789 Add packaged attestation support (#520) 2025-09-30 21:04:03 +02:00
Leendert de Borst
61c124364a Update passkey add and retrieve data flow (#520) 2025-09-30 20:51:45 +02:00
Leendert de Borst
0f62d15d74 Update PasskeyAuthenticate.tsx (#520) 2025-09-30 16:46:29 +02:00
Leendert de Borst
536c020bfb Make create passkey param passing work (#520) 2025-09-30 16:25:19 +02:00
Leendert de Borst
3c91103c3a Update webauthn keys (#520) 2025-09-30 16:12:42 +02:00
Leendert de Borst
3b196afe26 Merge branch 'main' into 520-feature-request-add-support-for-passkeys
* main:
  Refactor browser extension logout to not await webapi revoke
2025-09-30 15:44:15 +02:00
Leendert de Borst
68934ba48c Refactor browser extension logout to not await webapi revoke 2025-09-30 15:39:02 +02:00
Leendert de Borst
03ecc472b7 Update passkeys migration with new sql lib structure (#520) 2025-09-30 13:01:49 +02:00
Leendert de Borst
b103aab646 Merge branch 'main' into 520-feature-request-add-support-for-passkeys
* main: (31 commits)
  Fix versioning suffix mismatch in browser extension package.json (#1274)
  Fix versioning suffix in wxt.config.ts (#1274)
  Delete nginx.conf (superseded by port specific configs)
  Update AppInfo.ts (#1274)
  Bump app alpha build version (#1274)
  Fix app initialize and reinitialize layout (#1274)
  Update bump version to use semver for app store marketing version (#1274)
  Update login/unlock.tsx (#1274)
  Use API version for communicating app version with API instead of full version (#1274)
  Bump version to 0.24.0 alpha for testing mobile app (#1274)
  Update versioning to support stage suffix (#1274)
  Add browser extension vault upgrade test (#1274)
  Update VaultMessageHandler.ts (#1274)
  Simplify mobile app error handling preventing duplicate popups (#1274)
  Update error type location (#1274)
  Mobile app logout flow and AppContext refactor (#1274)
  Check both dev and build folders for browser extension test (#1274)
  Add GitHub workflow for running e2e browser extension tests (#1274)
  Update BrowserExtensionPlaywrightTest.cs (#1274)
  Make ChromeExtensionTests.cs tests pass locally (#1274)
  ...
2025-09-30 13:00:09 +02:00
Leendert de Borst
2d43858457 Fix versioning suffix mismatch in browser extension package.json (#1274) 2025-09-29 16:59:29 +02:00
Leendert de Borst
6b63b6b45d Fix versioning suffix in wxt.config.ts (#1274) 2025-09-29 16:46:43 +02:00
Leendert de Borst
1c9573eeb9 Delete nginx.conf (superseded by port specific configs) 2025-09-29 15:16:24 +02:00
Leendert de Borst
97141af1f1 Update AppInfo.ts (#1274) 2025-09-29 13:41:28 +02:00
Leendert de Borst
82a20e1fc5 Bump app alpha build version (#1274) 2025-09-29 13:32:46 +02:00
Leendert de Borst
75eea4162d Fix app initialize and reinitialize layout (#1274) 2025-09-29 13:32:10 +02:00
Leendert de Borst
5eb28d3ddf Update bump version to use semver for app store marketing version (#1274) 2025-09-29 13:32:10 +02:00
Leendert de Borst
257174c459 Update login/unlock.tsx (#1274) 2025-09-29 13:32:09 +02:00
Leendert de Borst
37c09c2c55 Use API version for communicating app version with API instead of full version (#1274) 2025-09-29 13:32:09 +02:00
Leendert de Borst
85348610a6 Bump version to 0.24.0 alpha for testing mobile app (#1274) 2025-09-29 13:32:09 +02:00
Leendert de Borst
9941473937 Update versioning to support stage suffix (#1274) 2025-09-29 13:32:09 +02:00
Leendert de Borst
afcef4f3bb Add browser extension vault upgrade test (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
a44e4102db Update VaultMessageHandler.ts (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
63c5d61616 Simplify mobile app error handling preventing duplicate popups (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
14cbce97d4 Update error type location (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
e5d924a094 Mobile app logout flow and AppContext refactor (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
46c364bbb4 Check both dev and build folders for browser extension test (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
7eef9b986f Add GitHub workflow for running e2e browser extension tests (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
af384ff6d1 Update BrowserExtensionPlaywrightTest.cs (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
3a62554fe2 Make ChromeExtensionTests.cs tests pass locally (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
717894c21c Update NavigationContext.tsx (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
2f8bc97a5a Prevent logout loop (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
5215a0bdb8 Add logout event emitter (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
624296da0d Add AppContext (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
c6028c4f32 Update translations (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
2e4caf8261 Remove obsolete vault status checks (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
5aea4aa6a1 Refactor browser extension logout flow (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
cad95e779d Improve vault upgrade unknown vault version flow in browser extension (#1274) 2025-09-28 10:56:51 +02:00
Leendert de Borst
c88b0d1d8a Add translations for client outdated in browser extension and mobile app (#1271) 2025-09-25 17:37:46 +02:00
Leendert de Borst
60371796f3 Update vault upgrade translations and web app mappings (#1271) 2025-09-25 17:26:06 +02:00
Leendert de Borst
ac3941f4aa Make vault upgrade pages show latest compatible version (#1271) 2025-09-25 17:26:06 +02:00
Leendert de Borst
dbae407df6 Add passkey proof-of-concept browser extension scaffolding (#520) 2025-09-25 15:50:04 +02:00
Leendert de Borst
181a27e94e Add passkey client db migration (#520) 2025-09-25 11:42:53 +02:00
Leendert de Borst
9a367acbdc Autofocus password field on web app unlock screen (#1269) 2025-09-25 06:28:27 +02:00
Leendert de Borst
938e8869f2 Update reinitialize.tsx (#1267) 2025-09-24 16:35:28 +02:00
Leendert de Borst
a9203600c1 Mark offline mode for manual unlock usecase correctly (#1267) 2025-09-24 16:35:28 +02:00
Leendert de Borst
ad2028e473 Update offline banner UX (#1267) 2025-09-24 16:35:28 +02:00
Leendert de Borst
7cb7c02bb2 Add explicit offline mode override button during app sync flow (#1267) 2025-09-24 16:35:28 +02:00
Leendert de Borst
836e33f821 Merge pull request #1265 from aliasvault/1264-bug-autofill-sometimes-shows-too-much-irrelevant-suggestions
Autofill sometimes shows too much irrelevant suggestions
2025-09-24 11:23:02 +02:00
Leendert de Borst
8d37e8ddbc Hide Android autofill items that do not have email/username/password info (#1264) 2025-09-23 17:48:58 +02:00
Leendert de Borst
b71f0b6a27 Update Filter.ts (#1264) 2025-09-23 17:17:35 +02:00
Leendert de Borst
375b2e3c12 Add service name that is being searched for to Android autofill list (#1264) 2025-09-23 12:40:04 +02:00
Leendert de Borst
216875ef05 Add common two level public TLDs to autofill matching implementations (#1264) 2025-09-23 10:55:24 +02:00
Leendert de Borst
ceaea5f214 Add max postgres pool size limits to avoid concurrency errors (#1260) 2025-09-23 09:36:20 +02:00
Leendert de Borst
fe20fb0bdb Update TwoFactorAuthController.cs (#1260) 2025-09-23 09:36:20 +02:00
Leendert de Borst
6a35ad4f98 Remove AuthLog UserAgent column, update DeviceIdentifier column length (#1260) 2025-09-23 09:36:20 +02:00
Leendert de Borst
a6cd33733f Update NDK version for full 16kb page size support (#1258) 2025-09-21 18:26:01 +02:00
Leendert de Borst
4b988e78ff Bump Android app build version (#1258) 2025-09-21 18:26:01 +02:00
Leendert de Borst
b96f01089f Update Android dependencies for 16kb page support (#1258) 2025-09-21 18:26:01 +02:00
Leendert de Borst
4875c50c90 Add Russian language to apps (#1257) 2025-09-20 10:08:38 +02:00
Leendert de Borst
8458a8cd19 Update docs with update instructions 2025-09-19 15:05:00 +02:00
Leendert de Borst
becec9dc95 Update changelogs (#1254) 2025-09-19 15:04:21 +02:00
Leendert de Borst
a4bdb22bf4 Add liquid glass icon to Safari browser extension launcher (#1254) 2025-09-19 15:03:55 +02:00
Leendert de Borst
48414dcae4 Bump install.sh version (#1254) 2025-09-19 14:39:13 +02:00
Leendert de Borst
151548f6f7 Bump versions (#1254) 2025-09-19 14:39:13 +02:00
Leendert de Borst
fd5c8096ad New Crowdin updates (#1222)
* New translations start.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* Add Ukrainian language (#1183)

* Add Hebrew language to all apps (#1182)

* New translations emailmodal.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (French)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Portuguese, Brazilian)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]
2025-09-19 14:38:19 +02:00
Leendert de Borst
09cfee2888 Add test case for nested form elements, refactor logic (#1252) 2025-09-19 12:48:02 +02:00
Leendert de Borst
74cb2eae7d Update password autofill to prevent duplicate character entry (#1252) 2025-09-19 12:48:02 +02:00
Leendert de Borst
35b8f0abae Prepopulate service title and URL based on current tab in browser extension (#1250) 2025-09-18 18:58:20 +02:00
Leendert de Borst
08517e3469 Add credential create popout icon in inline credential create as fallback (#1247) 2025-09-18 17:07:25 +02:00
Leendert de Borst
f3dabc3a39 Update last email/username placeholder to work like suggestions (#1247) 2025-09-18 17:07:25 +02:00
Leendert de Borst
d98f047963 Fix missing translations in confirm modals (#1244) 2025-09-18 13:30:18 +02:00
Leendert de Borst
599966996e Add liquid glass design optimized app icon to iOS app (#1239) 2025-09-18 12:45:33 +02:00
Leendert de Borst
952cfd9a28 Add argon2kt native implementation to Android (#1241) 2025-09-18 10:09:38 +02:00
Leendert de Borst
81a5155734 Replace argon2id react native with native iOS implementation to satisfy Xcode 26 reqs (#1241) 2025-09-18 10:09:38 +02:00
Leendert de Borst
3a953ec7c8 Add monochrome icon support to Android app (#1229) 2025-09-18 08:00:36 +02:00
dependabot[bot]
392dbd626c Bump rexml in /docs in the bundler group across 1 directory
Bumps the bundler group with 1 update in the /docs directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.9 to 3.4.2
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.9...v3.4.2)

---
updated-dependencies:
- dependency-name: rexml
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-18 08:00:24 +02:00
Leendert de Borst
b6d3f9e70f Run automatic Docker image cleanup after build and update (#1232) 2025-09-17 20:23:04 +02:00
Leendert de Borst
c2f2511f6a Delete CNAME 2025-09-17 19:09:27 +02:00
Leendert de Borst
ce2e21900f Add plausible to docs 2025-09-17 19:06:53 +02:00
Leendert de Borst
660b286ee9 Add clear alias fields button to web app (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
133037dcd8 Do not pregenerate password on credential create screen initialize (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
03b65a63ba Only overwrite email/username/pass if values were autogenerated during alias generation (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
f7a8189b86 Fix password field settings initialization (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
38973de6f1 Add clear alias fields button to mobile app (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
9ddd00bfa4 Add clear alias fields button to browser extension (#1235) 2025-09-17 19:00:31 +02:00
Leendert de Borst
88013161d1 Update email domain field behavior in browser extension and mobile app (#1231) 2025-09-17 12:58:04 +02:00
Leendert de Borst
b0da0d8590 Create CNAME 2025-09-17 09:50:56 +02:00
Leendert de Borst
7dcfd6bfd1 Delete CNAME 2025-09-17 09:41:31 +02:00
Leendert de Borst
586b0a3495 Update volume bind mounts to use local folder mounts 2025-09-17 09:14:33 +02:00
Leendert de Borst
30a009c5c4 Add docs local production docker-compose.yml 2025-09-17 09:10:10 +02:00
Leendert de Borst
7d73222ee1 Create SECURITY.txt 2025-09-16 15:10:11 +02:00
Leendert de Borst
6d191a1bd5 Rename SECURITY.md to ARCHITECTURE.md 2025-09-16 14:30:12 +02:00
Leendert de Borst
e5c68c6c6e Bump version to 0.23.1 (#1227) 2025-09-16 13:43:20 +02:00
Leendert de Borst
58c39815e4 Add more browser like behavior to improve FaviconExtractor success rate (#1225) 2025-09-16 13:19:22 +02:00
Leendert de Borst
4b706f466f Improve favicon extractor request handling (#1225) 2025-09-16 13:19:22 +02:00
Leendert de Borst
19f72b1386 Update self-signed SSL cert logic to use correct IP vs DNS name labels (#1223) 2025-09-16 11:40:00 +02:00
Leendert de Borst
b4d883dbf0 New Crowdin updates (#1220)
* 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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Dutch)
Update translations from Crowdin [ci skip]
2025-09-15 19:24:50 +02:00
Leendert de Borst
86f8f4ebdf Bump version to 0.23.0 (#1218) 2025-09-15 19:16:28 +02:00
Leendert de Borst
b5df1ed8dd Rebuild CSS (#1218) 2025-09-15 19:16:28 +02:00
Leendert de Borst
b2c25db5d9 Merge pull request #1185 from aliasvault/1181-optimize-all-in-one-docker-container-config-and-add-documentation
Optimize all in one docker container config and add documentation
2025-09-15 18:50:40 +02:00
Leendert de Borst
c0c876c694 Merge branch 'main' into 1181-optimize-all-in-one-docker-container-config-and-add-documentation 2025-09-15 18:49:42 +02:00
Leendert de Borst
b832d19e0e New translations en.json (Chinese Simplified) (#1217)
Update translations from Crowdin [ci skip]
2025-09-15 18:48:12 +02:00
Leendert de Borst
68214becad Add v0.23.0 update docs with new docker image locations (#1181) 2025-09-15 18:48:02 +02:00
Leendert de Borst
0971922518 New Crowdin updates (#1216)
* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* 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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations termsandconditionsstep.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations totpcodes.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations totpviewer.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations emailmodal.en.resx (Russian)
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 (Hebrew)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations emailpreview.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations emailrow.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations editemailformrow.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations defaultpasswordsettings.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations importservicecard.en.resx (Russian)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations showrecoverycodes.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations searchwidget.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations footer.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations topmenu.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations forgotpassword.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations logout.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations delete.en.resx (Hebrew)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (Hebrew)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations importexport.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations security.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations creating.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations errorvaultdecrypt.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (French)
Update translations from Crowdin [ci skip]

* New translations en.json (Hebrew)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Hebrew)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Hebrew)
Update translations from Crowdin [ci skip]

* New translations strings.xml (French)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Hebrew)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Russian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (French)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Hebrew)
Update translations from Crowdin [ci skip]
2025-09-15 17:20:27 +02:00
Leendert de Borst
1e9767b0bb Update fastlane descriptions 2025-09-15 16:37:34 +02:00
Leendert de Borst
3f12bdad9d Add instructions for using self-signed SSL cert with mobile apps (#1181) 2025-09-15 16:05:22 +02:00
Leendert de Borst
0ee17cc0ee Enable Android app local user added CA root cert compatibility (#1214) 2025-09-15 15:29:13 +02:00
Leendert de Borst
c7448f7e99 Fix mobile app login error to use correct translation key 2025-09-15 14:41:43 +02:00
Leendert de Borst
835b350d53 Improve self-signed SSL cert generation to take into account HOSTNAME env var (#1181) 2025-09-15 14:39:40 +02:00
Leendert de Borst
b7cbecc61d Add DateTime to/from conversion for all known formats to fix parsing and CSV export 2025-09-14 19:56:40 +02:00
Leendert de Borst
5e2f950b7e Force dates to be saved into vault with colons instead of periods for time separators (#1211) 2025-09-14 17:38:18 +02:00
Leendert de Borst
9a97a904fb Add credentials alphabetical sort option to web app (#1207) 2025-09-14 16:54:14 +02:00
Leendert de Borst
56b6753320 Remove hardcoded breadcrumb paths from breadcrumb component (#1208) 2025-09-14 16:20:50 +02:00
Leendert de Borst
f7675c0279 Remove duplicate translations 2025-09-14 13:57:42 +02:00
Leendert de Borst
961d237d42 Refine translation sources (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
47c2ae1e56 Refactor password-generator.tsx to fix Android freeze (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
9658a40c76 Update password-generator.tsx preview bg color (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
752ddaea9c Add password generator settings page to mobile app (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
5efc277316 Simplify AdvancedPasswordField.tsx (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
88b32efa97 Reflect password length in the hidden asterisks password display (#1204) 2025-09-13 18:22:32 +02:00
Leendert de Borst
03f692a62f Update short_description.txt 2025-09-13 10:30:51 +02:00
Leendert de Borst
bca8ffe676 Update import-export.tsx (#1103) 2025-09-12 22:33:47 +02:00
Leendert de Borst
d2590f4222 Add offline banner translations (#1103) 2025-09-12 22:33:47 +02:00
Leendert de Borst
ef245b2566 Add mobile app export import unit test (#1103) 2025-09-12 22:33:47 +02:00
Leendert de Borst
9ae92962d3 Add vault export to CSV option to mobile app (#1103) 2025-09-12 22:33:47 +02:00
Leendert de Borst
e52cd927a5 Update ResponsivePaginator.razor to take up less space (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
582f7c2ebc Add task runner tests for user active/inactive email cleanup task (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
ce5e5df644 Enable information logging for admin, smtp and task runner services (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
6a2e663c57 Update server settings UI (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
f6adb93518 Remove number of emails received from user listing page (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
077a4fb3ee Add user last active day tracking and email cleanup task (#1200) 2025-09-11 19:56:45 +02:00
Leendert de Borst
dc4fa1b487 Remove unused import (#1169) 2025-09-11 17:37:40 +02:00
Leendert de Borst
949b51defd Add password visibility toggle to client login and unlock pages (#1169) 2025-09-11 17:37:40 +02:00
Leendert de Borst
c2b824c31e Add password visibility toggle to browser extension login/unlock (#1169) 2025-09-11 17:37:40 +02:00
Leendert de Borst
cc846830fe Add password visibility toggle to mobile app login/unlock (#1197) 2025-09-11 17:37:40 +02:00
Leendert de Borst
f6ab23fa03 Linting refactor (#1197) 2025-09-11 14:48:20 +02:00
Leendert de Borst
44d84187c8 Update save header icon (#1197) 2025-09-11 14:48:20 +02:00
Leendert de Borst
fe78524e41 Update browser extension UI, standardize font sizes (#1197) 2025-09-11 14:48:20 +02:00
Leendert de Borst
adc0e8227f Hide email from/to information behind toggle to save on UI space (#1197) 2025-09-11 14:48:20 +02:00
Leendert de Borst
55cb24be68 Update browser extension folder structure (#1197) 2025-09-11 14:48:20 +02:00
Leendert de Borst
8efc021bd7 Make whole email row clickable in RecentEmails.razor (#1195) 2025-09-11 11:21:06 +02:00
Leendert de Borst
b649bdeb2e Update login page UI to show footer with app version (#1193) 2025-09-11 11:11:20 +02:00
Leendert de Borst
af4ca2e018 Hide language switcher in registration flow on small screens (#1191) 2025-09-11 10:02:31 +02:00
dependabot[bot]
1fa9606491 Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /shared/vault-sql directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.5 to 6.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

Updates `vite` from 6.3.5 to 7.1.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 7.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-10 22:21:41 +02:00
Leendert de Borst
7620fa8186 Fix clipboard copy animation warnings (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
4a5d42d65b Update keyboard margin on add-edit.tsx (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
af0f582090 Add explicit background color to Android native autofill rows (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
4f91ae7f1c Simplify translations (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
67c4b55cbb Update AdvancedPasswordField to prevent freezes on Android (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
7ff608b08c Refactor AdvancedPasswordField (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
4ebbea7825 Update Android build dependencies (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
1260e94199 Update linting (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
3b8d0d3a8a Add expo dev client package (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
2725646a6a Update KeyboardAwareScrollView (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
89cddcc626 Import buffer explicitly (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
f7d9d2a47c Update Android gradle files (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
60833efcda Update identity generator settings to only persist when navigating away (#1188) 2025-09-10 22:21:33 +02:00
Leendert de Borst
70208eb81a Fix package.json (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
ae6e734dc9 Mobile app replace screen after credential edit to preserve stack (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
f1fc2a5f96 Refactor to use RobustPressable to replace standard methods (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
b62621c9c6 Add type declaration to prevent lint warning (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
a372348dbf Add RobustPressable component (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
779d2a6b43 Update React Native Android (#1187) 2025-09-10 22:21:33 +02:00
Leendert de Borst
9510c0232f Update React Native to 0.79 and update iOS dependencies (#1187) 2025-09-10 22:21:33 +02:00
dependabot[bot]
1e97960eab Bump the npm_and_yarn group across 2 directories with 1 update
Bumps the npm_and_yarn group with 1 update in the /shared/identity-generator directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /shared/password-generator directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.3.4 to 6.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

Updates `vite` from 6.3.4 to 6.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 6.3.6
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-10 14:23:14 +02:00
Leendert de Borst
c756156e0d Update README.md 2025-09-09 16:22:15 +02:00
Leendert de Borst
af98a252c8 Update funding.json 2025-09-09 16:16:06 +02:00
Leendert de Borst
a7f016d73f Update FUNDING.yml 2025-09-09 16:12:48 +02:00
Leendert de Borst
3a287ebc77 Add NAS specific docker compose template (#1181) 2025-09-09 15:39:44 +02:00
Leendert de Borst
65c1a60447 Add filewatcher to reverse-proxy container to restart when SSL cert is updated (#635) 2025-09-09 14:29:06 +02:00
Leendert de Borst
c6906c8caf Update README.md (#1181) 2025-09-09 14:03:22 +02:00
Leendert de Borst
ace1bd7b0f Update docker-compose.all-in-one.yml (#1181) 2025-09-09 13:03:56 +02:00
Leendert de Borst
56e82cd046 Add optional FORCE_HTTPS_REDIRECT flag to install.sh method (#1181) 2025-09-09 10:50:59 +02:00
Leendert de Borst
58d6b4c67c Update instructions (#1181) 2025-09-09 10:08:57 +02:00
Leendert de Borst
7e4a0f6e07 Delete SolarLint.xml (#1181) 2025-09-09 07:40:48 +02:00
Leendert de Borst
b543696fa9 Update Admin login.razor (#1181) 2025-09-09 07:34:04 +02:00
Leendert de Borst
e669738e38 Update docker-build.yml (#1181) 2025-09-08 19:11:30 +02:00
Leendert de Borst
961977c9e2 Update docs (#1181) 2025-09-08 18:51:39 +02:00
Leendert de Borst
e3d2bec203 Update database import/export compatibility (#1181) 2025-09-08 18:51:18 +02:00
Leendert de Borst
75d9249577 Update styling (#1181) 2025-09-08 18:09:02 +02:00
Leendert de Borst
016a7e7559 Add aliasvault wrapper script to all-in-one Docker image (#1181) 2025-09-08 17:41:11 +02:00
Leendert de Borst
b6e7a2e77a Update admin first login message (#1181) 2025-09-08 17:40:47 +02:00
Leendert de Borst
fd9e62591e Add optional http to https redirect env setting (#1181) 2025-09-08 15:59:21 +02:00
Leendert de Borst
fd485b979c Add 301 redirects to jekyll docs (#1181) 2025-09-08 15:58:52 +02:00
Leendert de Borst
410e845811 Update self-host install titles (#1181) 2025-09-08 15:09:44 +02:00
Leendert de Borst
b5207d97fb Add comparison table to install method index page (#1181) 2025-09-08 14:03:54 +02:00
Leendert de Borst
3122dc4807 Update doc self-host titles (#1181) 2025-09-08 11:38:12 +02:00
Leendert de Borst
e010f0f57b Update ImportServices.en.resx 2025-09-08 11:24:34 +02:00
Leendert de Borst
864a7630d5 Tweak HTTPS required message, tweak crypto.js error handling (#1181) 2025-09-07 12:00:45 +02:00
Leendert de Borst
b603a177e2 Add update and troubleshooting docs (#1181) 2025-09-07 11:39:21 +02:00
Leendert de Borst
ee2fd9f9ae Add 404 and sitemap handler (#1181) 2025-09-07 10:52:40 +02:00
Leendert de Borst
a14066c43f Add database and uninstall docs for manual setup (#1181) 2025-09-07 10:35:39 +02:00
Leendert de Borst
1bcd088782 Add advanced and troubleshooting steps per self-host method (#1181) 2025-09-06 20:50:49 +02:00
Leendert de Borst
4ff937feec Update doc headings (#1181) 2025-09-06 15:02:51 +02:00
Leendert de Borst
77d49c52f0 Self-host docs refactor (#1181) 2025-09-06 10:33:54 +02:00
Leendert de Borst
f09cfecb13 Add HTTP warning for non-localhost hostnames (#1181) 2025-09-05 20:21:49 +02:00
Leendert de Borst
8655f15731 Support both HTTP and HTTPS in all in one docker image (#1181) 2025-09-05 19:05:45 +02:00
Leendert de Borst
d629ffb6e5 Update all-in-one build to prevent lock contention (#1181) 2025-09-05 17:49:37 +02:00
Leendert de Borst
21e0ad5017 Update all-in-one image to run in HTTP 80 mode (#1181) 2025-09-05 16:35:02 +02:00
Leendert de Borst
279a1f2ab2 Update docker-compose.all-in-one.yml (#1181) 2025-09-05 15:19:10 +02:00
Leendert de Borst
957be55927 Update funding.json 2025-09-05 09:18:19 +02:00
Leendert de Borst
63a8be657c Update docs HTML link 2025-09-04 22:22:27 +02:00
Leendert de Borst
7559f0aff4 Define labels and annotations per Docker image (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
c89afa613f Add annotations (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
7f449694c8 Add explicit title and description to release.yml to avoid it being overridden (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
8797b3b360 Add opencontainer labels to Dockerfile (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
4af333e22d Update manual docker publish release docs (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
17e8b6c16c Update release.yml (#1179) 2025-09-04 15:37:26 +02:00
Leendert de Borst
694f1d5e8f Update ghcr.io namespace to new aliasvault organization (#1177) 2025-09-04 13:58:04 +02:00
Leendert de Borst
6f32692342 Add ghcr.io namespace migration to install.sh (#1177) 2025-09-04 13:58:04 +02:00
Leendert de Borst
358d838f3b Update release.yml to publish images to new organization namespace (#1175) 2025-09-03 23:08:10 +02:00
Leendert de Borst
2e47486195 Add migrate-images.sh 2025-09-03 22:22:31 +02:00
Leendert de Borst
6936d4da3b Update docker container registry names 2025-09-03 22:01:59 +02:00
Leendert de Borst
17a7a57136 Remove sonarcloud analysis as new project settings are too restricted 2025-09-03 16:51:53 +02:00
Leendert de Borst
a3552471af Refactor (#1173) 2025-09-03 15:57:53 +02:00
Leendert de Borst
886208460b Update sonarcloud-code-analysis.yml with new organization name (#1171) 2025-09-03 14:59:14 +02:00
Leendert de Borst
a6fea3a60a Make curl follow redirects (#1171) 2025-09-03 14:59:14 +02:00
Leendert de Borst
fb9c2e1494 Update copyright header (#1171) 2025-09-03 14:59:14 +02:00
Leendert de Borst
2b259eee0c Update install.sh (#1171) 2025-09-03 14:59:14 +02:00
Leendert de Borst
d9a8e671a1 Update all repo URLS to point to new aliasvault organization (#1171) 2025-09-03 14:59:14 +02:00
Leendert de Borst
f9a9cb83c4 Update AllTimeStats.razor (#1167) 2025-09-03 09:07:44 +02:00
Leendert de Borst
3eae4b478f Make admin UI more responsive for mobile devices, update paginator (#1167) 2025-09-03 09:07:44 +02:00
Leendert de Borst
06dc2eadae Update release docs 2025-09-02 17:10:25 +02:00
Leendert de Borst
2fa11dab67 Update release.yml 2025-09-02 12:05:05 +02:00
Leendert de Borst
c73e3a489c Logging cleanup 2025-09-02 09:13:46 +02:00
Leendert de Borst
2b19d27902 Merge pull request #1164 from lanedirt/1163-prepare-0220-release
Prepare 0.22.0 release
2025-09-01 18:17:55 +02:00
Leendert de Borst
812302b9bc Merge branch '1163-prepare-0220-release' of https://github.com/lanedirt/AliasVault into 1163-prepare-0220-release
* '1163-prepare-0220-release' of https://github.com/lanedirt/AliasVault:
  Add 0.22.0 changelogs (#1163)
2025-09-01 17:55:30 +02:00
Leendert de Borst
4581dc8fd9 Bump browser extension safari build (#1163) 2025-09-01 17:55:01 +02:00
Leendert de Borst
42ba9d2869 Add 0.22.0 changelogs (#1163) 2025-09-01 17:54:51 +02:00
Leendert de Borst
773e6569c2 Add 0.22.0 changelogs (#1163) 2025-09-01 16:57:04 +02:00
Leendert de Borst
c24671ffb1 Bump version to 0.22.0 (#1163) 2025-09-01 16:56:53 +02:00
Leendert de Borst
cd87692588 Create funding.json 2025-09-01 15:17:18 +02:00
Leendert de Borst
15dc89ac07 New Crowdin updates (#1162)
* New translations general.en.resx (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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* Update source file en.json
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Chinese Simplified)
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 (Chinese Simplified)
Update translations from Crowdin [ci skip]
2025-09-01 11:45:57 +02:00
Leendert de Borst
a95757e982 Tweak browser extension autofill popup UI 2025-08-31 21:04:08 +02:00
Leendert de Borst
6061511d3c Update en.json 2025-08-31 20:55:29 +02:00
Leendert de Borst
cc873fd483 New Crowdin updates (#1152)
* New translations vaultdecryptionprogress.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations vaultdecryptionprogress.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations sync.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations changepassword.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations deleteaccount.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations enable2fa.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations resetvaultsection.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations disable2fa.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Catalan)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (German)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Italian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Russian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Swedish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Turkish)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations infoplist.strings (Chinese Simplified)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations strings.xml (German)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Finnish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Italian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Dutch)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Russian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Swedish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Turkish)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations strings.xml (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Catalan)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (German)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Finnish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Italian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Russian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Swedish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Turkish)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations localizable.strings (Chinese Simplified)
Update translations from Crowdin [ci skip]

* Refactor LanguageService.cs (#1079)

* Add new languages to apps (#1079)

* Update LanguageService.cs (#1079)

* Add language config to both AliasVault and Autofill targets for iOS (#1079)

* Update Program.cs to read available languages from LanguageService.cs (#1079)

* Add finnish language to all apps (#1079)

* Add german language (#1079)

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (French)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Spanish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Catalan)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations register.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations setup.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations start.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations passwordstep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations usernamestep.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations importservices.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations welcome.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations apierrors.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations resetvault.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations recentemails.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordsettingspopup.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations activesessionssection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations deleteaccountsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations passwordchangesection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations recentauthlogssection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations createnewidentitywidget.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations unlock.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations addedit.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations view.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations home.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations apps.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sync.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 en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* 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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Dutch)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Russian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Swedish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Turkish)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Chinese Simplified)
Update translations from Crowdin [ci skip]

* Update source file SharedResources.en.resx
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Finnish)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Finnish)
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 (German)
Update translations from Crowdin [ci skip]

* New translations quickvaultunlocksection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations twofactorauthenticationsection.en.resx (German)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations strings.xml (German)
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 (Catalan)
Update translations from Crowdin [ci skip]

* New translations en.json (German)
Update translations from Crowdin [ci skip]

* New translations en.json (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Dutch)
Update translations from Crowdin [ci skip]

* New translations en.json (Russian)
Update translations from Crowdin [ci skip]

* New translations en.json (Swedish)
Update translations from Crowdin [ci skip]

* New translations en.json (Turkish)
Update translations from Crowdin [ci skip]

* New translations en.json (Ukrainian)
Update translations from Crowdin [ci skip]

* New translations en.json (Chinese Simplified)
Update translations from Crowdin [ci skip]

* Update source file en.json
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 (Finnish)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations sharedresources.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations en.json (Italian)
Update translations from Crowdin [ci skip]

* New translations login.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations general.en.resx (Italian)
Update translations from Crowdin [ci skip]

* New translations validationmessages.en.resx (Italian)
Update translations from Crowdin [ci skip]
2025-08-31 17:13:40 +02:00
Leendert de Borst
8caa69e130 Prevent input fields from increasing in height on Android (#1160) 2025-08-31 17:11:57 +02:00
Leendert de Borst
c45d0c8f56 Add missing translation key to credential list search field in browser extension 2025-08-30 20:39:14 +02:00
Leendert de Borst
6c0fc44a66 Merge branch 'main' of https://github.com/lanedirt/AliasVault
* 'main' of https://github.com/lanedirt/AliasVault:
  Style refactor (#1157)
  Update ClipboardUtility.ts (#1157)
  Add missing translation (#1157)
  Add stubs for new NativeVaultManager spec for iOS (#1157)
  Add ignore battery optimization check for Android clipboard clear (#1157)
  Update native vault manager package namespace (#1157)
  Add android precise alarm timing implementation for clipboard clear (#1157)
  Implement native iOS clipboard clear after delay (#1157)
2025-08-29 20:39:10 +02:00
Leendert de Borst
3b88cb5b50 Update CredentialFilter.swift 2025-08-29 20:39:08 +02:00
Leendert de Borst
7314dc3d1d Style refactor (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
2c98b81111 Update ClipboardUtility.ts (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
fe7da551a4 Add missing translation (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
c4c29b11f3 Add stubs for new NativeVaultManager spec for iOS (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
ab740c093f Add ignore battery optimization check for Android clipboard clear (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
056f8e97e9 Update native vault manager package namespace (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
819924c6e2 Add android precise alarm timing implementation for clipboard clear (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
c6203b9e19 Implement native iOS clipboard clear after delay (#1157) 2025-08-29 19:07:48 +02:00
Leendert de Borst
347a72e55d Update CredentialFilter.swift 2025-08-29 11:40:06 +02:00
Leendert de Borst
30a2b1326c Autofocus browser extension unlock page 2025-08-29 11:39:43 +02:00
Leendert de Borst
4d66ea9694 Make refresh button spin counter clockwise (#1155) 2025-08-29 00:10:38 +02:00
Leendert de Borst
1cf28c43fb Add missing translations in web app (#1155) 2025-08-29 00:10:38 +02:00
Leendert de Borst
6a75e56123 Refactor client form model validation messages and add missing translations (#1153) 2025-08-28 13:19:28 +02:00
Leendert de Borst
ef72abceb4 Add missing translations for login and other client forms (#1153) 2025-08-28 13:19:28 +02:00
Leendert de Borst
19406cf58d Cleanup certificates dir (#1148) 2025-08-27 23:15:21 +02:00
Leendert de Borst
9fda76a5ff Use sequential builds (#1148) 2025-08-27 23:15:21 +02:00
Leendert de Borst
610d1b4654 Update all-in-one Dockerfile to reduce layers (#1148) 2025-08-27 23:15:21 +02:00
Leendert de Borst
602d59d268 Update release.yml (#1148) 2025-08-27 23:15:21 +02:00
Leendert de Borst
edae632025 Add all-in-one docker image push (#1148) 2025-08-27 23:15:21 +02:00
Leendert de Borst
2c3d2379ee Improve private email domain documentation in apps (#1150) 2025-08-27 16:40:22 +02:00
Leendert de Borst
70ed03e1b3 Update BaseImporter.cs (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
bf1a235dd2 Refactor (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
2bb7f0a742 Update Delete.razor margins (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
8cd5118749 Update KeePassImporter.cs (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
2fccb162e6 Add custom decoder support for importers (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
ad3c0323b9 Make CSV import more robust by handling special char decoding (#1146) 2025-08-26 23:42:00 +02:00
Leendert de Borst
9e859f6dc0 Update browser extension UI with settings subpages (#1144) 2025-08-26 13:23:44 +02:00
Leendert de Borst
5f70912b7a Update Filter.test.ts (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
dcc45eb5b6 Update app autofill matching (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
340d3943a2 Update CredentialMatcher.kt (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
64a879f72d Add autofill filter test for names with punctuation (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
0f8e1f7e15 Update autofill filter tests for mobile app (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
f86400fa50 Add autofill matching mode configurable setting to browser extension (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
047b0723b3 Use closed shadowroot for autofill popup (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
f785063065 Add clickjacking prevention measures through ClickValidator.ts (#1142) 2025-08-25 22:10:09 +02:00
Leendert de Borst
3720ad1961 Update translations 2025-08-25 14:40:22 +02:00
Leendert de Borst
fe617fc024 Update admin topmenu bg color and user icon style (#1140) 2025-08-25 12:43:49 +02:00
Leendert de Borst
1138b16daa Add popup open heartbeat, refactor background.ts (#1131) 2025-08-25 11:42:42 +02:00
Leendert de Borst
108a6855c2 Add vault autolock timer to browser extension (#1131) 2025-08-25 11:42:42 +02:00
Leendert de Borst
fb002e54b7 Add top users by credentials to admin all time stats (#1136) 2025-08-24 14:30:50 +02:00
Leendert de Borst
58ae63c74b Update browser extension popup search placeholder 2025-08-24 12:56:42 +02:00
Leendert de Borst
51287c85dc Update offscreen.js (#1134) 2025-08-23 17:53:44 +02:00
Leendert de Borst
b638e3375d Add shadowdom support to autofill form field detection (#1134) 2025-08-23 17:53:44 +02:00
Leendert de Borst
5d827bb7ac Tweak Settings.tsx UI 2025-08-23 17:53:44 +02:00
Leendert de Borst
666b3ccada Update email domain active entry styling (#1129) 2025-08-22 10:51:37 +02:00
Leendert de Borst
87a62000d3 Update modal background color (#1129) 2025-08-22 10:51:37 +02:00
Leendert de Borst
54c6e94751 Update style (#1129) 2025-08-22 10:51:37 +02:00
Leendert de Borst
54a5584baf Add email domain component to mobile app (#1129) 2025-08-22 10:51:37 +02:00
Leendert de Borst
ff48f1882f Merge branch 'main' of https://github.com/lanedirt/AliasVault
* 'main' of https://github.com/lanedirt/AliasVault:
  Bump vite-plugin-static-copy
2025-08-21 19:16:03 +02:00
Leendert de Borst
0b95203aac Update translation source to match web app 2025-08-21 19:15:49 +02:00
Leendert de Borst
3f5328ab3c Merge pull request #1132 from lanedirt/dependabot/npm_and_yarn/apps/browser-extension/npm_and_yarn-1975ee8f93
Bump vite-plugin-static-copy from 2.3.1 to 2.3.2 in /apps/browser-extension in the npm_and_yarn group across 1 directory
2025-08-21 19:02:58 +02:00
Leendert de Borst
f913d84557 Add email field domain chooser to browser extension (#1129) 2025-08-21 17:22:11 +02:00
dependabot[bot]
9a9752c557 Bump vite-plugin-static-copy
Bumps the npm_and_yarn group with 1 update in the /apps/browser-extension directory: [vite-plugin-static-copy](https://github.com/sapphi-red/vite-plugin-static-copy).


Updates `vite-plugin-static-copy` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/sapphi-red/vite-plugin-static-copy/releases)
- [Changelog](https://github.com/sapphi-red/vite-plugin-static-copy/blob/vite-plugin-static-copy@2.3.2/CHANGELOG.md)
- [Commits](https://github.com/sapphi-red/vite-plugin-static-copy/compare/vite-plugin-static-copy@2.3.1...vite-plugin-static-copy@2.3.2)

---
updated-dependencies:
- dependency-name: vite-plugin-static-copy
  dependency-version: 2.3.2
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 15:18:06 +00:00
Leendert de Borst
82458f74e3 Update user avatar style on unlock page (#1122) 2025-08-19 15:58:45 +02:00
Leendert de Borst
71633b166e Remove unused translations (#881) 2025-08-19 14:57:40 +02:00
Leendert de Borst
3305958e60 Tweak clipboard clear for various usecases and make it more robust (#881) 2025-08-19 14:57:40 +02:00
Leendert de Borst
4ae1f6ec35 Add clipboard clear delay in seconds setting (#881) 2025-08-19 14:57:40 +02:00
Leendert de Borst
4498833b4e Add clipboard countdown bar component (#881) 2025-08-19 14:57:40 +02:00
Leendert de Borst
7054593c07 Make clipboard clear work for mv3 and mv2 browsers (#881) 2025-08-19 14:57:22 +02:00
Leendert de Borst
6d197fe870 Make manifest browser specific (#881) 2025-08-19 14:57:22 +02:00
Leendert de Borst
d70eb0a447 Add clipboard clear and timer logic to background.ts, add offscreen API for Chrome 109+ (#881) 2025-08-19 14:57:22 +02:00
Leendert de Borst
aecb52de3c Add clipboard countdown bar component (#881) 2025-08-19 14:57:22 +02:00
Leendert de Borst
cd6ea06430 Add clear clipboard settings (#881) 2025-08-19 14:57:22 +02:00
Leendert de Borst
0d13440821 Merge pull request #1127 from lanedirt/881-feature-request-add-automatic-clipboard-clear-to-clients-after-copying-a-value-mobile
Add automatic clipboard clear to mobile apps after copying a value
2025-08-19 14:57:05 +02:00
Leendert de Borst
8e3da4b381 Add Android clear clipboard implementation and disclaimer to settings screen (#881) 2025-08-18 17:59:02 +02:00
Leendert de Borst
81538d4666 Refactor to use central clipboard clear timeout retrieval method (#881) 2025-08-18 17:35:22 +02:00
Leendert de Borst
634b7cada1 Add clipboard countdown context to keep global track of copied field id (#881) 2025-08-18 17:15:02 +02:00
Leendert de Borst
bed2c78964 Add clear clipboard animation to form input component (#881) 2025-08-18 16:04:59 +02:00
Leendert de Borst
a75392c573 Add clipboard clear settings page (#881) 2025-08-18 15:55:42 +02:00
Leendert de Borst
7b10665488 Add clear clipboard logic to mobile app iOS implementation (#881) 2025-08-18 15:31:54 +02:00
Leendert de Borst
ddf995db1d Update README.md 2025-08-18 12:33:07 +02:00
Leendert de Borst
8d9d55ce82 Update README.md 2025-08-18 12:23:46 +02:00
Leendert de Borst
ccf473635e Fix issue in iOS autofill where entire alias object would be null if birthdate was null (#1123) 2025-08-15 19:11:46 +02:00
Leendert de Borst
56c8b61e9e Make srpSalt check compatible with older API versions 2025-08-15 16:11:06 +02:00
Leendert de Borst
69234de51c Make autofill match tests match for all platforms (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
893c06cc00 Update Android autofill matching logic to match other platforms (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
b2c07f6de6 Only do text fallback search on credentials without a domain name (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
229fbd4824 Refactor iOS credential matching to use shared method with UI logic (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
48c5a5e38a Add test identifiers for easier cross-platform maintenance (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
5b3f36936a Add autofill matching unit tests to iOS Xcode project 2025-08-15 15:19:07 +02:00
Leendert de Borst
b4c696c89b Add autofill matching test cases to browser extension (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
d53c133812 Improve autofill matching to also support part of domain name (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
cbbfe1c611 Only convert service URL to anchor tag if it starts with http/https (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
437c7bb807 Make service URL field accept any value (#1120) 2025-08-15 15:19:07 +02:00
Leendert de Borst
03faee8d3a Cleanup unused translations 2025-08-14 18:26:07 +02:00
Leendert de Borst
e66a87e8df Add fallback to get encryption key (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
11f1daa08b Update derived key name in all methods (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
784e64ece8 Add srp salt sanity check to browser extension (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
4da1333aa5 Add SrpSalt check to the useVaultSync hook (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
65413c7ab7 Add SrpSalt to API status endpoint response (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
290e5329f8 Revoke user sessions during vault restore by admin (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
ec060d1392 Logout all user sessions after password change (#1118) 2025-08-14 18:18:43 +02:00
Leendert de Borst
293501405f Add public vs private email domain explanation to general settings page (#1116) 2025-08-13 22:04:46 +02:00
Leendert de Borst
783b2d44ef Add Dropbox Passwords import method (#1114) 2025-08-13 21:30:36 +02:00
Leendert de Borst
29d38759eb Update logging levels in admin and task runner 2025-08-12 19:52:35 +02:00
Leendert de Borst
97f30ad9ba Enable logging non-warnings to database log and adjust existing warning levels (#1112)
* Enable logging non-warnings to database log and adjust warnings (#443)

* Add log level filter (#443)

* Update General.razor (#443)
2025-08-12 17:39:26 +02:00
Leendert de Borst
c728d71868 Update Program.cs (#1110) 2025-08-11 23:23:12 +02:00
Leendert de Borst
27fc298b5e Add cancellation token to search fields in admin (#1110) 2025-08-11 23:23:12 +02:00
Leendert de Borst
6eb8266d05 Merge branch 'main' of https://github.com/lanedirt/AliasVault
* 'main' of https://github.com/lanedirt/AliasVault:
  Add reset admin password script for all-in-one image (#1108)
  Delete SINGLE-CONTAINER.md (#1108)
2025-08-11 22:05:31 +02:00
Leendert de Borst
f22cac70e9 Add known network config to admin to prevent proxy errors 2025-08-11 22:05:28 +02:00
Leendert de Borst
f1c94ea145 Update docs 2025-08-11 22:05:03 +02:00
Leendert de Borst
d587f3fd5c Add reset admin password script for all-in-one image (#1108) 2025-08-11 21:35:22 +02:00
Leendert de Borst
db874d3799 Delete SINGLE-CONTAINER.md (#1108) 2025-08-11 21:35:22 +02:00
Leendert de Borst
3f5b731703 Update tests (#1100) 2025-08-11 18:37:57 +02:00
Leendert de Borst
258981b2e4 Add user management tests (#1100) 2025-08-11 18:37:57 +02:00
Leendert de Borst
34b3545168 Add user name change option to admin (#1100) 2025-08-11 18:37:57 +02:00
Leendert de Borst
c37dafd228 Make breadcrumb urls relative in admin app (#1099) 2025-08-11 17:08:12 +02:00
Leendert de Borst
dbe15bdc51 Consolidate unnecessary translation keys (#1104) 2025-08-11 17:08:02 +02:00
Leendert de Borst
9eb4a3136a Add missing translations (#1104) 2025-08-11 17:08:02 +02:00
Leendert de Borst
747596615e Make API URL connection errors more descriptive (#1104) 2025-08-11 17:08:02 +02:00
Leendert de Borst
60221cf0e8 Update v0.22.0 migration docs (#1098) 2025-08-11 16:16:58 +02:00
Leendert de Borst
d9aa765284 Make nginx process wait for init to finish (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
b7a916e414 Add docker all-in-one build test, replacing pull test (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
110c0d2628 Update DbService.cs (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
ecfc6f948d Update install.sh status indicators (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
990d94397b Improve nginx status page (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
b861a30596 Remove env connectionstrings (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
583534fae9 Add status HTML to nginx to show if service is down or starting up (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
8136eb379d Remove startup dependencies from nginx (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
9f5c1b35c4 Update Dockerfile (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
7bd51fa2fe Make postgres connection support optional env overrides (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
4340ed48e6 Fix email claims retrieval (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
2fabc8c4dc Update docker-compose.dev.yml paths (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
99884b9761 Make data between all-in-one and multi-container setups compatible (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
c80a9c1b32 Add auto-migrate .env secrets to install.sh (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
3c993fe875 Update init script (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
ca1f3c3f64 Move folders (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
728b5c2a9c Add default env vars, update log (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
73600a49f8 Add notification script that's printed after all services are started (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
8a2e806311 Move aio docker files to subfolder 2025-08-11 13:18:45 +02:00
Leendert de Borst
9c8462f9ce Update container startup logging (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
e2fc9878b0 Improve verbosity config in aio image (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
f5f05703a0 Update init script (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
b30f8853aa Add update docs scaffolding for v0.22.0 (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
d85d62f3b4 Add installCli admin password generation to aio image (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
8bd8d688ef Add generic secretreader to support files when running in docker (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
c174a6bfb4 Update DataProtectionExtensions to load secrets from file when running under docker (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
3125eb3751 Update .gitignore (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
1e5a84b392 Update TaskRunnerWorker.cs (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
180977b833 Update DbService.cs (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
2d40e424e8 Refactor s6 config so each service has its separate run and type files (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
af0b5ff5f8 Add file based secret generation scaffolding (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
1b8e6cc6a1 Make services wait for postgres to be available and configured (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
eb04263751 Make clean startup work sharing directories with full docker compose setup (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
daccab9bcc Fix private email domain init (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
6577021bd7 Simplify PRIVATE_EMAIL_DOMAINS to default to empty string (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
de6ae7f7e1 Refactor to make certain env vars optional (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
a272aa11f2 Update self-signed cert generation logic (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
6cc77adbab Rename to allinone, make compatible with default nginx.conf (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
b6b476f9c8 Remove duplicated files (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
86aef6961c Update install.md (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
542f99c484 Rename dockerfile and update readme for clarity (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
6ce666a35d Move alternative docker related files to subdirectory (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
0ddd47b0e7 Update .env.example structure and explanation (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
f55d7717f8 Remove top level placeholder dirs which are automatically created during docker init or install.sh (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
1eaacd1ed0 Remove letsencrypt config from single docker setup (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
4b385e0ea2 Make admin work in single docker context (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
ff90cc2937 Make API work in single docker context (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
8bb6ec2b7c Make client appsettings.json replace work (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
7a4e55912c Make single docker stack boot (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
a1f97cd709 Add other service scaffolding to single docker (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
dbb2aa5610 Create Dockerfile.single (#1098) 2025-08-11 13:18:45 +02:00
Leendert de Borst
3af46c80fa Update bump-version.sh to use semantic build versions 2025-08-06 12:10:19 +02:00
Leendert de Borst
e10ef4bd75 Update linting fixes (#1085) 2025-08-06 08:53:19 +02:00
Leendert de Borst
54853c7a4d Refactor AuthContext to return translation keys instead of direct translations (#1085) 2025-08-06 08:45:12 +02:00
Leendert de Borst
1dde9ab4b4 Update sonarcloud-code-analysis.yml 2025-08-05 19:09:25 +02:00
Leendert de Borst
3585e20354 Add missing translations for Android biometrics and general vault unlock flow (#1085) 2025-08-05 15:28:03 +02:00
Leendert de Borst
c926933804 Update import order (#1085) 2025-08-05 15:28:03 +02:00
Leendert de Borst
5a43f7142c Add missing translations for mobile app (#1085) 2025-08-05 15:28:03 +02:00
Leendert de Borst
a15138afc8 Merge branch 'main' of https://github.com/lanedirt/AliasVault
* 'main' of https://github.com/lanedirt/AliasVault:
  Add changelog for 0.21.2 (#1095)
  Update bump-version.sh to show fastlane reminder (#1095)
  Bump version (#1095)
2025-08-05 14:25:23 +02:00
Leendert de Borst
bd62ecd8bd Update AliasVault Info.plist with exempt flag 2025-08-05 14:19:44 +02:00
Leendert de Borst
f48591685a Add changelog for 0.21.2 (#1095) 2025-08-05 13:49:24 +02:00
Leendert de Borst
cae1813084 Update bump-version.sh to show fastlane reminder (#1095) 2025-08-05 13:49:24 +02:00
Leendert de Borst
74e18a8fb1 Bump version (#1095) 2025-08-05 13:49:24 +02:00
Leendert de Borst
a89546200c Update sendEmailCLI.sh to test special char handling (#1093) 2025-08-05 13:22:57 +02:00
Leendert de Borst
a40f29d467 Make plain text emails more readable in browser extension (#1093) 2025-08-05 13:22:57 +02:00
Leendert de Borst
bcda120351 Render newlines for plain text emails in web app (#1093) 2025-08-05 13:22:57 +02:00
Leendert de Borst
ad1ffd63d5 Improve soft-delete cleanup mechanism to prevent EF related issues (#1091) 2025-08-05 12:14:31 +02:00
Leendert de Borst
4b55a21d33 Linting refactor (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
183548616e Update TaskRunnerTests.cs with per user email limits (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
4938129367 Add per user email limits configurable through admin (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
984f5a2c52 UI cleanup (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
5969a9d437 Update Entity Framework docs (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
efbb64637d Add TaskRunner to vscode build tasks (#1075) 2025-08-04 22:34:39 +02:00
Leendert de Borst
b460023911 Expand english identity generator dictionaries (#1087) 2025-08-04 22:28:59 +02:00
Leendert de Borst
c0e869a586 Always include birth year in email prefix to make aliases more unique (#1087) 2025-08-04 22:28:59 +02:00
Leendert de Borst
cd306ef878 Add top users by email table to admin all time stats page (#1082) 2025-08-04 21:27:11 +02:00
Leendert de Borst
1a40e31470 Make header right buttons on Android use Pressable instead of TouchableOpacity (#1080) 2025-08-04 19:16:04 +02:00
Leendert de Borst
30f9199a7e Prevent app re-initialization during cold boot and unlock/login (#1073) 2025-08-02 13:50:19 +02:00
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
2442 changed files with 275256 additions and 34458 deletions

View File

@@ -14,91 +14,71 @@
# 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.
# ===========================================
# NETWORK PORTS
# ===========================================
# Configure the network ports used by AliasVault by the `reverse-proxy` and `smtp` containers.
# You can change these if the defaults are already in use on your system.
# Requires a restart before taking effect.
HTTP_PORT=80
HTTPS_PORT=443
SMTP_PORT=25
SMTP_TLS_PORT=587
# Set the hostname that your AliasVault will be accessible at.
# E.g. `aliasvault.mydomain.com` or if you're running it on your local machine, choose `localhost`.
HOSTNAME=
# Whether to force redirect all HTTP traffic (80) to HTTPS (443). Defaults to true.
FORCE_HTTPS_REDIRECT=true
# Set a random 32 character string for the JWT key.
# This can be generated using the following command:
# $ openssl rand -base64 32
JWT_KEY=
# ===========================================
# EMAIL SERVER CONFIGURATION
# ===========================================
# Set the password for the data protection certificate.
# This can be generated using the following command:
# $ openssl rand -base64 32
DATA_PROTECTION_CERT_PASS=
# ----------------------------------------------------------------------------
# Database configuration
# ----------------------------------------------------------------------------
# These are the credentials that are used by the PostgreSQL container
# on startup to create the database and user, and for the application to
# connect to the database.
POSTGRES_DB=aliasvault
POSTGRES_USER=aliasvault
# Set the password for the database user.
# This can be generated using the following command:
# $ openssl rand -base64 32
POSTGRES_PASSWORD=
# Note: in order to change the password for an existing installation
# refer to https://docs.aliasvault.net/misc/dev/database-operations.html
# ----------------------------------------------------------------------------
# Admin user configuration
# ----------------------------------------------------------------------------
# Set the password for the admin user. This is an encrypted hash that needs
# to be generated using the `aliasvault-cli` tool. This allows you to login
# to the admin panel at https://your-hostname/admin.
#
# For example:
# docker run --rm ghcr.io/lanedirt/aliasvault-installcli:latest hash-password "my-password"
#
# Then copy the output and paste it into the ADMIN_PASSWORD_HASH variable below.
# When changing the hash, update the ADMIN_PASSWORD_GENERATED variable to the current date and time
# and then restart the AliasVault docker containers to apply the changes.
ADMIN_PASSWORD_HASH=
# Set the date and time the admin password was last generated. When changing the
# admin password hash manually, make sure to increase this value so the system
# knows that the password has been changed and should be overwritten with the new hash.
ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z
# ----------------------------------------------------------------------------
# Email server configuration for email aliases
# ----------------------------------------------------------------------------
# In order to use AliasVault's private email domains feature, you need to configure
# your DNS. Please refer to the full documentation for more instructions on DNS:
# https://docs.aliasvault.net/installation/install.html#3-email-server-setup
#
# Set the private email domains below that are allowed to be used (comma separated values).
# Set the private email domains below that the server should accept incoming mail for (comma separated values).
# Example: PRIVATE_EMAIL_DOMAINS=example.com,example2.org
# To disable the private email domains feature, set this to "DISABLED.TLD"
PRIVATE_EMAIL_DOMAINS=DISABLED.TLD
# To disable the private email domains feature, keep this empty.
PRIVATE_EMAIL_DOMAINS=
# Set whether TLS is enabled for SMTP.
# Set private email domains that should be hidden from UI components (comma separated values).
# These domains will still function as private email domains for receiving email and claims,
# but will not appear in domain selection dropdowns or settings. This is useful for deprecating
# legacy domains while maintaining backwards compatibility.
# Example: HIDDEN_PRIVATE_EMAIL_DOMAINS=old-domain.com,deprecated.org
# Note: Domains listed here should ALSO be included in PRIVATE_EMAIL_DOMAINS above.
HIDDEN_PRIVATE_EMAIL_DOMAINS=
# Enable TLS for SMTP.
# ⚠️ Requires valid TLS certificates on your mail server (not provided by the AliasVault installer).
# If set to true without proper certificates, the SMTP service will fail to start.
# For self-hosted setups, we recommend keeping this **false** unless you're sure how to configure it.
# Note: Disabling TLS does **not** impact email deliverability.
SMTP_TLS_ENABLED=false
# ----------------------------------------------------------------------------
# ===========================================
# Let's Encrypt configuration
# ----------------------------------------------------------------------------
# ===========================================
# Set whether Let's Encrypt is enabled. This is only supported through
# the install.sh script.
# the install.sh script and should be set to false for manual installations.
LETSENCRYPT_ENABLED=false
# ----------------------------------------------------------------------------
# Set the hostname that your AliasVault will be accessible at in order for LetsEncrypt
# to do its validation. This value is only required when LETSENCRYPT_ENABLED
# is set to true.
# Example: `aliasvault.mydomain.net`.
HOSTNAME=
# ===========================================
# Optional configuration settings
# ----------------------------------------------------------------------------
# ===========================================
# Enable or disable ability for new users to create an account via the web interface.
# Note: make sure you have created your (own) accounts before setting this to false.
PUBLIC_REGISTRATION_ENABLED=true
# Whether to enable IP logging for auth attempts. When set to true the last octet is
# always still anonymized, e.g. "127.0.0.1" becomes "127.0.0.xxx".
IP_LOGGING_ENABLED=true
# Set the support email address which is shown to users in the main web app.

1
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,3 @@
# These are supported funding model platforms
buy_me_a_coffee: lanedirt
open_collective: aliasvault

View File

@@ -44,6 +44,18 @@ runs:
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Configure Gradle JVM memory for CI
run: |
mkdir -p android
cat >> android/gradle.properties <<EOF
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.daemon.performance.disable-logging=true
org.gradle.daemon=true
org.gradle.caching=true
EOF
shell: bash
working-directory: apps/mobile-app
- name: Build JS bundle (Expo)
run: |
mkdir -p build

View File

@@ -35,6 +35,7 @@ jobs:
"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

View File

@@ -1,4 +1,4 @@
name: Docker Pull and Build
name: Docker Build Tests
on:
push:
@@ -11,125 +11,153 @@ concurrency:
cancel-in-progress: true
jobs:
docker-compose-pull:
name: Docker Compose Pull Test
docker-all-in-one-build:
name: Docker All-in-One Build Test
runs-on: ubuntu-latest
services:
docker:
image: docker:26.0.0
options: --privileged
steps:
- name: Get repository and branch information
id: repo-info
- uses: actions/checkout@v2
- name: Build all-in-one Docker image
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
docker build -f dockerfiles/all-in-one/Dockerfile -t aliasvault-allinone:test .
echo "✅ All-in-one Docker image built successfully"
- name: Run all-in-one container
run: |
docker run -d \
--name aliasvault-test \
-p 8080:80 \
-p 8443:443 \
-p 2525:25 \
-p 2587:587 \
-v "$(pwd)/database:/database" \
-v "$(pwd)/certificates:/certificates" \
-v "$(pwd)/logs:/logs" \
-v "$(pwd)/secrets:/secrets" \
aliasvault-allinone:test
- name: Wait for services to be ready
run: |
echo "Waiting for services to initialize..."
for i in {1..60}; do
if docker exec aliasvault-test curl -f http://localhost:3001/api 2>/dev/null; then
echo "✅ API service is ready"
break
fi
echo "Waiting for services... ($i/60)"
sleep 5
done
- name: Check container logs if needed
if: failure()
run: docker logs aliasvault-test
- name: Test root endpoint
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:8443/)
if [ "$http_code" -ne 200 ]; then
echo "❌ Root endpoint (/) failed with HTTP $http_code"
docker logs aliasvault-test
exit 1
fi
echo "✅ Root endpoint (/) returned HTTP 200"
- name: Test API endpoint
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:8443/api)
if [ "$http_code" -ne 200 ]; then
echo "❌ API endpoint (/api) failed with HTTP $http_code"
docker logs aliasvault-test
exit 1
fi
echo "✅ API endpoint (/api) returned HTTP 200"
- name: Test Admin endpoint
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:8443/admin/user/login)
if [ "$http_code" -ne 200 ]; then
echo "❌ Admin endpoint (/admin) failed with HTTP $http_code"
docker logs aliasvault-test
exit 1
fi
echo "✅ Admin endpoint (/admin) returned HTTP 200"
- name: Verify admin password hash file does not exist initially
run: |
if [ -f "./secrets/admin_password_hash" ]; then
echo "❌ Admin password hash file should not exist initially"
cat ./secrets/admin_password_hash
exit 1
fi
echo "✅ Admin password hash file correctly does not exist initially"
- name: Download install script from current branch
- name: Test admin password reset flow
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"
echo "🔧 Testing admin password reset flow..."
- name: Create .env file with custom SMTP port
run: echo "SMTP_PORT=2525" > .env
# Run the reset password script with auto-confirm
echo "Running reset-admin-password command..."
password_output=$(docker exec aliasvault-test aliasvault reset-admin-password -y 2>&1)
echo "Script output:"
echo "$password_output"
- 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
}
# Extract the generated password from the output
generated_password=$(echo "$password_output" | grep -E "^Password: " | sed 's/Password: //')
if [ -z "$generated_password" ]; then
echo "❌ Failed to extract generated password from script output"
echo "Full output was:"
echo "$password_output"
exit 1
fi
echo "✅ Generated password extracted: $generated_password"
- name: Run docker compose up
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: docker compose -f docker-compose.yml up -d
# Verify that the admin_password_hash file now exists in the container
if ! docker exec aliasvault-test test -f /secrets/admin_password_hash; then
echo "❌ Admin password hash file was not created in container"
docker exec aliasvault-test ls -la /secrets/
exit 1
fi
echo "✅ Admin password hash file created in container"
- name: Wait for services
if: ${{ !steps.install_script.outputs.skip_remaining }}
run: sleep 10
# Verify that the admin_password_hash file exists locally (mounted volume)
if [ ! -f "./secrets/admin_password_hash" ]; then
echo "❌ Admin password hash file not found in local secrets folder"
ls -la ./secrets/
exit 1
fi
echo "✅ Admin password hash file exists in local secrets folder"
- name: Test 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 }}
- name: Test SMTP port
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"
echo "SMTP port 2525 is not accessible"
docker logs aliasvault-test
exit 1
fi
echo "✅ SMTP port 2525 is accessible"
- name: Test reset-admin-password output
if: ${{ !steps.install_script.outputs.skip_remaining }}
- name: Cleanup
if: always()
run: |
output=$(./install.sh reset-admin-password)
if ! echo "$output" | grep -E '.*New admin password: [A-Za-z0-9+/=]{8,}.*'; then
echo "Invalid reset-admin-password output"
exit 1
fi
docker stop aliasvault-test || true
docker rm aliasvault-test || true
docker-compose-build:
name: Docker Compose Build Test
@@ -143,6 +171,21 @@ jobs:
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/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/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
@@ -197,9 +240,10 @@ jobs:
fi
- name: Test 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
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

@@ -24,6 +24,7 @@ jobs:
dotnet-version: 9.0.x
- name: Install dependencies
working-directory: apps/server
run: dotnet workload install wasm-tools
- name: Build
@@ -67,6 +68,7 @@ jobs:
dotnet-version: 9.0.x
- name: Install dependencies
working-directory: apps/server
run: dotnet workload install wasm-tools
- name: Build
@@ -86,3 +88,54 @@ jobs:
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 }}."
browser-extension-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: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
working-directory: apps/server
run: dotnet workload install wasm-tools
- name: Build server
working-directory: apps/server
run: dotnet build
- name: Build browser extension
working-directory: apps/browser-extension
run: |
npm install
npm run build:chrome
- 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 ExtensionTests 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=ExtensionTests"
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: extension-test-results
path: TestResults-Extension.xml

View File

@@ -24,6 +24,7 @@ jobs:
dotnet-version: 9.0.x
- name: Install dependencies
working-directory: apps/server
run: dotnet workload install wasm-tools
- name: Build

View File

@@ -23,6 +23,7 @@ jobs:
dotnet-version: 9.0.x
- name: Install dependencies
working-directory: apps/server
run: dotnet workload install wasm-tools
- name: Restore dependencies

View File

@@ -56,6 +56,7 @@ jobs:
"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

View File

@@ -4,10 +4,27 @@ on:
release:
types: [published]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
inputs:
build_browser_extensions:
description: 'Build browser extensions'
required: false
default: true
type: boolean
build_mobile_apps:
description: 'Build mobile apps'
required: false
default: true
type: boolean
build_multi_container:
description: 'Build and push multi-container images'
required: false
default: true
type: boolean
build_all_in_one:
description: 'Build and push all-in-one image'
required: false
default: true
type: boolean
jobs:
upload-install-script:
@@ -19,12 +36,14 @@ jobs:
uses: actions/checkout@v4
- name: Upload install.sh to release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: install.sh
token: ${{ secrets.GITHUB_TOKEN }}
build-chrome-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -34,11 +53,12 @@ jobs:
uses: ./.github/actions/build-browser-extension
with:
browser: chrome
upload_to_release: true
upload_to_release: ${{ github.event_name == 'release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-firefox-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -48,11 +68,12 @@ jobs:
uses: ./.github/actions/build-browser-extension
with:
browser: firefox
upload_to_release: true
upload_to_release: ${{ github.event_name == 'release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-edge-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -62,11 +83,12 @@ jobs:
uses: ./.github/actions/build-browser-extension
with:
browser: edge
upload_to_release: true
upload_to_release: ${{ github.event_name == 'release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-android-release:
if: github.event_name == 'release' || inputs.build_mobile_apps
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -76,7 +98,7 @@ jobs:
uses: ./.github/actions/build-android-app
with:
signed: true
upload_to_release: true
upload_to_release: ${{ github.event_name == 'release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
@@ -84,7 +106,8 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
build-and-push-docker:
build-and-push-docker-multi-container:
if: github.event_name == 'release' || inputs.build_multi_container
runs-on: ubuntu-latest
permissions:
contents: read
@@ -100,91 +123,316 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Convert repository name to lowercase
run: |
echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
- name: Log in to the Container registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
- name: Extract metadata for Postgres image
id: postgres-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}
images: ghcr.io/aliasvault/postgres
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault PostgreSQL
org.opencontainers.image.description=PostgreSQL database for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=PostgreSQL database for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for API image
id: api-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/api
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault API
org.opencontainers.image.description=REST API backend for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=REST API backend for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for Client image
id: client-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/client
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault Client
org.opencontainers.image.description=Blazor WebAssembly client UI for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=Blazor WebAssembly client UI for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for Admin image
id: admin-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/admin
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault Admin
org.opencontainers.image.description=Admin portal for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=Admin portal for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for Reverse Proxy image
id: reverse-proxy-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/reverse-proxy
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault Reverse Proxy
org.opencontainers.image.description=Nginx reverse proxy for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=Nginx reverse proxy for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for SMTP image
id: smtp-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/smtp
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault SMTP Service
org.opencontainers.image.description=SMTP service for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=SMTP service for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for TaskRunner image
id: task-runner-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/task-runner
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault TaskRunner
org.opencontainers.image.description=Background task runner for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
annotations: |
org.opencontainers.image.description=Background task runner for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net)
- name: Extract metadata for InstallCLI image
id: installcli-meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ghcr.io/aliasvault/installcli
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault Install CLI
org.opencontainers.image.description=Installation and configuration CLI for AliasVault. Used by install.sh for setup and configuration, not deployed as part of the application stack
annotations: |
org.opencontainers.image.description=Installation and configuration CLI for AliasVault. Used by install.sh for setup and configuration, not deployed as part of the application stack
- name: Build and push Postgres image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.postgres-meta.outputs.tags }}
labels: ${{ steps.postgres-meta.outputs.labels }}
annotations: ${{ steps.postgres-meta.outputs.annotations }}
- name: Build and push API image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.api-meta.outputs.tags }}
labels: ${{ steps.api-meta.outputs.labels }}
annotations: ${{ steps.api-meta.outputs.annotations }}
- name: Build and push Client image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.client-meta.outputs.tags }}
labels: ${{ steps.client-meta.outputs.labels }}
annotations: ${{ steps.client-meta.outputs.annotations }}
- name: Build and push Admin image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.admin-meta.outputs.tags }}
labels: ${{ steps.admin-meta.outputs.labels }}
annotations: ${{ steps.admin-meta.outputs.annotations }}
- name: Build and push Reverse Proxy image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.reverse-proxy-meta.outputs.tags }}
labels: ${{ steps.reverse-proxy-meta.outputs.labels }}
annotations: ${{ steps.reverse-proxy-meta.outputs.annotations }}
- name: Build and push SMTP image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.smtp-meta.outputs.tags }}
labels: ${{ steps.smtp-meta.outputs.labels }}
annotations: ${{ steps.smtp-meta.outputs.annotations }}
- name: Build and push TaskRunner image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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 }}
tags: ${{ steps.task-runner-meta.outputs.tags }}
labels: ${{ steps.task-runner-meta.outputs.labels }}
annotations: ${{ steps.task-runner-meta.outputs.annotations }}
- name: Build and push InstallCli image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
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: ${{ steps.installcli-meta.outputs.tags }}
labels: ${{ steps.installcli-meta.outputs.labels }}
annotations: ${{ steps.installcli-meta.outputs.annotations }}
build-and-push-docker-all-in-one:
if: github.event_name == 'release' || inputs.build_all_in_one
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata for all-in-one image
id: meta
uses: docker/metadata-action@v5
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: |
ghcr.io/aliasvault/aliasvault
aliasvault/aliasvault
tags: |
# For release events with latest tag (only for non-prerelease)
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
# semver tags for releases (works for prerelease and normal release)
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }}
# For tags, use tag name
type=ref,event=tag,enable=${{ github.ref_type == 'tag' }}
# For branches, use branch name and branch name + short SHA for uniqueness
type=ref,event=branch,enable=${{ github.ref_type == 'branch' }}
type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }}
labels: |
org.opencontainers.image.title=AliasVault All-in-One
org.opencontainers.image.description=Self-contained AliasVault server including web app, with all services bundled using s6-overlay. Single container solution for easy deployment (see docs.aliasvault.net).
annotations: |
org.opencontainers.image.description=Self-contained AliasVault server including web app, with all services bundled using s6-overlay. Single container solution for easy deployment (see docs.aliasvault.net).
- name: Build and push all-in-one image
uses: docker/build-push-action@v6
with:
context: .
file: dockerfiles/all-in-one/Dockerfile
platforms: linux/amd64,linux/arm64/v8
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}

View File

@@ -1,75 +0,0 @@
# This workflow will perform a SonarCloud code analysis on every push to the main branch or
# when a pull request is opened, synchronized, or reopened. The "pull_request_target" event is
# used to ensure that the analysis is done on the source branch of the pull request which has
# access to the SonarCloud token secret.
name: SonarCloud code analysis
on:
push:
branches:
- main
pull_request_target:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build and analyze
runs-on: windows-latest
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.x'
- name: Install WASM workload
run: dotnet workload install wasm-tools
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'zulu'
- name: Checkout code of PR branch
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
uses: actions/cache@v3
with:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
restore-keys: ${{ runner.os }}-sonar-scanner
- name: Install SonarCloud scanner
if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
shell: powershell
run: |
New-Item -Path .\.sonar\scanner -ItemType Directory
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
- name: Build and analyze
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') {
& $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/**"
} else {
& $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/**"
}
dotnet build
dotnet test -c Release /p:CollectCoverage=true /p:CoverletOutput=coverage /p:CoverletOutputFormat=opencover --filter 'FullyQualifiedName!~AliasVault.E2ETests'
& $scanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"

12
.gitignore vendored
View File

@@ -378,6 +378,10 @@ FodyWeavers.xsd
# Codebuddy Rider plugin
.codebuddy
# Claude Code
.claude
CLAUDE.md
# -------------------
# AliasVault specifics
# -------------------
@@ -400,8 +404,12 @@ certificates/**/*.crt
certificates/**/*.key
certificates/**/*.pfx
certificates/**/*.pem
certificates/**/.hostname_marker
certificates/letsencrypt/**
# Secrets
secrets/**
# Docs
docs/_site
docs/vendor
@@ -413,6 +421,7 @@ 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
@@ -422,3 +431,6 @@ temp
# Android keystore file (for publishing to Google Play)
*.keystore
# Safari extension build files
apps/browser-extension/safari-xcode/AliasVault/build

View File

@@ -23,5 +23,8 @@
"path": "../shared"
}
],
"settings": {}
"settings": {
"java.configuration.updateBuildConfiguration": "disabled",
"i18n-ally.keystyle": "nested"
}
}

30
.vscode/tasks.json vendored
View File

@@ -43,6 +43,34 @@
"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"
}
},
{
"label": "Build and watch TaskRunner",
"type": "shell",
"command": "dotnet watch",
"args": [],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/apps/server/Services/AliasVault.TaskRunner"
}
},
{
"label": "Build and watch Client CSS",
"type": "shell",
@@ -171,7 +199,7 @@
{
"label": "Build and watch Docs",
"type": "shell",
"command": "docker compose up",
"command": "docker compose -f docker-compose.dev.yml build && docker compose -f docker-compose.dev.yml up",
"problemMatcher": [],
"group": {
"kind": "build",

167
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,167 @@
# ARCHITECTURE.md
This document provides a high-level overview of the AliasVault architecture, focusing on the encryption algorithms used to ensure the security of user data.
## Overview
AliasVault implements zero-knowledge encryption using a combination of encryption algorithms to protect the privacy of its users.
The basic premise is that the master password chosen by the user upon registration forms the basis for all encryption and decryption operations. This master password is never transmitted over the network and only resides on the client.
### What is Zero-Knowledge Encrypted
- **Vault Data**: Your entire vault (passwords, usernames, notes, passkeys, email addresses, etc.) is fully encrypted client-side before being sent to the server. The server cannot decrypt any vault contents.
- **Email Contents**: When emails are received by the server, their contents are immediately encrypted with your public key (from your vault) before being saved. Only you can decrypt and read these emails with your private key.
This ensures that even if the AliasVault servers are compromised, vault contents and email messages remain secure and unreadable.
## Encryption algorithms
The following encryption algorithms and standards are used by AliasVault:
### Core Vault Encryption
- [Argon2id](#argon2id) - Key derivation from master password
- [SRP](#srp) - Secure authentication protocol
- [AES-GCM](#aes-gcm) - Vault data encryption
### Additional Features
- [RSA-OAEP](#rsa-oaep) - Email encryption
- [Passkeys (WebAuthn)](#passkeys-webauthn) - Passwordless authentication
- [Login with Mobile](#login-with-mobile) - Unlock vault in web app / browser extension via mobile app
Below is a detailed explanation of each encryption algorithm and standard.
For more information about how these algorithms are specifically used in AliasVault, see the [Architecture Documentation](https://docs.aliasvault.net/architecture) section on the documentation site.
### Argon2id
To derive a key from the master password, AliasVault uses the Argon2id key derivation function. Argon2id is a memory-hard
key derivation function which allows for controlling the execution time, memory required and degree of parallelism.
This makes it resilient against brute-force attacks and makes it one of the best choices for deriving keys from passwords.
AliasVault uses Argon2id with the following default parameters:
- Degree of parallelism: 1
- Memory size: 19456 KB
- Iterations: 2
More information about Argon2id can be found on the [Argon2](https://en.wikipedia.org/wiki/Argon2) Wikipedia page.
### SRP
The Secure Remote Password (SRP) protocol is used for authenticating a user with the AliasVault server during login.
The SRP protocol is a password-authenticated key exchange protocol (PAKE). This means that the client and server can
authenticate each other using a password, without sending the password itself over the network.
With the use of SRP the master password never leaves the client. The client sends a verifier to the server,
which is a value derived from the master password. The server uses this verifier to authenticate the client without
having ever seen the actual master password.
For more information see the [SRP protocol](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) information on Wikipedia.
### AES-256-GCM
All user's vault data is fully encrypted on the client using the AES-256-GCM encryption algorithm, which stands for
*Advanced Encryption Standard with 256-bit key in Galois/Counter Mode*. The key for encryption is derived from the
master password by using the Argon2Id algorithm. AliasVault implements AES-GCM with the following specifications:
- Key Size: 256 bits
- Uses the Web Crypto API's SubtleCrypto interface for secure cryptographic operations
- Generates a random 12-byte (96-bit) IV (initialization vector) for each encryption operation
- Performs all encryption/decryption operations entirely in the browser
#### The encryption process works as follows:
- A unique IV is generated for each encryption operation
- The users vault data is encrypted using AES-GCM with the derived key and IV
- The IV is prepended to the ciphertext
More information about AES-GCM can be found on the [AES-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) Wikipedia page.
### RSA-OAEP
To secure email communications, AliasVault uses RSA-OAEP (RSA with Optimal Asymmetric Encryption Padding). This asymmetric
encryption system allows AliasVault to store emails on the server in encrypted state which can only be read by the
intended recipient. AliasVault implements RSA-OAEP with the following specifications:
- Algorithm: RSA-OAEP with SHA-256 hash
- Key Size: 2048-bit modulus
- Key Format: JWK (JSON Web Key)
- Padding: OAEP (Optimal Asymmetric Encryption Padding)
#### Email Security Flow
1. Key Generation: When a user creates a vault, a RSA key pair is generated:
- A private key that remains in the encrypted user's vault and is never transmitted
- A public key that is sent to the server
2. Email Reception Process: When an email arrives at the AliasVault email server:
- The server generates a random 256-bit symmetric encryption key to encrypt the email contents
- The symmetric encryption key is encrypted using the recipient's asymmetric public key
- The encrypted email contents together with the encrypted symmetric encryption key are stored in the server's database
- The original email content is never stored or logged
3. Email Retrieval Process:
- When a user accesses their emails, the encrypted content is retrieved from the server
- The client-side application decrypts the symmetric encryption key using the user's private key that is stored in their vault
- The decrypted symmetric encryption key is used to decrypt the email contents
- Decryption occurs entirely in the browser, maintaining end-to-end encryption
This implementation ensures that:
- Emails are encrypted and secure at rest in the server database
- Only the intended recipient that holds the private key can decrypt and read their emails
- Even if the server is compromised, email contents remain encrypted and unreadable
More information about RSA-OAEP can be found on the [RSA-OAEP](https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding) Wikipedia page.
### Passkeys (WebAuthn)
AliasVault includes a virtual passkey authenticator that is fully compatible with the WebAuthn Level 2 specification. This enables users to securely store and use passkeys across their devices through the encrypted vault, providing a seamless and secure alternative to traditional password authentication.
#### Implementation Details
AliasVault implements passkey functionality across all supported platforms:
- **Browser Extension**: Virtual authenticator using the Web Crypto API
- **iOS**: Native Swift implementation using CryptoKit
- **Android**: Native Kotlin implementation using AndroidKeyStore
All implementations follow the WebAuthn Level 2 specification and use:
- ES256 (ECDSA P-256) for key pair generation
- CBOR/COSE encoding for attestation objects
- Proper authenticator data with WebAuthn flags (UP, UV, BE, BS, AT)
- AliasVault AAGUID (Authenticator Attestation GUID): `a11a5faa-9f32-4b8c-8c5d-2f7d13e8c942`
- Self-attestation (packed format) or none attestation
- Sign count always 0 for syncable passkeys
- BE/BS flags indicating backup-eligible and backed-up status
#### Key Features
1. **Zero-Knowledge Passkey Storage**: Passkey private keys are stored as encrypted entries in the user's vault alongside passwords and other credentials. The server never has access to the unencrypted private keys.
2. **Cross-Platform Sync**: Passkeys automatically sync across all devices where the user's vault is accessible, enabling seamless authentication on any platform (browser extension, iOS app, or Android app).
3. **PRF Extension Support**: Implements the hmac-secret (PRF) extension, allowing relying parties to derive additional secrets from passkeys for encryption keys or other cryptographic operations. Currently supported on browser extension and iOS; Android support is pending due to limited Android API support.
4. **Standards Compliance**: Full adherence to WebAuthn Level 2 specification ensures compatibility with all WebAuthn-compliant relying parties and services.
#### Security Benefits
- Private keys remain encrypted in the vault at all times
- All passkey operations (key generation, signing) occur on the client device
- Passkeys benefit from the same zero-knowledge architecture as passwords
- Cross-device sync provides convenience without compromising security
- Eliminates phishing risks through cryptographic domain binding
More information about WebAuthn can be found on the [WebAuthn specification](https://www.w3.org/TR/webauthn-2/) page.
### Login with Mobile
AliasVault provides a secure "Login with Mobile" feature that allows users to unlock their vault on web browsers or browser extensions by scanning a QR code with their authenticated mobile app. This convenient authentication method maintains zero-knowledge security through hybrid encryption.
#### Implementation Details
The mobile login system combines RSA-2048 asymmetric encryption with AES-256-GCM symmetric encryption:
1. **Initiation**: Browser/extension client generates an RSA-2048 key pair locally and sends the public key to the server, which returns a unique request ID displayed as a QR code.
2. **Authorization**: Mobile app scans the QR code, encrypts the user's vault decryption key with the RSA public key, and sends it to the server.
3. **Retrieval**: Browser client polls the server for completion. When ready, the server:
- Generates fresh JWT tokens for the session
- Creates a random AES-256 symmetric key
- Encrypts tokens and username with the symmetric key
- Encrypts the symmetric key with the client's RSA public key
- Returns encrypted data and immediately purges it from the database
4. **Decryption**: Client uses its RSA private key to decrypt the symmetric key, then uses the symmetric key to decrypt tokens and username, and the RSA private key to decrypt the vault decryption key.
#### Security Properties
- **Zero-Knowledge**: Server never accesses the vault decryption key in plaintext
- **One-Time Use**: Requests cannot be retrieved twice; data is immediately cleared after retrieval
- **Automatic Expiration**: Unfulfilled requests expire after 2 minutes client-side (3 minutes server-side); fulfilled but unretrieved requests auto-delete within 24 hours
- **MITM Protection**: Only the client with the RSA private key can decrypt the response
- **Limited Attack Surface**: Short timeout window minimizes QR code interception risks
More information about the mobile login flow can be found in the [Architecture Documentation](https://docs.aliasvault.net/architecture/#6-login-with-mobile).

View File

@@ -1,26 +1,85 @@
# 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.
### UI Translations
AliasVault UI translations are managed through [Crowdin](https://crowdin.com/), an online translation platform. If you'd like to help translate AliasVault into your native language, please [request access to the Crowdin project](https://crowdin.com/project/aliasvault).
You can also get in contact via [Discord](https://discord.gg/DsaXMTEtpF) to chat, or 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.
### Identity Generator Translations
In AliasVault, when creating a new credential AliasVault automatically generates realistic alias identities including: first names, last names and birthdates. For this AliasVault uses dictionaries of possible names per language. You can help to enable AliasVault to generate proper identities in your language too.
**How to help:**
- Create lists of common first names (male and female)
- Create a list of common last names (surnames)
- Optionally: Decade-specific names for more authentic generations
Read the specific instructions on how to contribute here: [Identity Generator Translations](https://docs.aliasvault.net/contributing/identity-generator.html).
## 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 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
## Contributor License Agreement (CLA)
Thank you for your interest in contributing to AliasVault (“Project”).
✅ 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 submitting code, documentation, or other contributions to this Project, you agree to the following:
1. You are legally entitled to grant this license (e.g., you are the author, or have permission).
2. You grant the Project maintainers a perpetual, worldwide, non-exclusive, royalty-free license to use, modify, distribute, and sublicense your contribution as part of the Project and any derivative works.
3. You understand that your contribution will be made public and licensed under the same terms as the Project (e.g., AGPLv3), or any later version the maintainers may release.
4. You are not expected to provide support or warranties for your contribution.
> All contributors must accept the CLA as a condition of contributing. By opening a pull request, you agree to these terms. We may enforce this automatically via GitHub if needed.
> By opening a pull request, you agree to these terms. Your contributions will be published under the AGPLv3 license.

View File

@@ -1,19 +1,21 @@
# <img src="https://github.com/user-attachments/assets/933c8b45-a190-4df6-913e-b7c64ad9938b" width="35" alt="AliasVault"> AliasVault
The privacy-first password & email alias manager. Fully end-to-end encrypted, with built-in alias generation and email server — giving you full control over your online identity and safeguarding your privacy.
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&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://img.shields.io/github/v/release/aliasvault/aliasvault?include_prereleases&logo=github&label=Release">](https://github.com/aliasvault/aliasvault/releases)
[![.NET E2E Tests (with Sharding)](https://github.com/aliasvault/aliasvault/actions/workflows/dotnet-e2e-tests.yml/badge.svg)](https://github.com/aliasvault/aliasvault/actions/workflows/dotnet-e2e-tests.yml)
[<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)
<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>
**⭐ Star us on GitHub, it motivates us a lot!**
If you enjoy using AliasVault, please also consider leaving a review on our apps or browser extensions, and share it with your friends or colleagues. Your support helps others discover a privacy-first alternative to traditional & closed-source password managers.
## About
AliasVault helps protect your privacy online by generating a unique password, identity, and email alias for every service you use. Everything is end-to-end encrypted and under your control — whether in the cloud or self-hosted.
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.
Built on 15 years of experience, AliasVault is 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), Creator of AliasVault
Leendert de Borst ([@lanedirt](https://github.com/lanedirt)), Creator of AliasVault
## Screenshots
@@ -47,40 +49,47 @@ Built on 15 years of experience, AliasVault is open-source, self-hostable and co
## 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](https://app.aliasvault.net) | [iOS](https://apps.apple.com/app/id6745490915) | [Android](https://play.google.com/store/apps/details?id=net.aliasvault.app) | [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)
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.
> [!NOTE]
> **Requirements:** 1 vCPU, 1GB RAM, 16GB disk, Docker ≥ 20.10, 64-bit Linux
### Install using install script
AliasVault can be self-hosted on your own servers using two different installation methods. Both use Docker, but they differ in how much is automated versus how much you manage yourself.
This method uses pre-built Docker images and works on minimal hardware specifications:
- **Option 1: Install Script** - Managed solution with automatic SSL (recommended for VPS/cloud)
- Linux VM with root access (Ubuntu/AlmaLinux recommended) or Raspberry Pi
- 1 vCPU
- 1GB RAM
- 16GB disk space
- Docker installed
- **Option 2: Docker Compose** - Single container with manual setup for use with existing SSL infrastructure (NAS, homelab)
### Quick Start (Install Script)
```bash
# Download install script from latest stable release
curl -L -o install.sh https://github.com/lanedirt/AliasVault/releases/latest/download/install.sh
# Download and run install script
curl -L -o install.sh https://github.com/aliasvault/aliasvault/releases/latest/download/install.sh
# Make install script executable and run it. This will create the .env file, pull the Docker images, and start the AliasVault containers.
chmod +x install.sh
./install.sh install
```
The install script will output the URL where the app is available. By default this is:
- Client: https://localhost
- Admin portal: https://localhost/admin
For other installation methods and more detailed steps, please read the [full installation guide](https://docs.aliasvault.net/installation) in the official docs.
> Note: If you want to change the default AliasVault ports you can do so in the `.env` file.
## Technical documentation
## 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)
## Security Architecture
@@ -93,7 +102,7 @@ AliasVault takes security seriously and implements various measures to protect y
- Zero-knowledge architecture ensures the server never has access to your unencrypted data
For detailed information about our encryption implementation and security architecture, see the following documents:
- [SECURITY.md](SECURITY.md)
- [ARCHITECTURE.md](ARCHITECTURE.md)
- [Security Architecture Diagram](https://docs.aliasvault.net/architecture)
## Features & Roadmap
@@ -115,17 +124,19 @@ Core features that are being worked on:
- [x] Import passwords from traditional password managers
- [x] iOS native app
- [x] Android native app
- [ ] Data model and usability improvements (more flexible aliases and credential types, folder support, editing in browser extension, bulk selecting etc.)
- [ ] Support for FIDO2/WebAuthn hardware keys and passkeys
- [x] Editing in browser extension
- [x] Multi-language support across all client applications
- [x] Passkeys
- [ ] Data model and usability improvements (more flexible aliases and credential types, folder support, bulk selecting etc.)
- [ ] Support for FIDO2/WebAuthn hardware keys
- [ ] Adding support for family/team sharing (organization features)
👉 [View the full AliasVault roadmap here](https://github.com/lanedirt/AliasVault/issues/731)
👉 [View the full AliasVault roadmap here](https://github.com/aliasvault/aliasvault/issues/731)
### 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.
Feel free to open an issue or discussion on GitHub. We warmly welcome all contributions: whether its translating, testing, helping to build features, sharing feedback - or helping spread the word about AliasVault. Every bit of support helps the project grow, so dont hesitate to jump in and [say hi to us on Discord](https://discord.gg/DsaXMTEtpF)!
### 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>
AliasVault is open-source and community-driven. If you like what were building, consider supporting us through [Open Collective](https://opencollective.com/aliasvault) or through:
<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>

View File

@@ -1,95 +0,0 @@
# SECURITY.md
This document describes the encryption algorithms used by AliasVault in order to keep its user data secure.
## Overview
AliasVault features a [zero-knowledge architecture](https://en.wikipedia.org/wiki/Zero-knowledge_service) and uses a combination of encryption algorithms to protect the data of its users.
The basic premise is that the master password chosen by the user upon registration forms the basis for all encryption
and decryption operations. This master password is never transmitted over the network and only resides on the client.
All data is encrypted at rest and in transit. This ensures that even if the AliasVault servers are compromised,
the user's data remains secure.
## Encryption algorithms
The following encryption algorithms are used by AliasVault:
- [Argon2id](#argon2id)
- [SRP](#srp)
- [AES-GCM](#aes-gcm)
- [RSA-OAEP](#rsa-oaep)
Below is a detailed explanation of each encryption algorithm.
For more information about how these algorithms are specifically used in AliasVault, see the [Architecture Documentation](https://docs.aliasvault.net/architecture) section on the documentation site.
### Argon2id
To derive a key from the master password, AliasVault uses the Argon2id key derivation function. Argon2id is a memory-hard
key derivation function which allows for controlling the execution time, memory required and degree of parallelism.
This makes it resilient against brute-force attacks and makes it one of the best choices for deriving keys from passwords.
AliasVault uses Argon2id with the following default parameters:
- Degree of parallelism: 1
- Memory size: 19456 KB
- Iterations: 2
More information about Argon2id can be found on the [Argon2](https://en.wikipedia.org/wiki/Argon2) Wikipedia page.
### SRP
The Secure Remote Password (SRP) protocol is used for authenticating a user with the AliasVault server during login.
The SRP protocol is a password-authenticated key exchange protocol (PAKE). This means that the client and server can
authenticate each other using a password, without sending the password itself over the network.
With the use of SRP the master password never leaves the client. The client sends a verifier to the server,
which is a value derived from the master password. The server uses this verifier to authenticate the client without
having ever seen the actual master password.
For more information see the [SRP protocol](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) information on Wikipedia.
### AES-256-GCM
All user's vault data is fully encrypted on the client using the AES-256-GCM encryption algorithm, which stands for
*Advanced Encryption Standard with 256-bit key in Galois/Counter Mode*. The key for encryption is derived from the
master password by using the Argon2Id algorithm. AliasVault implements AES-GCM with the following specifications:
- Key Size: 256 bits
- Uses the Web Crypto API's SubtleCrypto interface for secure cryptographic operations
- Generates a random 12-byte (96-bit) IV (initialization vector) for each encryption operation
- Performs all encryption/decryption operations entirely in the browser
#### The encryption process works as follows:
- A unique IV is generated for each encryption operation
- The users vault data is encrypted using AES-GCM with the derived key and IV
- The IV is prepended to the ciphertext
More information about AES-GCM can be found on the [AES-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) Wikipedia page.
### RSA-OAEP
To secure email communications, AliasVault uses RSA-OAEP (RSA with Optimal Asymmetric Encryption Padding). This asymmetric
encryption system allows AliasVault to store emails on the server in encrypted state which can only be read by the
intended recipient. AliasVault implements RSA-OAEP with the following specifications:
- Algorithm: RSA-OAEP with SHA-256 hash
- Key Size: 2048-bit modulus
- Key Format: JWK (JSON Web Key)
- Padding: OAEP (Optimal Asymmetric Encryption Padding)
#### Email Security Flow
1. Key Generation: When a user creates a vault, a RSA key pair is generated:
- A private key that remains in the encrypted user's vault and is never transmitted
- A public key that is sent to the server
2. Email Reception Process: When an email arrives at the AliasVault email server:
- The server generates a random 256-bit symmetric encryption key to encrypt the email contents
- The symmetric encryption key is encrypted using the recipient's asymmetric public key
- The encrypted email contents together with the encrypted symmetric encryption key are stored in the server's database
- The original email content is never stored or logged
3. Email Retrieval Process:
- When a user accesses their emails, the encrypted content is retrieved from the server
- The client-side application decrypts the symmetric encryption key using the user's private key that is stored in their vault
- The decrypted symmetric encryption key is used to decrypt the email contents
- Decryption occurs entirely in the browser, maintaining end-to-end encryption
This implementation ensures that:
- Emails are encrypted and secure at rest in the server database
- Only the intended recipient that holds the private key can decrypt and read their emails
- Even if the server is compromised, email contents remain encrypted and unreadable
More information about RSA-OAEP can be found on the [RSA-OAEP](https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding) Wikipedia page.

17
SECURITY.txt Normal file
View File

@@ -0,0 +1,17 @@
Contact: mailto:security@support.aliasvault.net
Expires: 2026-09-16T12:00:00.000Z
Preferred-Languages: en
Canonical: https://raw.githubusercontent.com/aliasvault/aliasvault/main/SECURITY.txt
# Security Policy for AliasVault
#
# We take security seriously and appreciate responsible disclosure of vulnerabilities.
# Please report security issues to the email above rather than opening public issues.
#
# Include the following information in your report:
# - Description of the vulnerability
# - Steps to reproduce
# - Potential impact
# - Suggested remediation (if any)
#
# We will acknowledge receipt within 48 hours and provide updates as we investigate.

View File

@@ -1,13 +0,0 @@
<SonarLint>
<Rules>
<Rule>
<Key>S1135</Key>
<Parameters>
<Parameter>
<Name>sonarlint.rule.enabled</Name>
<Value>false</Value>
</Parameter>
</Parameters>
</Rule>
</Rules>
</SonarLint>

1
apps/.version/major.txt Normal file
View File

@@ -0,0 +1 @@
0

1
apps/.version/minor.txt Normal file
View File

@@ -0,0 +1 @@
26

1
apps/.version/patch.txt Normal file
View File

@@ -0,0 +1 @@
0

1
apps/.version/suffix.txt Normal file
View File

@@ -0,0 +1 @@
-alpha

View File

@@ -0,0 +1 @@
0.26.0-alpha

View File

@@ -17,8 +17,6 @@ stats-*.json
web-ext.config.ts
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo

View File

@@ -0,0 +1,7 @@
{
"i18n-ally.localesPaths": [
"src/i18n",
"src/i18n/locales"
],
"i18n-ally.keystyle": "nested",
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "aliasvault-browser-extension",
"description": "AliasVault Browser Extension",
"private": true,
"version": "0.19.2",
"version": "0.26.0",
"type": "module",
"scripts": {
"dev:chrome": "wxt -b chrome",
@@ -30,10 +30,13 @@
"argon2-browser": "^1.18.0",
"buffer": "^6.0.3",
"globals": "^16.0.0",
"i18next": "^25.3.1",
"otpauth": "^9.3.6",
"qrcode": "^1.5.4",
"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",
@@ -45,6 +48,7 @@
"@types/chrome": "^0.0.280",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.13.10",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.0.7",
"@types/react-dom": "^19.0.3",
"@types/sql.js": "^1.4.9",
@@ -64,7 +68,7 @@
"postcss": "^8.5.1",
"tailwindcss": "^3.4.17",
"typescript": "^5.6.3",
"vite-plugin-static-copy": "^2.2.0",
"wxt": "^0.20.6"
"vite-plugin-static-copy": "^2.3.2",
"wxt": "^0.20.11"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>AliasVault Offscreen Document</title>
<!-- The offscreen.html is used for clipboard clearing. It is a hidden document that runs in a hidden context with access to clipboard operations. -->
</head>
<body>
<textarea id="text"></textarea>
<script src="offscreen.js"></script>
</body>
</html>

View File

@@ -0,0 +1,37 @@
/**
* Offscreen document for clipboard operations.
* This document runs in a hidden context with access to clipboard operations.
*/
// Listen for messages from the service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'CLEAR_CLIPBOARD') {
clearClipboard()
.then(() => {
sendResponse({ success: true, message: 'Clipboard cleared successfully' });
})
.catch((error) => {
console.error('[OFFSCREEN] Failed to clear clipboard:', error);
sendResponse({ success: false, message: error.message });
});
// Return true to indicate we'll send response asynchronously
return true;
}
});
const textEl = document.querySelector('#text');
/**
* Clear the clipboard by writing a space using execCommand.
*/
async function clearClipboard() {
try {
// Use execCommand to clear clipboard
textEl.value = '\n';
textEl.select();
document.execCommand('copy');
} catch (error) {
console.error('[OFFSCREEN] Error clearing clipboard:', error);
throw error;
}
}

View File

@@ -25,6 +25,10 @@
CE0CAFE02D81A9F8006174AB /* icon in Resources */ = {isa = PBXBuildFile; fileRef = CE0CAFD82D81A9F8006174AB /* icon */; };
CE0CAFE12D81A9F8006174AB /* assets in Resources */ = {isa = PBXBuildFile; fileRef = CE0CAFD92D81A9F8006174AB /* assets */; };
CE0CAFE22D81A9F8006174AB /* src in Resources */ = {isa = PBXBuildFile; fileRef = CE0CAFDA2D81A9F8006174AB /* src */; };
CE0E5BB02E7D8BA600515C80 /* AliasVault.icon in Resources */ = {isa = PBXBuildFile; fileRef = CE0E5BAF2E7D8BA600515C80 /* AliasVault.icon */; };
CEA194E42EB6221B00EAB23B /* webauthn.js in Resources */ = {isa = PBXBuildFile; fileRef = CEA194E32EB6221B00EAB23B /* webauthn.js */; };
CEA194E72EB6248D00EAB23B /* offscreen.html in Resources */ = {isa = PBXBuildFile; fileRef = CEA194E52EB6248D00EAB23B /* offscreen.html */; };
CEA194E82EB6248D00EAB23B /* offscreen.js in Resources */ = {isa = PBXBuildFile; fileRef = CEA194E62EB6248D00EAB23B /* offscreen.js */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -76,6 +80,10 @@
CE0CAFD82D81A9F8006174AB /* icon */ = {isa = PBXFileReference; lastKnownFileType = folder; name = icon; path = "../../../dist/safari-mv2/icon"; sourceTree = "<group>"; };
CE0CAFD92D81A9F8006174AB /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = "../../../dist/safari-mv2/assets"; sourceTree = "<group>"; };
CE0CAFDA2D81A9F8006174AB /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = "../../../dist/safari-mv2/src"; sourceTree = "<group>"; };
CE0E5BAF2E7D8BA600515C80 /* AliasVault.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AliasVault.icon; sourceTree = "<group>"; };
CEA194E32EB6221B00EAB23B /* webauthn.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = webauthn.js; path = "/Users/user/Projects/AliasVault/apps/browser-extension/dist/safari-mv2/webauthn.js"; sourceTree = "<absolute>"; };
CEA194E52EB6248D00EAB23B /* offscreen.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = offscreen.html; path = "/Users/user/Projects/AliasVault/apps/browser-extension/dist/safari-mv2/offscreen.html"; sourceTree = "<absolute>"; };
CEA194E62EB6248D00EAB23B /* offscreen.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = offscreen.js; path = "/Users/user/Projects/AliasVault/apps/browser-extension/dist/safari-mv2/offscreen.js"; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -117,6 +125,7 @@
CE0CAFA52D81A9F7006174AB /* AliasVault */ = {
isa = PBXGroup;
children = (
CE0E5BAF2E7D8BA600515C80 /* AliasVault.icon */,
CE0CAFA62D81A9F7006174AB /* AppDelegate.swift */,
CE0CAFB22D81A9F7006174AB /* ViewController.swift */,
CE0CAFB42D81A9F7006174AB /* Main.storyboard */,
@@ -154,6 +163,9 @@
CE0CAFD22D81A9F8006174AB /* Resources */ = {
isa = PBXGroup;
children = (
CEA194E52EB6248D00EAB23B /* offscreen.html */,
CEA194E62EB6248D00EAB23B /* offscreen.js */,
CEA194E32EB6221B00EAB23B /* webauthn.js */,
CE0CAFD32D81A9F8006174AB /* background.js */,
CE0CAFD42D81A9F8006174AB /* popup.html */,
CE0CAFD52D81A9F8006174AB /* chunks */,
@@ -248,6 +260,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CE0E5BB02E7D8BA600515C80 /* AliasVault.icon in Resources */,
CE0CAFAD2D81A9F7006174AB /* Icon.png in Resources */,
CE0CAFB12D81A9F7006174AB /* Script.js in Resources */,
CE0CAFAB2D81A9F7006174AB /* Base in Resources */,
@@ -262,8 +275,11 @@
buildActionMask = 2147483647;
files = (
CE0CAFDD2D81A9F8006174AB /* chunks in Resources */,
CEA194E42EB6221B00EAB23B /* webauthn.js in Resources */,
CE0CAFE02D81A9F8006174AB /* icon in Resources */,
CE0CAFE12D81A9F8006174AB /* assets in Resources */,
CEA194E72EB6248D00EAB23B /* offscreen.html in Resources */,
CEA194E82EB6248D00EAB23B /* offscreen.js in Resources */,
CE0CAFE22D81A9F8006174AB /* src in Resources */,
CE0CAFDB2D81A9F8006174AB /* background.js in Resources */,
CE0CAFDF2D81A9F8006174AB /* manifest.json in Resources */,
@@ -447,7 +463,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 2600100;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -460,7 +476,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.19.2;
MARKETING_VERSION = 0.26.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -479,7 +495,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "AliasVault Extension/AliasVault_Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 2600100;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -492,7 +508,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.19.2;
MARKETING_VERSION = 0.26.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -509,13 +525,14 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = AliasVault;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 2600100;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -530,7 +547,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.19.2;
MARKETING_VERSION = 0.26.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -549,12 +566,12 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_APPICON_NAME = AliasVault;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21;
CURRENT_PROJECT_VERSION = 2600100;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -569,7 +586,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.19.2;
MARKETING_VERSION = 0.26.0;
OTHER_LDFLAGS = (
"-framework",
SafariServices,

View File

@@ -0,0 +1,33 @@
{
"fill" : "automatic",
"groups" : [
{
"layers" : [
{
"blend-mode" : "overlay",
"fill" : {
"automatic-gradient" : "display-p3:0.90471,0.76358,0.48553,1.00000"
},
"glass" : true,
"hidden" : false,
"image-name" : "icon-1024.png",
"name" : "icon-1024"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View File

@@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "mac-icon-16@1x.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "mac-icon-16@2x.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "mac-icon-32@1x.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "mac-icon-32@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "mac-icon-128@1x.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "mac-icon-128@2x.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "mac-icon-256@1x.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "mac-icon-256@2x.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "mac-icon-512@1x.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "mac-icon-512@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env bash
BUNDLE_ID="net.aliasvault.safari.extension"
# Build settings
SCHEME="AliasVault"
PROJECT="AliasVault.xcodeproj"
CONFIG="Release"
ARCHIVE_PATH="$PWD/build/${SCHEME}.xcarchive"
EXPORT_DIR="$PWD/build/export"
EXPORT_PLIST="$PWD/exportOptions.plist"
# Put the fastlane API key in the home directory
API_KEY_PATH="$HOME/APPSTORE_CONNECT_FASTLANE.json"
# ------------------------------------------
if [ ! -f "$API_KEY_PATH" ]; then
echo "❌ API key file '$API_KEY_PATH' does not exist. Please provide the App Store Connect API key at this path."
exit 1
fi
# ------------------------------------------
# Shared function to extract version info
# ------------------------------------------
extract_version_info() {
local pkg_path="$1"
# For .pkg files, we need to expand and find the Info.plist
local temp_dir=$(mktemp -d -t aliasvault-pkg-extract)
trap "rm -rf '$temp_dir'" EXIT
# Expand the pkg to find the app bundle
pkgutil --expand "$pkg_path" "$temp_dir/expanded" 2>/dev/null
# Find the payload and extract it
local payload=$(find "$temp_dir/expanded" -name "Payload" | head -n 1)
if [ -n "$payload" ]; then
mkdir -p "$temp_dir/contents"
cd "$temp_dir/contents"
cat "$payload" | gunzip -dc | cpio -i 2>/dev/null
# Find Info.plist in the extracted contents
local info_plist=$(find "$temp_dir/contents" -name "Info.plist" -path "*/Contents/Info.plist" | head -n 1)
if [ -n "$info_plist" ]; then
# Read version and build from the plist
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$info_plist" 2>/dev/null)
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$info_plist" 2>/dev/null)
if [ -n "$VERSION" ] && [ -n "$BUILD" ]; then
return 0
fi
fi
fi
# Fallback: try to read from the archive directly if it's in a known location
local archive_plist="$ARCHIVE_PATH/Info.plist"
if [ -f "$archive_plist" ]; then
VERSION=$(/usr/libexec/PlistBuddy -c "Print :ApplicationProperties:CFBundleShortVersionString" "$archive_plist" 2>/dev/null)
BUILD=$(/usr/libexec/PlistBuddy -c "Print :ApplicationProperties:CFBundleVersion" "$archive_plist" 2>/dev/null)
if [ -n "$VERSION" ] && [ -n "$BUILD" ]; then
return 0
fi
fi
echo "❌ Could not extract version info from package"
exit 1
}
# ------------------------------------------
# Ask if user wants to build or use existing
# ------------------------------------------
echo ""
echo "What do you want to do?"
echo " 1) Build and submit to App Store"
echo " 2) Build only"
echo " 3) Submit existing PKG to App Store"
echo ""
read -p "Enter choice (1, 2, or 3): " -r CHOICE
echo ""
# ------------------------------------------
# Build PKG (for options 1 and 2)
# ------------------------------------------
if [[ $CHOICE == "1" || $CHOICE == "2" ]]; then
echo "Building browser extension..."
cd ../..
npm run build:safari
cd safari-xcode/AliasVault
echo "Building PKG..."
# Clean + archive
xcodebuild \
-project "$PROJECT" \
-scheme "$SCHEME" \
-configuration "$CONFIG" \
-archivePath "$ARCHIVE_PATH" \
clean archive \
-allowProvisioningUpdates
# Export .pkg
rm -rf "$EXPORT_DIR"
if ! xcodebuild -exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportOptionsPlist "$EXPORT_PLIST" \
-exportPath "$EXPORT_DIR" \
-allowProvisioningUpdates; then
echo "❌ Failed to export archive to PKG"
exit 1
fi
PKG_PATH=$(ls "$EXPORT_DIR"/*.pkg 2>/dev/null)
if [ -z "$PKG_PATH" ]; then
echo "❌ No PKG file found in $EXPORT_DIR after export"
echo "Contents of export directory:"
ls -la "$EXPORT_DIR"
exit 1
fi
# Extract version info from newly built PKG
extract_version_info "$PKG_PATH"
echo "PKG built at: $PKG_PATH"
echo " Version: $VERSION"
echo " Build: $BUILD"
echo ""
# Exit if build-only
if [[ $CHOICE == "2" ]]; then
echo "✅ Build complete. Exiting."
exit 0
fi
fi
# ------------------------------------------
# Submit to App Store (for options 1 and 3)
# ------------------------------------------
if [[ $CHOICE == "3" ]]; then
# Use existing PKG
PKG_PATH="$EXPORT_DIR/AliasVault.pkg"
if [ ! -f "$PKG_PATH" ]; then
echo "❌ PKG file not found at: $PKG_PATH"
exit 1
fi
# Extract version info from existing PKG
extract_version_info "$PKG_PATH"
echo "Using existing PKG: $PKG_PATH"
echo " Version: $VERSION"
echo " Build: $BUILD"
echo ""
fi
if [[ $CHOICE != "1" && $CHOICE != "3" ]]; then
echo "❌ Invalid choice. Please enter 1, 2, or 3."
exit 1
fi
echo ""
echo "================================================"
echo "Submitting to App Store:"
echo " PKG Path: $PKG_PATH"
echo " Version: $VERSION"
echo " Build: $BUILD"
echo "================================================"
echo ""
# Validate PKG_PATH is set and file exists
if [ -z "$PKG_PATH" ] || [ ! -f "$PKG_PATH" ]; then
echo "❌ Error: PKG file not found at: $PKG_PATH"
exit 1
fi
read -p "Are you sure you want to push this to App Store? (y/n): " -r
echo ""
if [[ ! $REPLY =~ ^([Yy]([Ee][Ss])?|[Yy])$ ]]; then
echo "❌ Submission cancelled"
exit 1
fi
echo "✅ Proceeding with upload..."
fastlane deliver --pkg "$PKG_PATH" --skip_screenshots --skip_metadata --api_key_path "$API_KEY_PATH" --run_precheck_before_submit false

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-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>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>automatic</string>
<key>destination</key>
<string>export</string>
<key>stripSwiftSymbols</key>
<true/>
<key>compileBitcode</key>
<true/>
<key>manageAppVersionAndBuildNumber</key>
<false/>
</dict>
</plist>

View File

@@ -2,7 +2,7 @@
@tailwind components;
@tailwind utilities;
@media (max-width: 400px) {
@media (max-width: 380px) {
html, body {
width: 350px;
max-width: 350px;

View File

@@ -1,10 +1,18 @@
/**
* Background script entry point - handles messages from the content script
*/
import { onMessage, sendMessage } from "webext-bridge/background";
import { handleResetAutoLockTimer, handlePopupHeartbeat, handleSetAutoLockTimeout } from '@/entrypoints/background/AutolockTimeoutHandler';
import { handleClipboardCopied, handleCancelClipboardClear, handleGetClipboardClearTimeout, handleSetClipboardClearTimeout, handleGetClipboardCountdownState } from '@/entrypoints/background/ClipboardClearHandler';
import { setupContextMenus } from '@/entrypoints/background/ContextMenu';
import { handleOpenPopup, handlePopupWithCredential, handleToggleContextMenu } from '@/entrypoints/background/PopupMessageHandler';
import { handleCheckAuthStatus, handleClearPersistedFormValues, handleClearVault, handleCreateIdentity, handleGetCredentials, handleGetDefaultEmailDomain, handleGetDefaultIdentityLanguage, handleGetDerivedKey, handleGetPasswordSettings, handleGetPersistedFormValues, handleGetVault, handlePersistFormValues, handleStoreVault, handleSyncVault, handleUploadVault } from '@/entrypoints/background/VaultMessageHandler';
import { handleGetWebAuthnSettings, handleWebAuthnCreate, handleWebAuthnGet, handlePasskeyPopupResponse, handleGetRequestData } from '@/entrypoints/background/PasskeyHandler';
import { handleOpenPopup, handlePopupWithCredential, handleOpenPopupCreateCredential, handleToggleContextMenu } from '@/entrypoints/background/PopupMessageHandler';
import { handleCheckAuthStatus, handleClearPersistedFormValues, handleClearVault, handleCreateIdentity, handleGetCredentials, handleGetFilteredCredentials, handleGetSearchCredentials, handleGetDefaultEmailDomain, handleGetDefaultIdentitySettings, handleGetEncryptionKey, handleGetEncryptionKeyDerivationParams, handleGetPasswordSettings, handleGetPersistedFormValues, handleGetVault, handlePersistFormValues, handleStoreEncryptionKey, handleStoreEncryptionKeyDerivationParams, handleStoreVault, handleSyncVault, handleUploadVault } from '@/entrypoints/background/VaultMessageHandler';
import { GLOBAL_CONTEXT_MENU_ENABLED_KEY } from '@/utils/Constants';
import { EncryptionKeyDerivationParams } from "@/utils/dist/shared/models/metadata";
import { defineBackground, storage, browser } from '#imports';
@@ -15,29 +23,62 @@ export default defineBackground({
async main() {
// Listen for messages using webext-bridge
onMessage('CHECK_AUTH_STATUS', () => handleCheckAuthStatus());
onMessage('GET_ENCRYPTION_KEY', () => handleGetEncryptionKey());
onMessage('GET_ENCRYPTION_KEY_DERIVATION_PARAMS', () => handleGetEncryptionKeyDerivationParams());
onMessage('GET_VAULT', () => handleGetVault());
onMessage('GET_CREDENTIALS', () => handleGetCredentials());
onMessage('GET_FILTERED_CREDENTIALS', ({ data }) => handleGetFilteredCredentials(data as { currentUrl: string, pageTitle: string, matchingMode?: string }));
onMessage('GET_SEARCH_CREDENTIALS', ({ data }) => handleGetSearchCredentials(data as { searchTerm: string }));
onMessage('GET_DEFAULT_EMAIL_DOMAIN', () => handleGetDefaultEmailDomain());
onMessage('GET_DEFAULT_IDENTITY_SETTINGS', () => handleGetDefaultIdentitySettings());
onMessage('GET_PASSWORD_SETTINGS', () => handleGetPasswordSettings());
onMessage('STORE_VAULT', ({ data }) => handleStoreVault(data));
onMessage('STORE_ENCRYPTION_KEY', ({ data }) => handleStoreEncryptionKey(data as string));
onMessage('STORE_ENCRYPTION_KEY_DERIVATION_PARAMS', ({ data }) => handleStoreEncryptionKeyDerivationParams(data as EncryptionKeyDerivationParams));
onMessage('CREATE_IDENTITY', ({ data }) => handleCreateIdentity(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_LANGUAGE', () => handleGetDefaultIdentityLanguage());
onMessage('GET_PASSWORD_SETTINGS', () => handleGetPasswordSettings());
onMessage('GET_DERIVED_KEY', () => handleGetDerivedKey());
onMessage('OPEN_POPUP', () => handleOpenPopup());
onMessage('OPEN_POPUP_WITH_CREDENTIAL', ({ data }) => handlePopupWithCredential(data));
onMessage('OPEN_POPUP_CREATE_CREDENTIAL', ({ data }) => handleOpenPopupCreateCredential(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());
// Clipboard management messages
onMessage('CLIPBOARD_COPIED', () => handleClipboardCopied());
onMessage('CANCEL_CLIPBOARD_CLEAR', () => handleCancelClipboardClear());
onMessage('GET_CLIPBOARD_CLEAR_TIMEOUT', () => handleGetClipboardClearTimeout());
onMessage('SET_CLIPBOARD_CLEAR_TIMEOUT', ({ data }) => handleSetClipboardClearTimeout(data as number));
onMessage('GET_CLIPBOARD_COUNTDOWN_STATE', () => handleGetClipboardCountdownState());
// Auto-lock management messages
onMessage('RESET_AUTO_LOCK_TIMER', () => handleResetAutoLockTimer());
onMessage('SET_AUTO_LOCK_TIMEOUT', ({ data }) => handleSetAutoLockTimeout(data as number));
onMessage('POPUP_HEARTBEAT', () => handlePopupHeartbeat());
// Handle clipboard copied from context menu
onMessage('CLIPBOARD_COPIED_FROM_CONTEXT', () => handleClipboardCopied());
// Passkey/WebAuthn management messages
onMessage('GET_WEBAUTHN_SETTINGS', ({ data }) => handleGetWebAuthnSettings(data));
onMessage('WEBAUTHN_CREATE', ({ data }) => handleWebAuthnCreate(data));
onMessage('WEBAUTHN_GET', ({ data }) => handleWebAuthnGet(data));
onMessage('PASSKEY_POPUP_RESPONSE', ({ data }) => handlePasskeyPopupResponse(data));
onMessage('GET_REQUEST_DATA', ({ data }) => handleGetRequestData(data));
// Setup context menus
const isContextMenuEnabled = await storage.getItem(GLOBAL_CONTEXT_MENU_ENABLED_KEY) ?? true;
if (isContextMenuEnabled) {
setupContextMenus();
await setupContextMenus();
}
// Listen for custom commands
@@ -77,4 +118,4 @@ function getActiveElementIdentifier() : string {
return target.id || target.name || '';
}
return '';
}
}

View File

@@ -0,0 +1,111 @@
import { storage } from 'wxt/utils/storage';
import { handleClearVault } from '@/entrypoints/background/VaultMessageHandler';
import { AUTO_LOCK_TIMEOUT_KEY } from '@/utils/Constants';
let autoLockTimer: NodeJS.Timeout | null = null;
/**
* Reset the auto-lock timer.
*/
export function handleResetAutoLockTimer(): void {
resetAutoLockTimer();
}
/**
* Handle popup heartbeat - extend auto-lock timer.
*/
export function handlePopupHeartbeat(): void {
extendAutoLockTimer();
}
/**
* Set the auto-lock timeout setting.
*/
export async function handleSetAutoLockTimeout(timeout: number): Promise<boolean> {
await storage.setItem(AUTO_LOCK_TIMEOUT_KEY, timeout);
resetAutoLockTimer();
return true;
}
/**
* Reset the auto-lock timer based on current settings.
*/
async function resetAutoLockTimer(): Promise<void> {
// Clear existing timer
if (autoLockTimer) {
clearTimeout(autoLockTimer);
autoLockTimer = null;
}
// Get timeout setting
const timeout = await storage.getItem(AUTO_LOCK_TIMEOUT_KEY) as number ?? 0;
// Don't set timer if timeout is 0 (disabled) or if vault is already locked
if (timeout === 0) {
return;
}
// Check if vault is unlocked before setting timer
const encryptionKey = await storage.getItem('session:encryptionKey') as string | null;
if (!encryptionKey) {
// Vault is already locked, don't start timer
return;
}
// Set new timer
autoLockTimer = setTimeout(async () => {
try {
// Lock the vault using the existing handler
handleClearVault();
console.info('[AUTO_LOCK] Vault locked due to inactivity');
autoLockTimer = null;
} catch (error) {
console.error('[AUTO_LOCK] Error locking vault:', error);
}
}, timeout * 1000);
}
/**
* Extend the auto-lock timer by the full timeout period.
* This is called by popup heartbeats to prevent locking while popup is active.
*/
async function extendAutoLockTimer(): Promise<void> {
// Get timeout setting
const timeout = await storage.getItem(AUTO_LOCK_TIMEOUT_KEY) as number ?? 0;
// Don't extend timer if timeout is 0 (disabled)
if (timeout === 0) {
return;
}
// Check if vault is unlocked
const encryptionKey = await storage.getItem('session:encryptionKey') as string | null;
if (!encryptionKey) {
// Vault is already locked, don't extend timer
return;
}
// Clear existing timer and start a new one
if (autoLockTimer) {
clearTimeout(autoLockTimer);
autoLockTimer = null;
}
// Set new timer
autoLockTimer = setTimeout(async () => {
try {
// Lock the vault using the existing handler
handleClearVault();
console.info('[AUTO_LOCK] Vault locked due to inactivity');
autoLockTimer = null;
} catch (error) {
console.error('[AUTO_LOCK] Error locking vault:', error);
}
}, timeout * 1000);
}

View File

@@ -0,0 +1,219 @@
import { sendMessage } from 'webext-bridge/background';
import { storage } from 'wxt/utils/storage';
import { CLIPBOARD_CLEAR_TIMEOUT_KEY } from '@/utils/Constants';
let clipboardClearTimer: NodeJS.Timeout | null = null;
let countdownInterval: NodeJS.Timeout | null = null;
let remainingTime = 0;
let currentCountdownId = 0;
let totalCountdownTime = 0;
let countdownStartTime = 0;
let offscreenDocumentCreated = false;
/**
* Create offscreen document if it doesn't exist.
*/
async function createOffscreenDocument(): Promise<void> {
if (offscreenDocumentCreated) {
return;
}
try {
// Check if chrome.offscreen API is available (Chrome 109+)
if (!chrome.offscreen) {
console.warn('[CLIPBOARD] Offscreen API not available, falling back to direct clipboard access');
return;
}
// Check if offscreen document already exists
if (chrome.runtime.getContexts) {
const existingContexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [chrome.runtime.getURL('offscreen.html')]
});
if (existingContexts.length > 0) {
offscreenDocumentCreated = true;
return;
}
}
// Create offscreen document
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Clear clipboard after timeout for security'
});
offscreenDocumentCreated = true;
} catch (error) {
console.error('[CLIPBOARD] Failed to create offscreen document:', error);
offscreenDocumentCreated = false;
}
}
/**
* Clear clipboard using offscreen document or fallback method.
*/
async function clearClipboardContent(): Promise<void> {
if (import.meta.env.CHROME || import.meta.env.EDGE) {
/*
* Chrome and Edge use mv3 and do not have direct access to clipboard
* so we use an offscreen document to clear the clipboard.
*/
await createOffscreenDocument();
// Send message to offscreen document to clear clipboard
const response = await chrome.runtime.sendMessage({ type: 'CLEAR_CLIPBOARD' });
if (response?.success) {
console.info('[CLIPBOARD] Clipboard cleared via offscreen document');
} else {
throw new Error(response?.message || 'Failed to clear clipboard via offscreen');
}
} else {
// Firefox and Safari use mv2 and can use direct clipboard access.
await navigator.clipboard.writeText('');
}
}
/**
* Handle clipboard copied event - starts countdown and timer to clear clipboard.
*/
export async function handleClipboardCopied() : Promise<void> {
const timeout = await storage.getItem(CLIPBOARD_CLEAR_TIMEOUT_KEY) as number ?? 10;
// Clear any existing timer
if (clipboardClearTimer) {
clearTimeout(clipboardClearTimer);
clipboardClearTimer = null;
}
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
// Don't set timer if timeout is 0 (disabled)
if (timeout === 0) {
return;
}
// Generate new countdown ID
currentCountdownId = Date.now();
const thisCountdownId = currentCountdownId;
countdownStartTime = Date.now();
totalCountdownTime = timeout;
remainingTime = timeout;
// Send initial countdown immediately with ID
sendMessage('CLIPBOARD_COUNTDOWN', { remaining: remainingTime, total: timeout, id: thisCountdownId }, 'popup').catch(() => {});
// Send countdown updates to popup every 100ms for smooth animation
let elapsed = 0;
countdownInterval = setInterval(() => {
// Check if this countdown is still active
if (thisCountdownId !== currentCountdownId) {
if (countdownInterval) {
clearInterval(countdownInterval);
}
return;
}
elapsed += 0.1;
remainingTime = Math.max(0, timeout - elapsed);
sendMessage('CLIPBOARD_COUNTDOWN', { remaining: remainingTime, total: timeout, id: thisCountdownId }, 'popup').catch(() => {});
if (elapsed >= timeout && countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
}, 100);
// Set timer to clear clipboard
clipboardClearTimer = setTimeout(async () => {
try {
// Clear clipboard using offscreen document or fallback
await clearClipboardContent();
// Clean up regardless of success/failure
clipboardClearTimer = null;
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
// Reset countdown tracking
currentCountdownId = 0;
countdownStartTime = 0;
totalCountdownTime = 0;
sendMessage('CLIPBOARD_CLEARED', {}, 'popup').catch(() => {});
} catch (error) {
console.error('[CLIPBOARD] Error during clipboard clear:', error);
// Clean up even on error
clipboardClearTimer = null;
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
currentCountdownId = 0;
countdownStartTime = 0;
totalCountdownTime = 0;
sendMessage('CLIPBOARD_CLEARED', {}, 'popup').catch(() => {});
}
}, timeout * 1000);
}
/**
* Cancel clipboard clear countdown and timer.
*/
export function handleCancelClipboardClear(): void {
if (clipboardClearTimer) {
clearTimeout(clipboardClearTimer);
clipboardClearTimer = null;
}
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
sendMessage('CLIPBOARD_COUNTDOWN_CANCELLED', {}, 'popup').catch(() => {});
}
/**
* Get the clipboard clear timeout setting.
*/
export async function handleGetClipboardClearTimeout(): Promise<number> {
const timeout = await storage.getItem(CLIPBOARD_CLEAR_TIMEOUT_KEY) as number ?? 10;
return timeout;
}
/**
* Set the clipboard clear timeout setting.
*/
export async function handleSetClipboardClearTimeout(data: number): Promise<boolean> {
await storage.setItem(CLIPBOARD_CLEAR_TIMEOUT_KEY, data);
return true;
}
/**
* Get the current clipboard countdown state.
*/
export function handleGetClipboardCountdownState(): { remaining: number; total: number; id: number } | null {
// Calculate actual remaining time based on elapsed time
if (currentCountdownId && countdownStartTime && totalCountdownTime) {
const elapsed = (Date.now() - countdownStartTime) / 1000;
const actualRemaining = Math.max(0, totalCountdownTime - elapsed);
if (actualRemaining > 0) {
return {
remaining: actualRemaining,
total: totalCountdownTime,
id: currentCountdownId
};
}
}
return null;
}

View File

@@ -3,12 +3,14 @@ import { sendMessage } from 'webext-bridge/background';
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",
@@ -20,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"],
});
@@ -36,7 +38,7 @@ 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"]
});
@@ -56,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 },
@@ -82,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

@@ -0,0 +1,314 @@
/**
* PasskeyHandler - Handles passkey popup management in background
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { handleGetEncryptionKey } from '@/entrypoints/background/VaultMessageHandler';
import {
PASSKEY_PROVIDER_ENABLED_KEY,
PASSKEY_DISABLED_SITES_KEY
} from '@/utils/Constants';
import { extractDomain, extractRootDomain } from '@/utils/credentialMatcher/CredentialMatcher';
import { EncryptionUtility } from '@/utils/EncryptionUtility';
import { PasskeyHelper } from '@/utils/passkey/PasskeyHelper';
import type {
PasskeyPopupResponse,
WebAuthnCreateRequest,
WebAuthnGetRequest,
PendingPasskeyRequest,
PendingPasskeyCreateRequest,
PendingPasskeyGetRequest,
WebAuthnSettingsResponse,
WebAuthnCreationPayload,
WebAuthnPublicKeyGetPayload
} from '@/utils/passkey/types';
import { SqliteClient } from '@/utils/SqliteClient';
import { browser, storage } from '#imports';
// Pending popup requests
const pendingRequests = new Map<string, {
resolve: (value: any) => void;
reject: (error: any) => void;
/**
* Store window ID in order to close the popup window from background script later.
*/
windowId?: number;
}>();
// Store request data temporarily (to avoid URL length limits)
const pendingRequestData = new Map<string, PendingPasskeyRequest>();
/**
* Handle WebAuthn settings request
*/
export async function handleGetWebAuthnSettings(data: any): Promise<WebAuthnSettingsResponse> {
// Check if passkey provider is enabled in settings (default to true if not set)
const globalEnabled = await storage.getItem(PASSKEY_PROVIDER_ENABLED_KEY);
if (globalEnabled === false) {
return { enabled: false };
}
// If hostname is provided, check if it's disabled for that site
const { hostname } = data || {};
if (hostname) {
// Extract base domain for matching
const baseDomain = extractRootDomain(extractDomain(hostname));
// Check disabled sites
const disabledSites = await storage.getItem(PASSKEY_DISABLED_SITES_KEY) as string[] ?? [];
if (disabledSites.includes(baseDomain)) {
return { enabled: false };
}
}
return { enabled: true };
}
/**
* Handle WebAuthn create (registration) request
*/
export async function handleWebAuthnCreate(data: any): Promise<any> {
const { publicKey, origin } = data as WebAuthnCreateRequest;
const requestId = Math.random().toString(36).substr(2, 9);
// Store request data temporarily (to avoid URL length limits)
const requestData: PendingPasskeyCreateRequest = {
type: 'create',
requestId,
origin,
publicKey: publicKey as WebAuthnCreationPayload
};
pendingRequestData.set(requestId, requestData);
// Create popup using main popup with hash navigation - only pass requestId
const popupUrl = browser.runtime.getURL('/popup.html') + '#/passkeys/create?' + new URLSearchParams({
requestId
}).toString();
try {
const popup = await browser.windows.create({
url: popupUrl,
type: 'popup',
width: 450,
height: 600,
focused: true
});
// Wait for response from popup
return new Promise((resolve, reject) => {
pendingRequests.set(requestId, { resolve, reject, windowId: popup.id });
// Clean up if popup is closed without response
const checkClosed = setInterval(async () => {
try {
if (popup.id) {
const _window = await browser.windows.get(popup.id);
// Window still exists, continue waiting
}
} catch {
// Window no longer exists
clearInterval(checkClosed);
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
pendingRequestData.delete(requestId);
resolve({ cancelled: true });
}
}
}, 1000);
});
} catch {
return { error: 'Failed to create popup window' };
}
}
/**
* Handle WebAuthn get (authentication) request
* Note: Passkey retrieval is now handled in the popup via SqliteClient
*/
export async function handleWebAuthnGet(data: any): Promise<any> {
const { publicKey, origin, isAutomaticRequest } = data as WebAuthnGetRequest;
const requestId = Math.random().toString(36).substr(2, 9);
/*
* If this is an automatic request (within 2 seconds of page load), check if we have matching credentials
* before opening the popup. This prevents AliasVault from blocking other password managers when we
* don't have the passkey they need.
*/
if (isAutomaticRequest) {
try {
// Check if we have any matching passkeys in storage
const hasMatchingPasskeys = await checkForMatchingPasskeys(publicKey, origin);
if (!hasMatchingPasskeys) {
// No matching passkeys - don't intercept, let other password managers handle it
return { fallback: true };
}
} catch (error) {
console.error('Error checking for matching passkeys:', error);
// On error, fall back to showing the popup (better UX than silently failing)
}
}
// Store request data temporarily (to avoid URL length limits)
const requestData: PendingPasskeyGetRequest = {
type: 'get',
requestId,
origin,
publicKey: publicKey as WebAuthnPublicKeyGetPayload,
passkeys: [] // Will be populated by the popup from vault
};
pendingRequestData.set(requestId, requestData);
// Create popup using main popup with hash navigation - only pass requestId
const popupUrl = browser.runtime.getURL('/popup.html') + '#/passkeys/authenticate?' + new URLSearchParams({
requestId
}).toString();
try {
const popup = await browser.windows.create({
url: popupUrl,
type: 'popup',
width: 450,
height: 600,
focused: true
});
// Wait for response from popup
return new Promise((resolve, reject) => {
pendingRequests.set(requestId, { resolve, reject, windowId: popup.id });
// Clean up if popup is closed without response
const checkClosed = setInterval(async () => {
try {
if (popup.id) {
const _window = await browser.windows.get(popup.id);
// Window still exists, continue waiting
}
} catch {
// Window no longer exists
clearInterval(checkClosed);
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
pendingRequestData.delete(requestId);
resolve({ cancelled: true });
}
}
}, 1000);
});
} catch {
return { error: 'Failed to create popup window' };
}
}
/**
* Check if we have any matching passkeys for the given request.
* This is used to determine if we should intercept automatic passkey requests.
*/
async function checkForMatchingPasskeys(publicKey: any, origin: string): Promise<boolean> {
try {
// Check if vault is unlocked
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
const encryptionKey = await handleGetEncryptionKey();
if (!encryptedVault || !encryptionKey) {
/*
* Vault is locked - we can't check for passkeys
* In this case, we return false to avoid intercepting
*/
return false;
}
// Decrypt and load the vault
const decryptedVault = await EncryptionUtility.symmetricDecrypt(
encryptedVault,
encryptionKey
);
const sqliteClient = new SqliteClient();
await sqliteClient.initializeFromBase64(decryptedVault);
// Get the rpId from the request or derive from origin
const rpId = publicKey.rpId || new URL(origin).hostname;
// Get passkeys for this rpId
const passkeys = sqliteClient.getPasskeysByRpId(rpId);
// If allowCredentials is specified, filter by those specific credentials
if (publicKey.allowCredentials && publicKey.allowCredentials.length > 0) {
// Convert the RP's base64url credential IDs to GUIDs for comparison
const allowedGuids = new Set(
publicKey.allowCredentials.map((c: any) => {
try {
return PasskeyHelper.base64urlToGuid(c.id);
} catch (e) {
console.warn('Failed to convert credential ID to GUID:', c.id, e);
return null;
}
}).filter((id: string | null): id is string => id !== null)
);
// Check if we have any of the allowed credentials
const matchingPasskeys = passkeys.filter(pk => allowedGuids.has(pk.Id));
return matchingPasskeys.length > 0;
}
// No allowCredentials specified - just check if we have any passkeys for this rpId
return passkeys.length > 0;
} catch (error) {
console.error('Error in checkForMatchingPasskeys:', error);
// On error, return false to avoid intercepting
return false;
}
}
/**
* Handle response from passkey popup
*/
export async function handlePasskeyPopupResponse(data: any): Promise<{ success: boolean }> {
const { requestId, credential, fallback, cancelled } = data as PasskeyPopupResponse;
const request = pendingRequests.get(requestId);
if (!request) {
return { success: false };
}
/**
* Close the popup window from background script to ensure it always works.
* Calling window.close() from the popup does not work in all browsers.
*/
if (request.windowId) {
try {
await browser.windows.remove(request.windowId);
} catch (error) {
// Window might already be closed, ignore error
console.debug('Failed to close popup window:', error);
}
}
// Clean up both maps
pendingRequests.delete(requestId);
pendingRequestData.delete(requestId);
if (cancelled) {
request.resolve({ cancelled: true });
} else if (fallback) {
request.resolve({ fallback: true });
} else if (credential) {
request.resolve({ credential });
} else {
request.resolve({ cancelled: true });
}
return { success: true };
}
/**
* Get request data by request ID
*/
export async function handleGetRequestData(data: any): Promise<PendingPasskeyRequest | null> {
const { requestId } = data as { requestId: string };
const requestData = pendingRequestData.get(requestId);
return requestData || null;
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { setupContextMenus } from '@/entrypoints/background/ContextMenu';
import { SKIP_FORM_RESTORE_KEY } from '@/utils/Constants';
import { BoolResponse } from '@/utils/types/messaging/BoolResponse';
import { browser } from '#imports';
@@ -37,6 +38,53 @@ export function handlePopupWithCredential(message: any) : Promise<BoolResponse>
})();
}
/**
* Handle opening the popup on create credential page with prefilled service name.
*/
export function handleOpenPopupCreateCredential(message: any) : Promise<BoolResponse> {
return (async () : Promise<BoolResponse> => {
const serviceName = encodeURIComponent(message.serviceName || '');
// Use the URL passed from the content script (current page URL)
let serviceUrl = '';
if (message.currentUrl) {
try {
const url = new URL(message.currentUrl);
// Only include http/https URLs
if (url.protocol === 'http:' || url.protocol === 'https:') {
serviceUrl = encodeURIComponent(url.origin + url.pathname);
}
} catch (error) {
console.error('Error parsing current URL:', error);
}
}
// Set a localStorage flag to skip restoring previously persisted form values as we want to start fresh with this explicit create credential request.
await browser.storage.local.set({ [SKIP_FORM_RESTORE_KEY]: true });
const urlParams = new URLSearchParams();
urlParams.set('expanded', 'true');
if (serviceName) {
urlParams.set('serviceName', serviceName);
}
if (serviceUrl) {
urlParams.set('serviceUrl', serviceUrl);
}
if (message.currentUrl) {
urlParams.set('currentUrl', message.currentUrl);
}
browser.windows.create({
url: browser.runtime.getURL(`/popup.html?${urlParams.toString()}#/credentials/add`),
type: 'popup',
width: 400,
height: 600,
focused: true
});
return { success: true };
})();
}
/**
* Handle toggling the context menu.
*/
@@ -45,7 +93,7 @@ export function handleToggleContextMenu(message: any) : Promise<BoolResponse> {
if (!message.enabled) {
browser.contextMenus.removeAll();
} else {
setupContextMenus();
await setupContextMenus();
}
return { success: true };
})();

View File

@@ -1,11 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { storage } from 'wxt/utils/storage';
import type { EncryptionKeyDerivationParams } from '@/utils/dist/shared/models/metadata';
import type { Vault, VaultResponse, VaultPostResponse } from '@/utils/dist/shared/models/webapi';
import { EncryptionUtility } from '@/utils/EncryptionUtility';
import { SqliteClient } from '@/utils/SqliteClient';
import { VaultVersionIncompatibleError } from '@/utils/types/errors/VaultVersionIncompatibleError';
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';
@@ -13,21 +16,81 @@ import { VaultResponse as messageVaultResponse } from '@/utils/types/messaging/V
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.
* Cache for the SqliteClient to avoid repeated decryption and initialization.
* The cached instance is the single source of truth for the in-memory vault.
*
* Cache Strategy:
* - Local mutations (createCredential, etc.): Work directly on cachedSqliteClient, no cache clearing
* - New vault from remote (login, sync): Clear cache by setting both to null
* - Logout/clear vault: Clear cache by setting both to null
*
* The cache is cleared by setting cachedSqliteClient and cachedVaultBlob to null directly
* in the functions that receive new vault data from external sources.
*/
export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, isVaultLocked: boolean }> {
let cachedSqliteClient: SqliteClient | null = null;
let cachedVaultBlob: string | null = null;
/**
* 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, 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');
const encryptionKey = await handleGetEncryptionKey();
const isLoggedIn = username !== null && accessToken !== null;
const isVaultLocked = isLoggedIn && vaultData === null;
const isVaultLocked = isLoggedIn && (vaultData === null || encryptionKey === 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) {
// If it's a version incompatibility error, we need to handle it specially
if (error instanceof VaultVersionIncompatibleError) {
// Return the error so the UI can handle it appropriately (logout user)
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false,
error: error.message
};
}
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false,
error: error instanceof Error ? error.message : await t('common.errors.unknownError')
};
}
}
/**
@@ -42,16 +105,15 @@ export async function handleStoreVault(
// Store new encrypted vault in session storage.
await storage.setItem('session:encryptedVault', vaultRequest.vaultBlob);
// Clear cached client since we received a new vault blob from external source
cachedSqliteClient = null;
cachedVaultBlob = null;
/*
* 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);
}
@@ -60,6 +122,10 @@ export async function handleStoreVault(
await storage.setItem('session:privateEmailDomains', vaultRequest.privateEmailDomainList);
}
if (vaultRequest.hiddenPrivateEmailDomainList) {
await storage.setItem('session:hiddenPrivateEmailDomains', vaultRequest.hiddenPrivateEmailDomainList);
}
if (vaultRequest.vaultRevisionNumber) {
await storage.setItem('session:vaultRevisionNumber', vaultRequest.vaultRevisionNumber);
}
@@ -67,7 +133,37 @@ export async function handleStoreVault(
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.unknownError') };
}
}
/**
* Store the encryption key (derived key) in browser storage.
*/
export async function handleStoreEncryptionKey(
encryptionKey: string,
) : Promise<messageBoolResponse> {
try {
await storage.setItem('session:encryptionKey', encryptionKey);
return { success: true };
} catch (error) {
console.error('Failed to store encryption key:', error);
return { success: false, error: await t('common.errors.unknownErrorTryAgain') };
}
}
/**
* Store the encryption key derivation parameters in browser storage.
*/
export async function handleStoreEncryptionKeyDerivationParams(
params: EncryptionKeyDerivationParams,
) : Promise<messageBoolResponse> {
try {
await storage.setItem('session:encryptionKeyDerivationParams', params);
return { success: true };
} catch (error) {
console.error('Failed to store encryption key derivation params:', error);
return { success: false, error: await t('common.errors.unknownErrorTryAgain') };
}
}
@@ -76,11 +172,11 @@ export async function handleStoreVault(
*/
export async function handleSyncVault(
) : Promise<messageBoolResponse> {
const webApi = new WebApiService(() => {});
const webApi = new WebApiService();
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;
@@ -93,8 +189,13 @@ export async function handleSyncVault(
{ key: 'session:encryptedVault', value: vaultResponse.vault.blob },
{ key: 'session:publicEmailDomains', value: vaultResponse.vault.publicEmailDomainList },
{ key: 'session:privateEmailDomains', value: vaultResponse.vault.privateEmailDomainList },
{ key: 'session:hiddenPrivateEmailDomains', value: vaultResponse.vault.hiddenPrivateEmailDomainList },
{ key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber }
]);
// Clear cached client since we received a new vault blob from server
cachedSqliteClient = null;
cachedVaultBlob = null;
}
return { success: true };
@@ -106,20 +207,27 @@ export async function handleSyncVault(
export async function handleGetVault(
) : Promise<messageVaultResponse> {
try {
const encryptionKey = await handleGetEncryptionKey();
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
const derivedKey = await storage.getItem('session:derivedKey') as string;
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[];
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[];
const hiddenPrivateEmailDomains = await storage.getItem('session:hiddenPrivateEmailDomains') as string[] ?? [];
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
if (!encryptedVault) {
console.error('Vault not available');
return { success: false, error: 'Vault not available' };
return { success: false, error: await t('common.errors.vaultNotAvailable') };
}
if (!encryptionKey) {
console.error('Encryption key not available');
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
const decryptedVault = await EncryptionUtility.symmetricDecrypt(
encryptedVault,
derivedKey
encryptionKey
);
return {
@@ -127,11 +235,12 @@ export async function handleGetVault(
vault: decryptedVault,
publicEmailDomains: publicEmailDomains ?? [],
privateEmailDomains: privateEmailDomains ?? [],
hiddenPrivateEmailDomains: hiddenPrivateEmailDomains ?? [],
vaultRevisionNumber: vaultRevisionNumber ?? 0
};
} 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.unknownError') };
}
}
@@ -142,12 +251,20 @@ export function handleClearVault(
) : messageBoolResponse {
storage.removeItems([
'session:encryptedVault',
'session:encryptionKey',
// TODO: the derivedKey clear can be removed some period of time after 0.22.0 is released.
'session:derivedKey',
'session:encryptionKeyDerivationParams',
'session:publicEmailDomains',
'session:privateEmailDomains',
'session:hiddenPrivateEmailDomains',
'session:vaultRevisionNumber'
]);
// Clear cached client since vault was cleared
cachedSqliteClient = null;
cachedVaultBlob = null;
return { success: true };
}
@@ -156,10 +273,10 @@ export function handleClearVault(
*/
export async function handleGetCredentials(
) : Promise<messageCredentialsResponse> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
const encryptionKey = await handleGetEncryptionKey();
if (!derivedKey) {
return { success: false, error: 'Vault is locked' };
if (!encryptionKey) {
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
@@ -168,7 +285,101 @@ 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.unknownError') };
}
}
/**
* Get credentials filtered by URL and page title for autofill performance optimization.
* Filters credentials in the background script before sending to reduce message payload size.
* Critical for large vaults (1000+ credentials) to avoid multi-second delays.
*
* @param message - Filtering parameters: currentUrl, pageTitle, matchingMode
*/
export async function handleGetFilteredCredentials(
message: { currentUrl: string, pageTitle: string, matchingMode?: string }
) : Promise<messageCredentialsResponse> {
const encryptionKey = await handleGetEncryptionKey();
if (!encryptionKey) {
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
const sqliteClient = await createVaultSqliteClient();
const allCredentials = sqliteClient.getAllCredentials();
const { filterCredentials, AutofillMatchingMode } = await import('@/utils/credentialMatcher/CredentialMatcher');
// Parse matching mode from string
let matchingMode = AutofillMatchingMode.DEFAULT;
if (message.matchingMode) {
matchingMode = message.matchingMode as typeof AutofillMatchingMode[keyof typeof AutofillMatchingMode];
}
// Filter credentials in background to reduce payload size (~95% reduction)
const filteredCredentials = filterCredentials(
allCredentials,
message.currentUrl,
message.pageTitle,
matchingMode
);
return { success: true, credentials: filteredCredentials };
} catch (error) {
console.error('Error getting filtered credentials:', error);
return { success: false, error: await t('common.errors.unknownError') };
}
}
/**
* Get credentials filtered by text search query.
* Searches across entire vault (service name, username, email, URL) and returns matches.
*
* @param message - Search parameters: searchTerm
*/
export async function handleGetSearchCredentials(
message: { searchTerm: string }
) : Promise<messageCredentialsResponse> {
const encryptionKey = await handleGetEncryptionKey();
if (!encryptionKey) {
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
const sqliteClient = await createVaultSqliteClient();
const allCredentials = sqliteClient.getAllCredentials();
// If search term is empty, return empty array
if (!message.searchTerm || message.searchTerm.trim() === '') {
return { success: true, credentials: [] };
}
const searchTerm = message.searchTerm.toLowerCase().trim();
// Filter credentials by search term across multiple fields
const searchResults = allCredentials.filter(cred => {
const searchableFields = [
cred.ServiceName?.toLowerCase(),
cred.Username?.toLowerCase(),
cred.Alias?.Email?.toLowerCase(),
cred.ServiceUrl?.toLowerCase()
];
return searchableFields.some(field => field?.includes(searchTerm));
}).sort((a, b) => {
// Sort by service name, then username
const serviceNameComparison = (a.ServiceName ?? '').localeCompare(b.ServiceName ?? '');
if (serviceNameComparison !== 0) {
return serviceNameComparison;
}
return (a.Username ?? '').localeCompare(b.Username ?? '');
});
return { success: true, credentials: searchResults };
} catch (error) {
console.error('Error searching credentials:', error);
return { success: false, error: await t('common.errors.unknownError') };
}
}
@@ -178,17 +389,17 @@ export async function handleGetCredentials(
export async function handleCreateIdentity(
message: any,
) : Promise<messageBoolResponse> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
const encryptionKey = await handleGetEncryptionKey();
if (!derivedKey) {
return { success: false, error: 'Vault is locked' };
if (!encryptionKey) {
return { success: false, error: await t('common.errors.vaultIsLocked') };
}
try {
const sqliteClient = await createVaultSqliteClient();
// Add the new credential to the vault/database.
sqliteClient.createCredential(message.credential);
await sqliteClient.createCredential(message.credential, message.attachments || []);
// Upload the new vault to the server.
await uploadNewVaultToServer(sqliteClient);
@@ -196,7 +407,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.unknownError') };
}
}
@@ -229,33 +440,38 @@ export async function getEmailAddressesForVault(
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(privateEmailDomains, publicEmailDomains);
const defaultEmailDomain = await sqliteClient.getDefaultEmailDomain();
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.unknownError') };
}
})();
}
/**
* Get the default identity language.
* Get the default identity settings.
* Returns the effective language (with smart UI language matching if no explicit override is set).
*/
export async function handleGetDefaultIdentityLanguage(
) : Promise<stringResponse> {
export async function handleGetDefaultIdentitySettings(
) : Promise<IdentitySettingsResponse> {
try {
const sqliteClient = await createVaultSqliteClient();
const settingValue = sqliteClient.getDefaultIdentityLanguage();
const language = await sqliteClient.getEffectiveIdentityLanguage();
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.unknownError') };
}
}
@@ -271,17 +487,34 @@ 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.unknownError') };
}
}
/**
* Get the derived key for the encrypted vault.
* Get the encryption key for the encrypted vault.
*/
export async function handleGetDerivedKey(
) : Promise<string> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
return derivedKey;
export async function handleGetEncryptionKey(
) : Promise<string | null> {
// Try the current key name first (since 0.22.0)
let encryptionKey = await storage.getItem('session:encryptionKey') as string | null;
// Fall back to the legacy key name if not found
if (!encryptionKey) {
// TODO: this check can be removed some period of time after 0.22.0 is released.
encryptionKey = await storage.getItem('session:derivedKey') as string | null;
}
return encryptionKey;
}
/**
* Get the encryption key derivation parameters for password change detection and offline mode.
*/
export async function handleGetEncryptionKeyDerivationParams(
) : Promise<EncryptionKeyDerivationParams | null> {
const params = await storage.getItem('session:encryptionKeyDerivationParams') as EncryptionKeyDerivationParams | null;
return params;
}
/**
@@ -291,18 +524,16 @@ export async function handleUploadVault(
message: any
) : Promise<messageVaultUploadResponse> {
try {
// Store the new vault blob in session storage.
// Persist the current updated 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 sqliteClient = await createVaultSqliteClient();
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: 'Failed to upload vault' };
return { success: false, error: await t('common.errors.unknownError') };
}
}
@@ -311,16 +542,16 @@ export async function handleUploadVault(
* 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('No derived key available for encryption');
const encryptionKey = await handleGetEncryptionKey();
if (!encryptionKey) {
throw new Error(await t('common.errors.unknownError'));
}
// Always stringify the data properly
const serializedData = JSON.stringify(data);
const encryptedData = await EncryptionUtility.symmetricEncrypt(
serializedData,
derivedKey
encryptionKey
);
await storage.setItem('session:persistedFormValues', encryptedData);
}
@@ -330,17 +561,17 @@ export async function handlePersistFormValues(data: any): Promise<void> {
* Data is decrypted using the derived key.
*/
export async function handleGetPersistedFormValues(): Promise<any | null> {
const derivedKey = await storage.getItem('session:derivedKey') as string;
const encryptionKey = await handleGetEncryptionKey();
const encryptedData = await storage.getItem('session:persistedFormValues') as string | null;
if (!encryptedData || !derivedKey) {
if (!encryptedData || !encryptionKey) {
return null;
}
try {
const decryptedData = await EncryptionUtility.symmetricDecrypt(
encryptedData,
derivedKey
encryptionKey
);
return JSON.parse(decryptedData);
} catch (error) {
@@ -361,17 +592,28 @@ export async function handleClearPersistedFormValues(): Promise<void> {
*/
async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<VaultPostResponse> {
const updatedVaultData = sqliteClient.exportToBase64();
const derivedKey = await storage.getItem('session:derivedKey') as string;
const encryptionKey = await handleGetEncryptionKey();
if (!encryptionKey) {
throw new Error(await t('common.errors.vaultIsLocked'));
}
const encryptedVault = await EncryptionUtility.symmetricEncrypt(
updatedVaultData,
derivedKey
encryptionKey
);
// Update storage with the newly encrypted vault (serialized from current in-memory state)
await storage.setItems([
{ key: 'session:encryptedVault', value: encryptedVault }
]);
/*
* Update cached vault blob to match the new encrypted version
* This prevents unnecessary cache invalidation since the in-memory sqliteClient is already up to date
*/
cachedVaultBlob = encryptedVault;
// Get metadata from storage
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
@@ -385,23 +627,21 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
credentialsCount: sqliteClient.getAllCredentials().length,
currentRevisionNumber: vaultRevisionNumber,
emailAddressList: emailAddresses,
privateEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
publicEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
encryptionPublicKey: '', // Empty on purpose, only required if new public/private key pair is generated.
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: (await sqliteClient.getDatabaseVersion()).version,
// TODO: add public RSA encryption key to payload when implementing vault creation from browser extension. Currently only web app does this.
encryptionPublicKey: '',
};
const webApi = new WebApiService(() => {});
const webApi = new WebApiService();
const response = await webApi.post<Vault, VaultPostResponse>('Vault', newVault);
// Check if response is successful (.status === 0)
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.unknownError'));
}
return response;
@@ -409,23 +649,33 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
/**
* Create a new sqlite client for the stored vault.
* Uses a cache to avoid repeated decryption and initialization for read operations.
*/
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');
const encryptionKey = await handleGetEncryptionKey();
if (!encryptedVault || !encryptionKey) {
throw new Error(await t('common.errors.unknownError'));
}
// Decrypt the vault.
// Check if we have a valid cached client
if (cachedSqliteClient && cachedVaultBlob === encryptedVault) {
return cachedSqliteClient;
}
// Decrypt the vault
const decryptedVault = await EncryptionUtility.symmetricDecrypt(
encryptedVault,
derivedKey
encryptionKey
);
// Initialize the SQLite client with the decrypted vault.
// Initialize the SQLite client with the decrypted vault
const sqliteClient = new SqliteClient();
await sqliteClient.initializeFromBase64(decryptedVault);
// Cache the client and vault blob
cachedSqliteClient = sqliteClient;
cachedVaultBlob = encryptedVault;
return sqliteClient;
}

View File

@@ -1,14 +1,20 @@
/**
* Content script entry point - handles autofill UI and WebAuthn passkey interception
*/
import '@/entrypoints/contentScript/style.css';
import { onMessage } from "webext-bridge/content-script";
import { injectIcon, popupDebounceTimeHasPassed, validateInputField } from '@/entrypoints/contentScript/Form';
import { isAutoShowPopupEnabled, openAutofillPopup, removeExistingPopup } from '@/entrypoints/contentScript/Popup';
import { isAutoShowPopupEnabled, openAutofillPopup, removeExistingPopup, createUpgradeRequiredPopup } from '@/entrypoints/contentScript/Popup';
import { initializeWebAuthnInterceptor } from '@/entrypoints/contentScript/WebAuthnInterceptor';
import { FormDetector } from '@/utils/formDetector/FormDetector';
import { BoolResponse as messageBoolResponse } from '@/utils/types/messaging/BoolResponse';
import { defineContentScript } from '#imports';
import { createShadowRootUi } from '#imports';
import { t } from '@/i18n/StandaloneI18n';
import { defineContentScript, createShadowRootUi } from '#imports';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -25,6 +31,9 @@ export default defineContentScript({
return;
}
// Initialize WebAuthn interceptor for passkey support
await initializeWebAuthnInterceptor(ctx);
// 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));
@@ -33,6 +42,7 @@ export default defineContentScript({
name: 'aliasvault-ui',
position: 'inline',
anchor: 'body',
mode: 'closed',
/**
* Handle mount.
*/
@@ -69,7 +79,7 @@ export default defineContentScript({
// Only show popup if debounce time has passed
if (popupDebounceTimeHasPassed()) {
openAutofillPopup(inputElement, container);
await showPopupWithAuthCheck(inputElement, container);
}
}
}
@@ -132,6 +142,48 @@ export default defineContentScript({
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);
}
}

View File

@@ -1,108 +0,0 @@
import type { Credential } from '@/utils/dist/shared/models/vault';
import { CombinedStopWords } from '@/utils/formDetector/FieldPatterns';
type CredentialWithPriority = Credential & {
priority: number;
}
/**
* Filter credentials based on current URL and page context to determine which credentials to show
* in the autofill popup. Credentials are sorted by priority:
* 1. Exact URL match (highest priority)
* 2. Base URL match AND page title word match
* 3. Base URL match only
* 4. Page title word match only (lowest priority)
*/
export function filterCredentials(credentials: Credential[], currentUrl: string, pageTitle: string): Credential[] {
const urlObject = new URL(currentUrl);
const baseUrl = `${urlObject.protocol}//${urlObject.hostname}`;
const filtered: CredentialWithPriority[] = [];
const sanitizedCurrentUrl = currentUrl.toLowerCase().replace('www.', '');
// 1. Exact URL match (priority 1)
credentials.forEach(cred => {
if (!cred.ServiceUrl || cred.ServiceUrl.length === 0) {
return;
}
const sanitizedCredUrl = cred.ServiceUrl.toLowerCase().replace('www.', '');
if (sanitizedCurrentUrl.startsWith(sanitizedCredUrl)) {
filtered.push({ ...cred, priority: 1 });
}
});
// If we have one or more exact matches, do not continue to other matches
if (filtered.length > 0) {
return filtered;
}
// Prepare page title words for matching
const titleWords = pageTitle.length > 0
? pageTitle.toLowerCase()
.split(/\s+/)
.filter(word =>
word.length > 3 &&
!CombinedStopWords.has(word.toLowerCase())
)
: [];
// Check for base URL matches and page title matches
credentials.forEach(cred => {
if (!cred.ServiceUrl || filtered.some(f => f.Id === cred.Id)) {
return;
}
let hasBaseUrlMatch = false;
let hasTitleMatch = false;
// Check base URL match
try {
const credUrlObject = new URL(cred.ServiceUrl);
const currentUrlObject = new URL(baseUrl);
const credDomainParts = credUrlObject.hostname.toLowerCase().split('.');
const currentDomainParts = currentUrlObject.hostname.toLowerCase().split('.');
const credRootDomain = credDomainParts.slice(-2).join('.');
const currentRootDomain = currentDomainParts.slice(-2).join('.');
if (credUrlObject.protocol === currentUrlObject.protocol &&
credRootDomain === currentRootDomain) {
hasBaseUrlMatch = true;
}
} catch {
// Invalid URL, skip
}
// Check page title match
if (titleWords.length > 0) {
const credNameWords = cred.ServiceName.toLowerCase()
.split(/\s+/)
.filter(word => word.length > 3 && !CombinedStopWords.has(word));
hasTitleMatch = titleWords.some(word =>
credNameWords.some(credWord => credWord.includes(word))
);
}
// Assign priority based on matches
if (hasBaseUrlMatch && hasTitleMatch) {
filtered.push({ ...cred, priority: 2 });
} else if (hasBaseUrlMatch) {
filtered.push({ ...cred, priority: 3 });
} else if (hasTitleMatch) {
filtered.push({ ...cred, priority: 4 });
}
});
// Sort by priority and then take unique credentials
const uniqueCredentials = Array.from(
new Map(filtered
.sort((a, b) => a.priority - b.priority)
.map(cred => [cred.Id, cred]))
.values()
);
// Show max 3 results
return uniqueCredentials.slice(0, 3);
}

View File

@@ -1,8 +1,11 @@
import { sendMessage } from 'webext-bridge/content-script';
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';
import { ClickValidator } from '@/utils/security/ClickValidator';
/**
* Global timestamp to track popup debounce time.
@@ -12,6 +15,11 @@ import { FormFiller } from '@/utils/formDetector/FormFiller';
*/
let popupDebounceTime = 0;
/**
* ClickValidator instance for form security validation
*/
const clickValidator = ClickValidator.getInstance();
/**
* Check if popup can be shown based on debounce time.
*/
@@ -32,6 +40,8 @@ export function hidePopupFor(ms: number) : void {
/**
* Validates if an element is a supported input field that can be processed for autofill.
* This function supports regular input elements, custom elements with type attributes,
* and custom web components that may contain shadow DOM.
* @param element The element to validate
* @returns An object containing validation result and the element cast as HTMLInputElement if valid
*/
@@ -42,14 +52,30 @@ export function validateInputField(element: Element | null): { isValid: boolean;
const textInputTypes = ['text', 'email', 'tel', 'password', 'search', 'url', 'number'];
const elementType = element.getAttribute('type');
const isInputElement = element.tagName.toLowerCase() === 'input';
const tagName = element.tagName.toLowerCase();
const isInputElement = tagName === 'input';
// Check if element has shadow DOM with input elements
const elementWithShadow = element as HTMLElement & { shadowRoot?: ShadowRoot };
const hasShadowDOMInput = elementWithShadow.shadowRoot &&
elementWithShadow.shadowRoot.querySelector('input, textarea');
// Check if it's a custom element that might be an input
const isLikelyCustomInputElement = tagName.includes('-') && (
tagName.includes('input') ||
tagName.includes('field') ||
tagName.includes('text') ||
hasShadowDOMInput
);
// Check if it's a valid input field we should process
const isValid = (
// Case 1: It's an input element (with either explicit type or defaulting to "text")
(isInputElement && (!elementType || textInputTypes.includes(elementType?.toLowerCase() ?? ''))) ||
// Case 2: Non-input element but has valid type attribute
(!isInputElement && elementType && textInputTypes.includes(elementType.toLowerCase()))
(!isInputElement && elementType && textInputTypes.includes(elementType.toLowerCase())) ||
// Case 3: It's a custom element that likely contains an input
(isLikelyCustomInputElement)
) as boolean;
return {
@@ -64,10 +90,15 @@ export function validateInputField(element: Element | null): { isValid: boolean;
* @param credential - The credential to fill.
* @param input - The input element that triggered the popup. Required when filling credentials to know which form to fill.
*/
export function fillCredential(credential: Credential, input: HTMLInputElement) : void {
export async function fillCredential(credential: Credential, input: HTMLInputElement): Promise<void> {
// Set debounce time to 300ms to prevent the popup from being shown again within 300ms because of autofill events.
hidePopupFor(300);
// Reset auto-lock timer when autofilling
sendMessage('RESET_AUTO_LOCK_TIMER', {}, 'background').catch(() => {
// Ignore errors as background script might not be ready
});
const formDetector = new FormDetector(document, input);
const form = formDetector.getForm();
@@ -77,7 +108,7 @@ export function fillCredential(credential: Credential, input: HTMLInputElement)
}
const formFiller = new FormFiller(form, triggerInputEvents);
formFiller.fillFields(credential);
await formFiller.fillFields(credential);
}
/**
@@ -98,7 +129,7 @@ function findActualInput(element: HTMLElement): HTMLInputElement {
return element as HTMLInputElement;
}
// Try to find a visible child input
// Try to find a visible child input in regular DOM
const childInput = element.querySelector('input');
if (childInput) {
const style = window.getComputedStyle(childInput);
@@ -107,6 +138,17 @@ function findActualInput(element: HTMLElement): HTMLInputElement {
}
}
// Try to find input in shadow DOM if element has shadowRoot
if (element.shadowRoot) {
const shadowInput = element.shadowRoot.querySelector('input');
if (shadowInput) {
const style = window.getComputedStyle(shadowInput);
if (style.display !== 'none' && style.visibility !== 'hidden') {
return shadowInput as HTMLInputElement;
}
}
}
// Fallback to the provided element if no child input found
return element as HTMLInputElement;
}
@@ -179,9 +221,16 @@ export function injectIcon(input: HTMLInputElement, container: HTMLElement): voi
window.addEventListener('resize', updateIconPosition);
// Add click event to trigger the autofill popup and refocus the input
icon.addEventListener('click', (e: MouseEvent) => {
icon.addEventListener('click', async (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
// Validate the click for security
if (!await clickValidator.validateClick(e)) {
console.warn('[AliasVault Security] Blocked autofill popup opening due to security validation failure');
return;
}
setTimeout(() => actualInput.focus(), 0);
openAutofillPopup(actualInput, container);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,271 @@
/**
* WebAuthn Interceptor - Handles communication between page and extension
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { sendMessage } from 'webext-bridge/content-script';
import type { WebAuthnSettingsResponse } from '@/utils/passkey/types';
import { browser } from '#imports';
// Firefox-specific global function for cloning objects into page context
declare function cloneInto<T>(obj: T, targetScope: any): T;
let interceptorInitialized = false;
/**
* Track last cancelled request to prevent rapid-fire popups.
* This is used to track the last time a WebAuthn request was cancelled.
* Some websites try to automatically re-trigger a WebAuthn request after a cancellation.
* which results in a jarring UX for the user.
* This cooldown prevents rapid-fire popups by waiting for a short period after a cancellation.
*/
let lastCancelledTimestamp = 0;
const CANCEL_COOLDOWN_MS = 500; // 500ms cooldown after a recent cancellation
/**
* Track when the page finished loading to detect automatic vs user-initiated requests.
* Some websites (like Nintendo, Amazon) automatically trigger passkey requests on page load.
* We should filter these if no matching credentials exist.
*/
let pageLoadTime = 0;
const AUTO_REQUEST_THRESHOLD_MS = 1000; // Requests within 1 second of page load are considered "automatic"
/**
* Check if page is ready for WebAuthn interactions.
* Safari and other browsers can trigger WebAuthn requests during URL autocomplete
* or page prefetch, which creates popups before the user actually navigates to the page.
* We check if the document is visible and interactive to prevent these spurious requests.
*/
function isPageReadyForWebAuthn(): boolean {
// If page is hidden (prefetch/background tab), block the request
if (document.hidden || document.visibilityState === 'hidden') {
return false;
}
// If document is still loading (not even interactive), block the request
if (document.readyState === 'loading') {
return false;
}
// Page is visible and at least interactive - allow the request
return true;
}
/**
* Initialize the WebAuthn interceptor
*/
export async function initializeWebAuthnInterceptor(_ctx: any): Promise<void> {
if (interceptorInitialized) {
return;
}
// Track page load time for detecting automatic requests
pageLoadTime = Date.now();
// Listen for WebAuthn create events from the page
window.addEventListener('aliasvault:webauthn:create', async (event: any) => {
const { requestId, publicKey, origin } = event.detail;
/**
* Helper to dispatch event with Firefox compatibility
* Firefox has strict cross-context security, so we serialize to JSON and back
*/
const dispatchResponse = (detail: any): void => {
let eventDetail: any;
/*
* For Firefox, we need to ensure the detail is accessible in the page context
* cloneInto is a global function in Firefox content scripts
*/
if (typeof cloneInto !== 'undefined') {
// Firefox: serialize and clone into page context
const serialized = JSON.parse(JSON.stringify(detail));
eventDetail = cloneInto(serialized, (window as any).wrappedJSObject || window);
} else {
// Chrome/Edge: direct assignment works
eventDetail = detail;
}
window.dispatchEvent(new CustomEvent('aliasvault:webauthn:create:response', {
detail: eventDetail
}));
};
try {
/**
* Note: We don't block create (registration) requests based on page readiness.
* Registration is always user-initiated (button click), so it's never spurious.
*/
// Check if we're in cooldown period after a recent cancellation
const now = Date.now();
if (lastCancelledTimestamp > 0 && (now - lastCancelledTimestamp) < CANCEL_COOLDOWN_MS) {
// Silently fall back to native implementation during cooldown
dispatchResponse({
requestId,
fallback: true
});
return;
}
// Check if passkey provider is enabled
const enabled = await isWebAuthnInterceptionEnabled();
if (!enabled) {
// If disabled, signal fallback to native browser implementation
dispatchResponse({
requestId,
fallback: true
});
return;
}
// Send to background script to handle
const result = await sendMessage('WEBAUTHN_CREATE', {
publicKey,
origin
}, 'background');
// Track if user cancelled to enable cooldown
if (result && typeof result === 'object' && (result as any).cancelled) {
lastCancelledTimestamp = Date.now();
}
// Send response back to page
dispatchResponse({
requestId,
...(typeof result === 'object' && result !== null ? result : {})
});
} catch (error: any) {
dispatchResponse({
requestId,
error: error.message
});
}
});
// Listen for WebAuthn get events from the page
window.addEventListener('aliasvault:webauthn:get', async (event: any) => {
const { requestId, publicKey, origin } = event.detail;
/**
* Helper to dispatch event with Firefox compatibility
* Firefox has strict cross-context security, so we serialize to JSON and back
*/
const dispatchResponse = (detail: any): void => {
let eventDetail: any;
/*
* For Firefox, we need to ensure the detail is accessible in the page context
* cloneInto is a global function in Firefox content scripts
*/
if (typeof cloneInto !== 'undefined') {
// Firefox: serialize and clone into page context
const serialized = JSON.parse(JSON.stringify(detail));
eventDetail = cloneInto(serialized, (window as any).wrappedJSObject || window);
} else {
// Chrome/Edge: direct assignment works
eventDetail = detail;
}
window.dispatchEvent(new CustomEvent('aliasvault:webauthn:get:response', {
detail: eventDetail
}));
};
try {
// Block requests if page isn't ready (prevents prefetch/autocomplete popups)
if (!isPageReadyForWebAuthn()) {
dispatchResponse({
requestId,
fallback: true
});
return;
}
// Check if we're in cooldown period after a recent cancellation
const now = Date.now();
if (lastCancelledTimestamp > 0 && (now - lastCancelledTimestamp) < CANCEL_COOLDOWN_MS) {
// Silently fall back to native implementation during cooldown
dispatchResponse({
requestId,
fallback: true
});
return;
}
// Check if passkey provider is enabled
const enabled = await isWebAuthnInterceptionEnabled();
if (!enabled) {
// If disabled, signal fallback to native browser implementation
dispatchResponse({
requestId,
fallback: true
});
return;
}
// Detect if this is an automatic request (within 2 seconds of page load)
const isAutomaticRequest = (Date.now() - pageLoadTime) < AUTO_REQUEST_THRESHOLD_MS;
// Send to background script to handle
const result = await sendMessage('WEBAUTHN_GET', {
publicKey,
origin,
isAutomaticRequest
}, 'background');
// Track if user cancelled to enable cooldown
if (result && typeof result === 'object' && (result as any).cancelled) {
lastCancelledTimestamp = Date.now();
}
// Send response back to page
dispatchResponse({
requestId,
...(typeof result === 'object' && result !== null ? result : {})
});
} catch (error: any) {
dispatchResponse({
requestId,
error: error.message
});
}
});
// Inject the page script
const script = document.createElement('script');
script.src = browser.runtime.getURL('/webauthn.js');
script.async = true;
(document.head || document.documentElement).appendChild(script);
/**
* onload
*/
script.onload = () : void => {
script.remove();
};
/**
* onerror
*/
script.onerror = () : void => {
// Ignore
};
interceptorInitialized = true;
}
/**
* Check if WebAuthn interception is enabled for the current site
*/
export async function isWebAuthnInterceptionEnabled(): Promise<boolean> {
try {
const response = await sendMessage('GET_WEBAUTHN_SETTINGS', {
hostname: window.location.hostname
}, 'background') as unknown as WebAuthnSettingsResponse;
return response.enabled ?? false;
} catch {
return false;
}
}

View File

@@ -222,7 +222,7 @@ body {
/* Search Input */
.av-search-input {
flex: 2;
flex: 1;
border-radius: 4px;
background: #374151;
color: #e5e7eb;
@@ -231,12 +231,13 @@ body {
border: 1px solid #4b5563;
outline: none;
line-height: 1;
text-align: center;
min-width: 0px;
padding: 8px 12px;
padding-right: 0;
}
.av-search-input::placeholder {
color: #bdbebe;
opacity: 1;
}
.av-search-input:focus {
@@ -299,6 +300,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;
@@ -473,6 +539,62 @@ body {
box-shadow: 0 0 0 1px #ef4444 !important;
}
/* Field Suggestions - Pill Style */
.av-field-suggestions {
margin-top: 8px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.av-suggestion-pill {
display: inline-flex;
align-items: center;
background: #4b5563;
border: 1px solid #6b7280;
border-radius: 16px;
padding: 4px 8px 4px 12px;
font-size: 13px;
color: #e5e7eb;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
}
.av-suggestion-pill:hover {
background: #6b7280;
border-color: #9ca3af;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.av-suggestion-pill-text {
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.av-suggestion-pill-delete {
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 6px;
padding: 0 2px;
color: #9ca3af;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: color 0.2s;
border-left: 1px solid #6b7280;
padding-left: 6px;
}
.av-suggestion-pill-delete:hover {
color: #ef4444;
}
.av-create-popup-error-text {
color: #ef4444;
font-size: 0.875rem;
@@ -662,28 +784,41 @@ body {
.av-create-popup-title-container {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
justify-content: center;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: background-color 0.2s;
position: relative;
}
.av-create-popup-title-wrapper {
position: absolute;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #d68338;
pointer-events: none;
}
.av-create-popup-header-buttons {
display: flex;
align-items: center;
gap: 4px;
margin-left: auto;
}
.av-create-popup-title-wrapper .av-icon {
width: 20px;
height: 20px;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
pointer-events: auto;
}
.av-create-popup-title-wrapper .av-create-popup-title {
@@ -691,6 +826,7 @@ body {
font-size: 18px;
font-weight: 600;
color: #f8f9fa;
pointer-events: auto;
}
.av-create-popup-title-container:hover {
@@ -719,6 +855,34 @@ body {
height: 16px;
}
.av-create-popup-popout {
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: #9ca3af;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s, color 0.2s;
}
.av-create-popup-popout:hover {
background-color: #4b5563;
color: #d68338;
}
.av-create-popup-popout .av-icon {
width: 16px;
height: 16px;
stroke: currentColor;
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.av-create-popup-mode-dropdown-menu {
position: absolute;
left: 50%;
@@ -832,4 +996,288 @@ body {
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

@@ -1,30 +1,55 @@
import React, { useState, useEffect } from 'react';
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { HashRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
import { sendMessage } from 'webext-bridge/popup';
import GlobalStateChangeHandler from '@/entrypoints/popup/components/GlobalStateChangeHandler';
import BottomNav from '@/entrypoints/popup/components/Layout/BottomNav';
import DefaultLayout from '@/entrypoints/popup/components/Layout/DefaultLayout';
import Header from '@/entrypoints/popup/components/Layout/Header';
import PasskeyLayout from '@/entrypoints/popup/components/Layout/PasskeyLayout';
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import { useApp } from '@/entrypoints/popup/context/AppContext';
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 Home from '@/entrypoints/popup/pages/Home';
import Login from '@/entrypoints/popup/pages/Login';
import Logout from '@/entrypoints/popup/pages/Logout';
import Settings from '@/entrypoints/popup/pages/Settings';
import Unlock from '@/entrypoints/popup/pages/Unlock';
import UnlockSuccess from '@/entrypoints/popup/pages/UnlockSuccess';
import AuthSettings from '@/entrypoints/popup/pages/auth/AuthSettings';
import Login from '@/entrypoints/popup/pages/auth/Login';
import Unlock from '@/entrypoints/popup/pages/auth/Unlock';
import UnlockSuccess from '@/entrypoints/popup/pages/auth/UnlockSuccess';
import Upgrade from '@/entrypoints/popup/pages/auth/Upgrade';
import CredentialAddEdit from '@/entrypoints/popup/pages/credentials/CredentialAddEdit';
import CredentialDetails from '@/entrypoints/popup/pages/credentials/CredentialDetails';
import CredentialsList from '@/entrypoints/popup/pages/credentials/CredentialsList';
import EmailDetails from '@/entrypoints/popup/pages/emails/EmailDetails';
import EmailsList from '@/entrypoints/popup/pages/emails/EmailsList';
import Index from '@/entrypoints/popup/pages/Index';
import PasskeyAuthenticate from '@/entrypoints/popup/pages/passkeys/PasskeyAuthenticate';
import PasskeyCreate from '@/entrypoints/popup/pages/passkeys/PasskeyCreate';
import Reinitialize from '@/entrypoints/popup/pages/Reinitialize';
import AutofillSettings from '@/entrypoints/popup/pages/settings/AutofillSettings';
import AutoLockSettings from '@/entrypoints/popup/pages/settings/AutoLockSettings';
import ClipboardSettings from '@/entrypoints/popup/pages/settings/ClipboardSettings';
import ContextMenuSettings from '@/entrypoints/popup/pages/settings/ContextMenuSettings';
import LanguageSettings from '@/entrypoints/popup/pages/settings/LanguageSettings';
import PasskeySettings from '@/entrypoints/popup/pages/settings/PasskeySettings';
import Settings from '@/entrypoints/popup/pages/settings/Settings';
import VaultUnlockSettings from '@/entrypoints/popup/pages/settings/VaultUnlockSettings';
import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';
import '@/entrypoints/popup/style.css';
import { clearPendingRedirectUrl } from './hooks/useVaultLockRedirect';
/**
* Available layout types for different page contexts.
*/
enum LayoutType {
/** Default layout with header, footer navigation, and full UI */
DEFAULT = 'default',
/** Minimal layout for passkey operations - logo only, no footer */
PASSKEY = 'passkey',
/** Auth layout for login/unlock pages - no footer menu */
AUTH = 'auth',
}
/**
* Route configuration.
@@ -34,34 +59,146 @@ type RouteConfig = {
element: React.ReactNode;
showBackButton?: boolean;
title?: string;
/** Layout type to use for this route. Defaults to LayoutType.DEFAULT if not specified. */
layout?: LayoutType;
};
/**
* AppContent - Wrapper component that switches between different layout types
*/
const AppContent: React.FC<{
routes: RouteConfig[];
isLoading: boolean;
message: string | null;
headerButtons: React.ReactNode;
}> = ({ routes, isLoading, message, headerButtons }) => {
const location = useLocation();
// Find the current route configuration
const currentRoute = routes.find(route => {
const pattern = route.path.replace(/:\w+/g, '[^/]+');
const regex = new RegExp(`^${pattern}$`);
return regex.test(location.pathname);
});
// Get layout type, defaulting to DEFAULT if not specified
const layoutType = currentRoute?.layout ?? LayoutType.DEFAULT;
// Common loading overlay
const loadingOverlay = isLoading && (
<div className="fixed inset-0 bg-white dark:bg-gray-900 z-50 flex items-center justify-center">
<LoadingSpinner />
</div>
);
// Common routes component
const routesComponent = (
<Routes>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
element={route.element}
/>
))}
</Routes>
);
// Render based on layout type
switch (layoutType) {
case LayoutType.PASSKEY:
// Passkey layout - minimal UI with just logo header
return (
<PasskeyLayout>
{loadingOverlay}
{message && (
<p className="mb-4 text-red-500 dark:text-red-400 text-sm">{message}</p>
)}
{routesComponent}
</PasskeyLayout>
);
case LayoutType.AUTH:
// Auth layout - header only, no footer menu for login/unlock pages
return (
<div className="min-h-screen min-w-[350px] bg-white dark:bg-gray-900 flex flex-col max-h-[600px]">
{loadingOverlay}
<Header
routes={routes}
rightButtons={headerButtons}
/>
<main
className="flex-1 overflow-y-auto bg-gray-100 dark:bg-gray-900"
style={{
paddingTop: '64px',
height: '100%',
}}
>
{message && (
<div className="px-4 pt-0">
<p className="text-red-500 dark:text-red-400 text-sm">{message}</p>
</div>
)}
{routesComponent}
</main>
</div>
);
case LayoutType.DEFAULT:
default:
// Default layout with full header, footer, navigation
return (
<>
{loadingOverlay}
<DefaultLayout
routes={routes}
headerButtons={headerButtons}
message={message}
>
{routesComponent}
</DefaultLayout>
</>
);
}
};
/**
* App component.
*/
const App: React.FC = () => {
const authContext = useAuth();
const { t } = useTranslation();
const app = useApp();
const { isInitialLoading } = useLoading();
const [isLoading, setIsLoading] = useMinDurationLoading(true, 150);
const [message, setMessage] = useState<string | null>(null);
const { headerButtons } = useHeaderButtons();
// Add these route configurations
const routes: RouteConfig[] = [
{ path: '/', element: <Home />, showBackButton: false },
{ path: '/login', element: <Login />, showBackButton: false },
{ path: '/unlock', element: <Unlock />, showBackButton: false },
{ path: '/unlock-success', element: <UnlockSuccess onClose={() => window.location.search = ''} />, showBackButton: false },
{ path: '/auth-settings', element: <AuthSettings />, showBackButton: true, title: 'Settings' },
// 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, layout: LayoutType.AUTH },
{ path: '/unlock', element: <Unlock />, showBackButton: false, layout: LayoutType.AUTH },
{ 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: 'Add credential' },
{ path: '/credentials/:id', element: <CredentialDetails />, showBackButton: true, title: 'Credential details' },
{ path: '/credentials/:id/edit', element: <CredentialAddEdit />, showBackButton: true, title: 'Edit credential' },
{ 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: '/passkeys/create', element: <PasskeyCreate />, layout: LayoutType.PASSKEY },
{ path: '/passkeys/authenticate', element: <PasskeyAuthenticate />, layout: LayoutType.PASSKEY },
{ path: '/emails', element: <EmailsList />, showBackButton: false },
{ path: '/emails/:id', element: <EmailDetails />, showBackButton: true, title: 'Email details' },
{ path: '/emails/:id', element: <EmailDetails />, showBackButton: true, title: t('emails.title') },
{ path: '/settings', element: <Settings />, showBackButton: false },
{ path: '/logout', element: <Logout />, showBackButton: false },
];
{ path: '/settings/unlock-method', element: <VaultUnlockSettings />, showBackButton: true, title: t('settings.unlockMethod.title') },
{ path: '/settings/autofill', element: <AutofillSettings />, showBackButton: true, title: t('settings.autofillSettings') },
{ path: '/settings/context-menu', element: <ContextMenuSettings />, showBackButton: true, title: t('settings.contextMenuSettings') },
{ path: '/settings/clipboard', element: <ClipboardSettings />, showBackButton: true, title: t('settings.clipboardSettings') },
{ path: '/settings/language', element: <LanguageSettings />, showBackButton: true, title: t('settings.language') },
{ path: '/settings/auto-lock', element: <AutoLockSettings />, showBackButton: true, title: t('settings.autoLockTimeout') },
{ path: '/settings/passkeys', element: <PasskeySettings />, showBackButton: true, title: t('settings.passkeySettings') },
], [t]);
useEffect(() => {
if (!isInitialLoading) {
@@ -69,57 +206,59 @@ const App: React.FC = () => {
}
}, [isInitialLoading, setIsLoading]);
/**
* Send heartbeat to background every 5 seconds while popup is open.
* This extends the auto-lock timer to prevent vault locking while popup is active.
*/
useEffect(() => {
// Send initial heartbeat
sendMessage('POPUP_HEARTBEAT', {}, 'background').catch(() => {
// Ignore errors as background script might not be ready
});
// Set up heartbeat interval
const heartbeatInterval = setInterval(() => {
sendMessage('POPUP_HEARTBEAT', {}, 'background').catch(() => {
// Ignore errors as background script might not be ready
});
}, 5000); // Send heartbeat every 5 seconds
// Cleanup: clear interval when popup closes
return () : void => {
clearInterval(heartbeatInterval);
};
}, []);
/**
* On initial load, clear any stale pending redirect URL if popup was not opened with a specific hash path.
*/
useEffect(() => {
const hasHashPath = window.location.hash && window.location.hash !== '#/' && window.location.hash !== '#';
if (!hasHashPath) {
clearPendingRedirectUrl();
}
}, []);
/**
* Print global message if it exists.
*/
useEffect(() => {
if (authContext.globalMessage) {
setMessage(authContext.globalMessage);
if (app.globalMessage) {
setMessage(app.globalMessage);
} else {
setMessage(null);
}
}, [authContext, authContext.globalMessage]);
}, [app, app.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>
)}
<GlobalStateChangeHandler />
<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>
<AppContent
routes={routes}
isLoading={isLoading}
message={message}
headerButtons={headerButtons}
/>
</NavigationProvider>
</Router>
);

View File

@@ -0,0 +1,31 @@
import React from 'react';
type AlertVariant = 'info' | 'warning' | 'error' | 'success';
interface IAlertProps {
variant: AlertVariant;
children: React.ReactNode;
className?: string;
}
/**
* Reusable alert component with consistent styling
*/
const Alert: React.FC<IAlertProps> = ({ variant, children, className = '' }) => {
const variantStyles = {
info: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200',
warning: 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800 text-yellow-800 dark:text-yellow-200',
error: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200',
success: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800 text-green-800 dark:text-green-200'
};
return (
<div className={`p-3 border rounded-lg ${variantStyles[variant]} ${className}`}>
<p className="text-sm">
{children}
</p>
</div>
);
};
export default Alert;

View File

@@ -0,0 +1,48 @@
import React from 'react';
export type AlertType = 'error' | 'success' | 'warning' | 'info';
interface IAlertMessageProps {
type: AlertType;
message: string;
className?: string;
}
/**
* Alert message component for displaying error, success, warning, or info messages.
* @param props - The component props.
* @param props.type - The type of alert (error, success, warning, info).
* @param props.message - The message to display.
* @param props.className - Optional additional CSS classes.
* @returns The rendered alert message component.
*/
const AlertMessage: React.FC<IAlertMessageProps> = ({ type, message, className = '' }) => {
/**
* Get the appropriate CSS classes based on alert type.
* @returns CSS class string.
*/
const getAlertClasses = (): string => {
const baseClasses = 'p-3 border rounded-md text-sm';
switch (type) {
case 'error':
return `${baseClasses} bg-red-100 dark:bg-red-900/30 border-red-300 dark:border-red-700 text-red-800 dark:text-red-300`;
case 'success':
return `${baseClasses} bg-green-100 dark:bg-green-900/30 border-green-300 dark:border-green-700 text-green-800 dark:text-green-300`;
case 'warning':
return `${baseClasses} bg-yellow-100 dark:bg-yellow-900/30 border-yellow-300 dark:border-yellow-700 text-yellow-800 dark:text-yellow-300`;
case 'info':
return `${baseClasses} bg-blue-100 dark:bg-blue-900/30 border-blue-300 dark:border-blue-700 text-blue-800 dark:text-blue-300`;
default:
return baseClasses;
}
};
return (
<div className={`${getAlertClasses()} ${className}`}>
{message}
</div>
);
};
export default AlertMessage;

View File

@@ -1,7 +1,8 @@
import React from 'react';
import React, { forwardRef } from 'react';
type ButtonProps = {
onClick?: () => void;
id?: string;
children: React.ReactNode;
type?: 'button' | 'submit' | 'reset';
variant?: 'primary' | 'secondary';
@@ -10,12 +11,13 @@ type ButtonProps = {
/**
* Button component
*/
const Button: React.FC<ButtonProps> = ({
const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
onClick,
id,
children,
type = 'button',
variant = 'primary'
}) => {
}, ref) => {
const colorClasses = {
primary: 'bg-primary-500 hover:bg-primary-600',
secondary: 'bg-gray-500 hover:bg-gray-600'
@@ -23,13 +25,17 @@ const Button: React.FC<ButtonProps> = ({
return (
<button
ref={ref}
className={`${colorClasses[variant]} text-white font-medium rounded-lg px-4 py-2 text-sm w-full`}
onClick={onClick}
type={type}
id={id}
>
{children}
</button>
);
};
});
Button.displayName = 'Button';
export default Button;

View File

@@ -0,0 +1,108 @@
import React, { useEffect, useState, useRef } from 'react';
import { onMessage, sendMessage } from 'webext-bridge/popup';
/**
* Clipboard countdown bar component.
*/
export const ClipboardCountdownBar: React.FC = () => {
const [isVisible, setIsVisible] = useState<boolean>(false);
const animationRef = useRef<HTMLDivElement>(null);
const currentCountdownIdRef = useRef<number>(0);
/**
* Starts the countdown animation.
*/
const startAnimation = (remaining: number, total: number) : void => {
// Use a small delay to ensure the component is fully rendered
setTimeout(() => {
if (animationRef.current) {
// Calculate the starting percentage based on remaining time
const percentage = (remaining / total) * 100;
// Reset any existing animation
animationRef.current.style.transition = 'none';
animationRef.current.style.width = `${percentage}%`;
// Force browser to flush styles
void animationRef.current.offsetHeight;
// Start animation from current position to 0
requestAnimationFrame(() => {
if (animationRef.current) {
animationRef.current.style.transition = `width ${remaining}s linear`;
animationRef.current.style.width = '0%';
}
});
}
}, 10);
};
useEffect(() => {
// Request current countdown state on mount
sendMessage('GET_CLIPBOARD_COUNTDOWN_STATE', {}, 'background').then((state) => {
const countdownState = state as { remaining: number; total: number; id: number } | null;
if (countdownState && countdownState.remaining > 0) {
currentCountdownIdRef.current = countdownState.id;
setIsVisible(true);
startAnimation(countdownState.remaining, countdownState.total);
}
}).catch(() => {
// No active countdown
});
// Listen for countdown updates from background script
const unsubscribe = onMessage('CLIPBOARD_COUNTDOWN', ({ data }) => {
const { remaining, total, id } = data as { remaining: number; total: number; id: number };
setIsVisible(remaining > 0);
// Check if this is a new countdown (different ID)
const isNewCountdown = id !== currentCountdownIdRef.current;
// Start animation when new countdown begins
if (isNewCountdown && remaining > 0) {
currentCountdownIdRef.current = id;
startAnimation(remaining, total);
}
});
// Listen for clipboard cleared message
const unsubscribeClear = onMessage('CLIPBOARD_CLEARED', () => {
setIsVisible(false);
currentCountdownIdRef.current = 0;
if (animationRef.current) {
animationRef.current.style.transition = 'none';
animationRef.current.style.width = '0%';
}
});
// Listen for countdown cancelled message
const unsubscribeCancel = onMessage('CLIPBOARD_COUNTDOWN_CANCELLED', () => {
setIsVisible(false);
currentCountdownIdRef.current = 0;
if (animationRef.current) {
animationRef.current.style.transition = 'none';
animationRef.current.style.width = '0%';
}
});
return () : void => {
// Clean up listeners
unsubscribe();
unsubscribeClear();
unsubscribeCancel();
};
}, []);
if (!isVisible) {
return null;
}
return (
<div className="fixed top-0 left-0 right-0 z-50 h-1 bg-gray-200 dark:bg-gray-700">
<div
ref={animationRef}
className="h-full bg-orange-500"
style={{ width: '100%', transition: 'none' }}
/>
</div>
);
};

View File

@@ -1,52 +0,0 @@
import React from 'react';
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 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">Login credentials</h2>
{email && (
<FormInputCopyToClipboard
id="email"
label="Email"
value={email}
/>
)}
{username && (
<FormInputCopyToClipboard
id="username"
label="Username"
value={username}
/>
)}
{password && (
<FormInputCopyToClipboard
id="password"
label="Password"
value={password}
type="password"
/>
)}
</div>
);
};
export default LoginCredentialsBlock;

View File

@@ -69,8 +69,38 @@ const CredentialCard: React.FC<CredentialCardProps> = ({ credential }) => {
target.src = '/assets/images/service-placeholder.webp';
}}
/>
<div className="text-left">
<p className="font-medium text-gray-900 dark:text-white">{getCredentialServiceName(credential)}</p>
<div className="text-left flex-1">
<div className="flex items-center gap-1.5">
<p className="font-medium text-gray-900 dark:text-white">{getCredentialServiceName(credential)}</p>
{credential.HasPasskey && (
<svg
className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-label="Has passkey"
>
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
</svg>
)}
{credential.HasAttachment && (
<svg
className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-label="Has attachments"
>
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
</svg>
)}
</div>
<p className="text-sm text-gray-600 dark:text-gray-400">{getDisplayText(credential)}</p>
</div>
</button>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/FormInputCopyToClipboard';
import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/Forms/FormInputCopyToClipboard';
import { IdentityHelperUtils } from '@/utils/dist/shared/identity-generator';
import type { Credential } from '@/utils/dist/shared/models/vault';
@@ -13,6 +14,7 @@ type AliasBlockProps = {
* 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());
@@ -24,39 +26,39 @@ const AliasBlock: React.FC<AliasBlockProps> = ({ credential }) => {
return (
<div className="space-y-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">Alias</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">{t('common.alias')}</h2>
{(hasFirstName || hasLastName) && (
<FormInputCopyToClipboard
id="fullName"
label="Full Name"
label={t('common.fullName')}
value={[credential.Alias?.FirstName, credential.Alias?.LastName].filter(Boolean).join(' ')}
/>
)}
{hasFirstName && (
<FormInputCopyToClipboard
id="firstName"
label="First Name"
label={t('common.firstName')}
value={credential.Alias?.FirstName ?? ''}
/>
)}
{hasLastName && (
<FormInputCopyToClipboard
id="lastName"
label="Last Name"
label={t('common.lastName')}
value={credential.Alias?.LastName ?? ''}
/>
)}
{hasBirthDate && (
<FormInputCopyToClipboard
id="birthDate"
label="Birth Date"
label={t('common.birthDate')}
value={IdentityHelperUtils.normalizeBirthDateForDisplay(credential.Alias?.BirthDate)}
/>
)}
{hasNickName && (
<FormInputCopyToClipboard
id="nickName"
label="Nickname"
label={t('common.nickname')}
value={credential.Alias?.NickName ?? ''}
/>
)}

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

@@ -27,7 +27,7 @@ const EmailBlock: React.FC<EmailBlockProps> = ({ email }) => {
const privateDomains = vaultMetadata?.privateEmailDomains ?? [];
return [...publicDomains, ...privateDomains].some(supportedDomain =>
domain === supportedDomain || domain.endsWith(`.${supportedDomain}`)
domain === supportedDomain.toLowerCase()
);
};

View File

@@ -21,14 +21,18 @@ const HeaderBlock: React.FC<HeaderBlockProps> = ({ credential }) => (
<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>
/^https?:\/\//i.test(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>
) : (
<span className="text-gray-500 dark:text-gray-300 break-all">{credential.ServiceUrl}</span>
)
)}
</div>
</div>

View File

@@ -0,0 +1,93 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { FormInputCopyToClipboard } from '@/entrypoints/popup/components/Forms/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 && !credential.HasPasskey) {
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}
/>
)}
{credential.HasPasskey && (
<div className="p-3 rounded bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700">
<div className="flex items-start gap-2">
<svg
className="w-5 h-5 text-gray-600 dark:text-gray-400 mt-0.5 flex-shrink-0"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" />
</svg>
<div className="flex-1">
<div className="mb-1">
<span className="text-sm font-medium text-gray-900 dark:text-white">{t('passkeys.passkey')}</span>
</div>
<div className="space-y-1 mb-2">
{credential.PasskeyRpId && (
<div>
<span className="text-xs text-gray-500 dark:text-gray-400">{t('passkeys.site')}: </span>
<span className="text-sm text-gray-900 dark:text-white">{credential.PasskeyRpId}</span>
</div>
)}
{credential.PasskeyDisplayName && (
<div>
<span className="text-xs text-gray-500 dark:text-gray-400">{t('passkeys.displayName')}: </span>
<span className="text-sm text-gray-900 dark:text-white">{credential.PasskeyDisplayName}</span>
</div>
)}
</div>
<p className="text-xs text-gray-600 dark:text-gray-400">
{t('passkeys.helpText')}
</p>
</div>
</div>
</div>
)}
{password && (
<FormInputCopyToClipboard
id="password"
label={t('common.password')}
value={password}
type="password"
/>
)}
</div>
);
};
export default LoginCredentialsBlock;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
type NotesBlockProps = {
notes: string | undefined;
@@ -20,6 +21,7 @@ const convertUrlsToLinks = (text: string): string => {
* Render the notes block.
*/
const NotesBlock: React.FC<NotesBlockProps> = ({ notes }) => {
const { t } = useTranslation();
if (!notes) {
return null;
}
@@ -28,7 +30,7 @@ const NotesBlock: React.FC<NotesBlockProps> = ({ notes }) => {
return (
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">Notes</h2>
<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"

View File

@@ -1,5 +1,7 @@
import * as OTPAuth from 'otpauth';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { sendMessage } from 'webext-bridge/popup';
import { useDb } from '@/entrypoints/popup/context/DbContext';
@@ -13,6 +15,7 @@ type TotpBlockProps = {
* This component shows TOTP codes for a credential.
*/
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>>({});
@@ -66,6 +69,9 @@ const TotpBlock: React.FC<TotpBlockProps> = ({ credentialId }) => {
try {
await navigator.clipboard.writeText(code);
setCopiedId(id);
// Notify background script that clipboard was copied
await sendMessage('CLIPBOARD_COPIED', { value: code }, 'background');
// Reset copied state after 2 seconds
setTimeout(() => {
@@ -138,8 +144,8 @@ const TotpBlock: React.FC<TotpBlockProps> = ({ 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>
);
}
@@ -151,7 +157,7 @@ const TotpBlock: React.FC<TotpBlockProps> = ({ credentialId }) => {
return (
<div className="mb-4">
<div className="space-y-2">
<h2 className="text-lg 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
@@ -171,7 +177,7 @@ const TotpBlock: React.FC<TotpBlockProps> = ({ 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>
)}

View File

@@ -0,0 +1,317 @@
import * as OTPAuth from 'otpauth';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { TotpCode } from '@/utils/dist/shared/models/vault';
type TotpFormData = {
name: string;
secretKey: string;
}
type TotpEditorState = {
isAddFormVisible: boolean;
formData: TotpFormData;
}
type TotpEditorProps = {
totpCodes: TotpCode[];
onTotpCodesChange: (totpCodes: TotpCode[]) => void;
originalTotpCodeIds: string[];
isAddFormVisible: boolean;
formData: TotpFormData;
onStateChange: (state: TotpEditorState) => void;
}
/**
* Component for editing TOTP codes for a credential.
*/
const TotpEditor: React.FC<TotpEditorProps> = ({
totpCodes,
onTotpCodesChange,
originalTotpCodeIds,
isAddFormVisible,
formData,
onStateChange
}) => {
const { t } = useTranslation();
const [formError, setFormError] = useState<string | null>(null);
/**
* Sanitizes the secret key by extracting it from a TOTP URI if needed
*/
const sanitizeSecretKey = (secretKeyInput: string, nameInput: string): { secretKey: string, name: string } => {
let secretKey = secretKeyInput.trim();
let name = nameInput.trim();
// Check if it's a TOTP URI
if (secretKey.toLowerCase().startsWith('otpauth://totp/')) {
try {
const uri = OTPAuth.URI.parse(secretKey);
if (uri instanceof OTPAuth.TOTP) {
secretKey = uri.secret.base32;
// If name is empty, use the label from the URI
if (!name && uri.label) {
name = uri.label;
}
}
} catch {
throw new Error(t('totp.errors.invalidSecretKey'));
}
}
// Remove spaces from the secret key
secretKey = secretKey.replace(/\s/g, '');
// Validate the secret key format (base32)
if (!/^[A-Z2-7]+=*$/i.test(secretKey)) {
throw new Error(t('totp.errors.invalidSecretKey'));
}
return { secretKey, name: name || 'Authenticator' };
};
/**
* Shows the add form
*/
const showAddForm = (): void => {
onStateChange({
isAddFormVisible: true,
formData: { name: '', secretKey: '' }
});
setFormError(null);
};
/**
* Hides the add form
*/
const hideAddForm = (): void => {
onStateChange({
isAddFormVisible: false,
formData: { name: '', secretKey: '' }
});
setFormError(null);
};
/**
* Updates form data
*/
const updateFormData = (updates: Partial<TotpFormData>): void => {
onStateChange({
isAddFormVisible,
formData: { ...formData, ...updates }
});
};
/**
* Handles adding a new TOTP code
*/
const handleAddTotpCode = (e?: React.MouseEvent | React.KeyboardEvent): void => {
e?.preventDefault();
setFormError(null);
// Validate required fields
if (!formData.secretKey) {
setFormError(t('credentials.validation.required'));
return;
}
try {
// Sanitize the secret key
const { secretKey, name } = sanitizeSecretKey(formData.secretKey, formData.name);
// Create new TOTP code
const newTotpCode: TotpCode = {
Id: crypto.randomUUID().toUpperCase(),
Name: name,
SecretKey: secretKey,
CredentialId: '' // Will be set when saving the credential
};
// Add to the list
const updatedTotpCodes = [...totpCodes, newTotpCode];
onTotpCodesChange(updatedTotpCodes);
// Hide the form
hideAddForm();
} catch (error) {
if (error instanceof Error) {
setFormError(error.message);
} else {
setFormError(t('common.errors.unknownErrorTryAgain'));
}
}
};
/**
* Initiates the delete process for a TOTP code
*/
const deleteTotpCode = (totpToDelete: TotpCode): void => {
// Check if this TOTP code was part of the original set
const wasOriginal = originalTotpCodeIds.includes(totpToDelete.Id);
let updatedTotpCodes: TotpCode[];
if (wasOriginal) {
// Mark as deleted (soft delete for syncing)
updatedTotpCodes = totpCodes.map(tc =>
tc.Id === totpToDelete.Id
? { ...tc, IsDeleted: true }
: tc
);
} else {
// Hard delete (remove from array)
updatedTotpCodes = totpCodes.filter(tc => tc.Id !== totpToDelete.Id);
}
onTotpCodesChange(updatedTotpCodes);
};
// Filter out deleted TOTP codes for display
const activeTotpCodes = totpCodes.filter(tc => !tc.IsDeleted);
const hasActiveTotpCodes = activeTotpCodes.length > 0;
return (
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
{t('common.twoFactorAuthentication')}
</h2>
{hasActiveTotpCodes && !isAddFormVisible && (
<button
type="button"
onClick={showAddForm}
className="w-8 h-8 flex items-center justify-center text-primary-700 hover:text-white border border-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg dark:border-primary-500 dark:text-primary-500 dark:hover:text-white dark:hover:bg-primary-600 dark:focus:ring-primary-800"
title={t('totp.addCode')}
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
)}
</div>
{!hasActiveTotpCodes && !isAddFormVisible && (
<button
type="button"
onClick={showAddForm}
className="w-full py-1.5 px-4 flex items-center justify-center gap-2 text-primary-700 hover:text-white border border-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg dark:border-primary-500 dark:text-primary-500 dark:hover:text-white dark:hover:bg-primary-600 dark:focus:ring-primary-800"
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
<span>{t('totp.addCode')}</span>
</button>
)}
{isAddFormVisible && (
<div className="p-4 mb-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
<div className="flex justify-between items-center mb-4">
<h4 className="text-lg font-medium text-gray-900 dark:text-white">
{t('totp.addCode')}
</h4>
<button
type="button"
onClick={hideAddForm}
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg className="w-3 h-3" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
</button>
</div>
<p className="mb-4 text-sm text-gray-500 dark:text-gray-400">
{t('totp.instructions')}
</p>
{formError && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg dark:bg-red-900/20 dark:border-red-800">
<p className="text-sm text-red-800 dark:text-red-200">{formError}</p>
</div>
)}
<div className="mb-4">
<label htmlFor="totp-name" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
{t('totp.nameOptional')}
</label>
<input
id="totp-name"
type="text"
value={formData.name}
onChange={(e) => updateFormData({ name: e.target.value })}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div className="mb-4">
<label htmlFor="totp-secret" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
{t('totp.secretKey')}
</label>
<input
id="totp-secret"
type="text"
value={formData.secretKey}
onChange={(e) => updateFormData({ secretKey: e.target.value })}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddTotpCode(e);
}
}}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div className="flex justify-end">
<button
type="button"
onClick={(e) => handleAddTotpCode(e)}
className="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
>
{t('common.save')}
</button>
</div>
</div>
)}
{hasActiveTotpCodes && (
<div className="grid grid-cols-1 gap-4 mt-4">
{activeTotpCodes.map(totpCode => (
<div
key={totpCode.Id}
className="p-2 ps-3 pe-3 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600"
>
<div className="flex justify-between items-center gap-2">
<div className="flex items-center flex-1">
<h4 className="text-sm font-medium text-gray-900 dark:text-white">
{totpCode.Name}
</h4>
</div>
<div className="flex items-center gap-2">
<div className="flex flex-col items-end">
<div className="text-sm text-gray-500 dark:text-gray-400">
{t('totp.saveToViewCode')}
</div>
</div>
<button
type="button"
onClick={() => deleteTotpCode(totpCode)}
className="text-red-600 hover:text-red-800 dark:text-red-500 dark:hover:text-red-400"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd"></path>
</svg>
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
};
export default TotpEditor;

View File

@@ -1,4 +1,5 @@
import AliasBlock from './AliasBlock';
import AttachmentBlock from './AttachmentBlock';
import EmailBlock from './EmailBlock';
import HeaderBlock from './HeaderBlock';
import LoginCredentialsBlock from './LoginCredentialsBlock';
@@ -11,5 +12,6 @@ export {
TotpBlock,
LoginCredentialsBlock,
AliasBlock,
NotesBlock
NotesBlock,
AttachmentBlock
};

View File

@@ -0,0 +1,84 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
type HelpModalProps = {
title: string;
content: string;
className?: string;
}
/**
* Reusable help modal component with a question mark icon button.
* Shows a modal popup with help information when clicked.
*/
const HelpModal: React.FC<HelpModalProps> = ({ title, content, className = '' }) => {
const { t } = useTranslation();
const [showModal, setShowModal] = useState(false);
return (
<>
<button
onClick={() => setShowModal(true)}
className={`${className}`}
type="button"
aria-label="Help"
>
<svg
className="w-4 h-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 cursor-help"
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</button>
{showModal && (
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-sm w-full">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{title}
</h3>
<button
onClick={() => setShowModal(false)}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label={t('common.close')}
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<p className="text-sm text-gray-600 dark:text-gray-300">
{content}
</p>
<button
onClick={() => setShowModal(false)}
className="mt-4 w-full px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded-md transition-colors"
>
{t('common.close')}
</button>
</div>
</div>
)}
</>
);
};
export default HelpModal;

View File

@@ -0,0 +1,244 @@
import QRCode from 'qrcode';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MobileLoginErrorCode } from '@/entrypoints/popup/types/MobileLoginErrorCode';
import { MobileLoginUtility } from '@/entrypoints/popup/utils/MobileLoginUtility';
import type { MobileLoginResult } from '@/utils/types/messaging/MobileLoginResult';
import type { WebApiService } from '@/utils/WebApiService';
interface IMobileUnlockModalProps {
isOpen: boolean;
onClose: () => void;
onSuccess: (result: MobileLoginResult) => Promise<void>;
webApi: WebApiService;
mode?: 'login' | 'unlock';
}
/**
* Modal component for mobile login/unlock via QR code scanning.
*/
const MobileUnlockModal: React.FC<IMobileUnlockModalProps> = ({
isOpen,
onClose,
onSuccess,
webApi,
mode = 'login'
}) => {
const { t } = useTranslation();
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
const [error, setError] = useState<MobileLoginErrorCode | null>(null);
const [timeRemaining, setTimeRemaining] = useState<number>(120); // 2 minutes in seconds
const mobileLoginRef = useRef<MobileLoginUtility | null>(null);
const countdownIntervalRef = useRef<NodeJS.Timeout | null>(null);
/**
* Get translated error message for error code.
*/
const getErrorMessage = (errorCode: MobileLoginErrorCode): string => {
switch (errorCode) {
case MobileLoginErrorCode.TIMEOUT:
return t('auth.errors.mobileLoginRequestExpired');
case MobileLoginErrorCode.GENERIC:
default:
return t('common.errors.unknownError');
}
};
// Countdown timer effect
useEffect(() => {
if (qrCodeUrl && timeRemaining > 0 && isOpen) {
countdownIntervalRef.current = setInterval(() => {
setTimeRemaining(prev => {
if (prev <= 1) {
if (countdownIntervalRef.current) {
clearInterval(countdownIntervalRef.current);
}
return 0;
}
return prev - 1;
});
}, 1000);
return (): void => {
if (countdownIntervalRef.current) {
clearInterval(countdownIntervalRef.current);
}
};
}
}, [qrCodeUrl, timeRemaining, isOpen]);
// Initialize mobile login when modal opens
useEffect(() => {
if (!isOpen) {
return;
}
/**
* Initialize mobile login on modal open.
*/
const initiateMobileLogin = async (): Promise<void> => {
try {
setError(null);
setQrCodeUrl(null);
setTimeRemaining(120);
// Initialize mobile login utility
if (!mobileLoginRef.current) {
mobileLoginRef.current = new MobileLoginUtility(webApi);
}
// Initiate mobile login and get QR code data
const requestId = await mobileLoginRef.current.initiate();
// Generate QR code with AliasVault prefix for mobile login
const qrData = `aliasvault://open/mobile-unlock/${requestId}`;
const qrDataUrl = await QRCode.toDataURL(qrData, {
width: 256,
margin: 2,
});
setQrCodeUrl(qrDataUrl);
// Start polling for response
await mobileLoginRef.current.startPolling(
async (result: MobileLoginResult) => {
try {
// Call success callback (parent handles loading state)
await onSuccess(result);
// Close modal after successful processing
handleClose();
} catch {
// Show error if success handler fails and hide QR code
setQrCodeUrl(null);
setError(MobileLoginErrorCode.GENERIC);
}
},
(errorCode) => {
// Hide QR code when error occurs
setQrCodeUrl(null);
setError(errorCode);
}
);
} catch (err) {
// err is a MobileLoginErrorCode thrown by initiate()
if (typeof err === 'string' && Object.values(MobileLoginErrorCode).includes(err as MobileLoginErrorCode)) {
setError(err as MobileLoginErrorCode);
} else {
setError(MobileLoginErrorCode.GENERIC);
}
}
};
initiateMobileLogin();
// Cleanup on unmount or when modal closes
return (): void => {
if (mobileLoginRef.current) {
mobileLoginRef.current.cleanup();
}
if (countdownIntervalRef.current) {
clearInterval(countdownIntervalRef.current);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
/**
* Handle modal close.
*/
const handleClose = (): void => {
if (mobileLoginRef.current) {
mobileLoginRef.current.cleanup();
}
if (countdownIntervalRef.current) {
clearInterval(countdownIntervalRef.current);
}
setQrCodeUrl(null);
setError(null);
setTimeRemaining(120);
onClose();
};
/**
* Format time remaining as MM:SS.
*/
const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
if (!isOpen) {
return null;
}
const title = mode === 'unlock' ? t('auth.unlockWithMobile') : t('auth.loginWithMobile');
const description = t('auth.scanQrCode');
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div className="fixed inset-0 bg-black bg-opacity-80 transition-opacity" onClick={handleClose} />
{/* Modal */}
<div className="fixed inset-0 flex items-center justify-center p-4">
<div className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 px-6 pb-6 pt-5 text-left shadow-xl transition-all w-full max-w-md">
{/* Close button */}
<button
type="button"
className="absolute right-4 top-4 text-gray-400 hover:text-gray-500 focus:outline-none"
onClick={handleClose}
>
<span className="sr-only">{t('common.close')}</span>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Content */}
<div className="mt-3">
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2">
{title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
{description}
</p>
{error && (
<div className="mb-4 p-3 bg-red-100 dark:bg-red-900/30 border border-red-400 dark:border-red-700 rounded text-red-700 dark:text-red-400 text-sm">
{getErrorMessage(error)}
</div>
)}
{qrCodeUrl && (
<div className="flex flex-col items-center mb-4">
<img src={qrCodeUrl} alt="QR Code" className="border-4 border-gray-200 dark:border-gray-600 rounded mb-3" />
<div className="text-gray-700 dark:text-gray-300 text-sm font-medium">
{formatTime(timeRemaining)}
</div>
</div>
)}
{!qrCodeUrl && !error && (
<div className="flex justify-center py-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
</div>
)}
<button
type="button"
onClick={handleClose}
className="mt-4 w-full inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500"
>
{t('common.cancel')}
</button>
</div>
</div>
</div>
</div>
);
};
export default MobileUnlockModal;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
interface IModalProps {
isOpen: boolean;
@@ -20,10 +21,11 @@ const Modal: React.FC<IModalProps> = ({
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
confirmText = '',
cancelText = '',
variant = 'default'
}) => {
const { t } = useTranslation();
if (!isOpen) {
return null;
}
@@ -35,7 +37,7 @@ const Modal: React.FC<IModalProps> = ({
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div className="fixed inset-0 bg-black bg-opacity-60 transition-opacity" onClick={onClose} />
<div className="fixed inset-0 bg-black bg-opacity-80 transition-opacity" onClick={onClose} />
{/* Modal */}
<div className="fixed inset-0 flex items-center justify-center p-4">
@@ -46,7 +48,7 @@ const Modal: React.FC<IModalProps> = ({
className="absolute right-4 top-4 text-gray-400 hover:text-gray-500 focus:outline-none"
onClick={onClose}
>
<span className="sr-only">Close</span>
<span className="sr-only">{t('common.close')}</span>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
@@ -75,20 +77,24 @@ const Modal: React.FC<IModalProps> = ({
{/* Actions */}
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="button"
className={`inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 sm:ml-3 sm:w-auto ${confirmButtonClass}`}
onClick={onConfirm}
>
{confirmText}
</button>
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 sm:mt-0 sm:w-auto"
onClick={onClose}
>
{cancelText}
</button>
{confirmText && (
<button
type="button"
className={`inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 sm:ml-3 sm:w-auto ${confirmButtonClass}`}
onClick={onConfirm}
>
{confirmText}
</button>
)}
{cancelText && (
<button
type="button"
className="mt-3 inline-flex w-full justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 sm:mt-0 sm:w-auto"
onClick={onClose}
>
{cancelText}
</button>
)}
</div>
</div>
</div>

View File

@@ -0,0 +1,60 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../Button';
type PasskeyBypassDialogProps = {
origin: string;
onChoice: (choice: 'once' | 'always') => void;
onCancel: () => void;
};
/**
* Dialog for choosing how to bypass AliasVault passkey provider
*/
const PasskeyBypassDialog: React.FC<PasskeyBypassDialogProps> = ({
origin,
onChoice,
onCancel
}) => {
const { t } = useTranslation();
return (
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
{t('passkeys.bypass.title')}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-6">
{t('passkeys.bypass.description', { origin })}
</p>
<div className="space-y-3">
<Button
variant="primary"
onClick={() => onChoice('once')}
>
{t('passkeys.bypass.thisTimeOnly')}
</Button>
<Button
variant="secondary"
onClick={() => onChoice('always')}
>
{t('passkeys.bypass.alwaysForSite')}
</Button>
<Button
variant="secondary"
onClick={onCancel}
>
{t('common.cancel')}
</Button>
</div>
</div>
</div>
);
};
export default PasskeyBypassDialog;

View File

@@ -0,0 +1,218 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import type { PasswordSettings } from '@/utils/dist/shared/models/vault';
import { CreatePasswordGenerator } from '@/utils/dist/shared/password-generator';
interface IPasswordConfigDialogProps {
isOpen: boolean;
onClose: () => void;
onSave: (password: string) => void;
onSettingsChange?: (settings: PasswordSettings) => void;
initialSettings: PasswordSettings;
}
/**
* Password configuration dialog component.
*/
const PasswordConfigDialog: React.FC<IPasswordConfigDialogProps> = ({
isOpen,
onClose,
onSave,
onSettingsChange,
initialSettings
}) => {
const { t } = useTranslation();
const [settings, setSettings] = useState<PasswordSettings>(initialSettings);
const [previewPassword, setPreviewPassword] = useState<string>('');
const generatePreview = useCallback((currentSettings: PasswordSettings) => {
try {
const passwordGenerator = CreatePasswordGenerator(currentSettings);
const password = passwordGenerator.generateRandomPassword();
setPreviewPassword(password);
} catch (error) {
console.error('Error generating preview password:', error);
setPreviewPassword('');
}
}, []);
// Initialize settings when dialog opens
useEffect(() => {
if (isOpen) {
setSettings({ ...initialSettings });
generatePreview({ ...initialSettings });
}
}, [isOpen, initialSettings, generatePreview]);
const handleSettingChange = useCallback((key: keyof PasswordSettings, value: boolean | number) => {
const newSettings = { ...settings, [key]: value };
setSettings(newSettings);
generatePreview(newSettings);
onSettingsChange?.(newSettings);
}, [settings, generatePreview, onSettingsChange]);
const handleRefreshPreview = useCallback(() => {
generatePreview(settings);
}, [settings, generatePreview]);
const handleSave = useCallback(() => {
onSave(previewPassword);
onClose();
}, [previewPassword, onSave, onClose]);
const handleCancel = useCallback(() => {
onClose();
}, [onClose]);
if (!isOpen) {
return null;
}
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
{/* Backdrop */}
<div className="fixed inset-0 bg-black bg-opacity-60 transition-opacity" onClick={handleCancel} />
{/* Modal */}
<div className="fixed inset-0 flex items-center justify-center p-4">
<div className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl transition-all w-full max-w-lg">
{/* Close button */}
<button
type="button"
className="absolute right-4 top-4 text-gray-400 hover:text-gray-500 focus:outline-none"
onClick={handleCancel}
>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
{/* Modal content */}
<div className="sm:flex sm:items-start">
<div className="w-full mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-white mb-4">
{t('credentials.changePasswordComplexity')}
</h3>
<div className="space-y-4">
{/* Password Preview */}
<div className="mt-4">
<div className="flex items-center gap-2">
<input
type="text"
value={previewPassword}
readOnly
className="flex-1 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:text-white font-mono"
/>
<button
type="button"
onClick={handleRefreshPreview}
className="px-3 py-2 text-sm text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
title={t('credentials.generateNewPreview')}
>
<svg className="w-4 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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"></path>
</svg>
</button>
</div>
</div>
{/* Character Type Toggle Buttons */}
<div className="space-y-3">
<div className="grid grid-cols-2 gap-2">
{/* Lowercase Toggle */}
<button
type="button"
onClick={() => handleSettingChange('UseLowercase', !settings.UseLowercase)}
className={`flex items-center justify-center px-3 py-2 rounded-md text-sm font-medium transition-colors ${
settings.UseLowercase
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
}`}
title={t('credentials.includeLowercase')}
>
<span className="font-mono text-base">a-z</span>
</button>
{/* Uppercase Toggle */}
<button
type="button"
onClick={() => handleSettingChange('UseUppercase', !settings.UseUppercase)}
className={`flex items-center justify-center px-3 py-2 rounded-md text-sm font-medium transition-colors ${
settings.UseUppercase
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
}`}
title={t('credentials.includeUppercase')}
>
<span className="font-mono text-base">A-Z</span>
</button>
{/* Numbers Toggle */}
<button
type="button"
onClick={() => handleSettingChange('UseNumbers', !settings.UseNumbers)}
className={`flex items-center justify-center px-3 py-2 rounded-md text-sm font-medium transition-colors ${
settings.UseNumbers
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
}`}
title={t('credentials.includeNumbers')}
>
<span className="font-mono text-base">0-9</span>
</button>
{/* Special Characters Toggle */}
<button
type="button"
onClick={() => handleSettingChange('UseSpecialChars', !settings.UseSpecialChars)}
className={`flex items-center justify-center px-3 py-2 rounded-md text-sm font-medium transition-colors ${
settings.UseSpecialChars
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
}`}
title={t('credentials.includeSpecialChars')}
>
<span className="font-mono text-base">!@#</span>
</button>
</div>
{/* Avoid Ambiguous Characters - Checkbox */}
<div className="flex items-center">
<input
id="use-non-ambiguous"
type="checkbox"
checked={settings.UseNonAmbiguousChars}
onChange={(e) => handleSettingChange('UseNonAmbiguousChars', e.target.checked)}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
/>
<label htmlFor="use-non-ambiguous" className="ml-2 text-sm text-gray-700 dark:text-gray-300">
{t('credentials.avoidAmbiguousChars')}
</label>
</div>
</div>
</div>
{/* Action buttons */}
<div className="mt-5 sm:mt-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="inline-flex w-full justify-center items-center gap-1 rounded-md bg-gray-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 sm:ml-3 sm:w-auto"
onClick={handleSave}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 13l-3 3m0 0l-3-3m3 3V8m0 13a9 9 0 110-18 9 9 0 010 18z" />
</svg>
{t('common.use')}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default PasswordConfigDialog;

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { useDb } from '@/entrypoints/popup/context/DbContext';
@@ -18,22 +19,45 @@ type EmailPreviewProps = {
* 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));
return publicEmailDomains.some(domain => emailAddress.toLowerCase().endsWith(`@${domain.toLowerCase()}`));
};
/**
@@ -42,7 +66,7 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
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));
return privateEmailDomains.some(domain => emailAddress.toLowerCase().endsWith(`@${domain.toLowerCase()}`));
};
useEffect(() => {
@@ -74,23 +98,30 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
});
if (!response.ok) {
setError('An error occurred while loading emails. Please try again later.');
setError(t('emails.errors.emailLoadError'));
return;
}
const data = await response.json();
// Only show the latest 2 emails to save space in UI
const latestMails = data?.mails
// 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())
?.slice(0, 2) ?? [];
new Date(b.dateSystem).getTime() - new Date(a.dateSystem).getTime()) ?? [];
if (loading && latestMails.length > 0) {
setLastEmailId(latestMails[0].id);
if (loading && allMails.length > 0) {
setLastEmailId(allMails[0].id);
}
setEmails(latestMails);
// 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 {
@@ -102,15 +133,14 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
try {
const data = response as { mails: MailboxEmail[] };
// Only show the latest 2 emails to save space in UI
const latestMails = data.mails
.toSorted((a, b) => new Date(b.dateSystem).getTime() - new Date(a.dateSystem).getTime())
.slice(0, 2);
// Store all emails, sorted by date
const allMails = data.mails
.toSorted((a, b) => new Date(b.dateSystem).getTime() - new Date(a.dateSystem).getTime());
if (latestMails) {
if (allMails) {
// Loop through all emails and decrypt them locally
const decryptedEmails: MailboxEmail[] = await EncryptionUtility.decryptEmailList(
latestMails,
allMails,
dbContext.sqliteClient!.getAllEncryptionKeys()
);
@@ -118,30 +148,30 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
setLastEmailId(decryptedEmails[0].id);
}
setEmails(decryptedEmails);
// 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;
if (apiErrorResponse?.code === 'CLAIM_DOES_NOT_MATCH_USER') {
setError('The current chosen email address is already in use. Please change the email address by editing this credential.');
} else if (apiErrorResponse?.code === 'CLAIM_DOES_NOT_EXIST') {
setError('An error occurred while trying to load the emails. Please try to edit and save the credential entry to synchronize the database, then try again.');
} else {
setError('An error occurred while loading emails. Please try again later.');
}
setError(t('emails.apiErrors.' + apiErrorResponse?.code));
return;
}
} catch {
setError('An error occurred while loading emails. Please try again later.');
setError(t('emails.errors.emailLoadError'));
return;
}
}
} catch (err) {
console.error('Error loading emails:', err);
setError('An unexpected error occurred while loading emails. Please try again later.');
setError(t('emails.errors.emailUnexpectedError'));
}
setLoading(false);
};
@@ -150,7 +180,7 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
// Set up auto-refresh interval
const interval = setInterval(loadEmails, 2000);
return () : void => clearInterval(interval);
}, [email, loading, webApi, dbContext]);
}, [email, loading, webApi, dbContext, t, displayedCount]);
// Don't render anything if the domain is not supported
if (!isSupportedDomain) {
@@ -161,7 +191,7 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
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">Recent emails</h2>
<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>
@@ -174,21 +204,21 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
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">Recent emails</h2>
<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>
Loading emails...
{t('common.loadingEmails')}
</div>
);
}
if (emails.length === 0) {
return (
<div className="text-gray-500 dark:text-gray-400 mb-4">
<div className="text-gray-500 dark:text-gray-400 mb-4 text-sm">
<div className="flex items-center gap-2 mb-2">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Recent emails</h2>
<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>
No emails received yet.
{t('emails.noEmails')}
</div>
);
}
@@ -196,11 +226,11 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
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">Recent emails</h2>
<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>
{emails.map((mail) => (
{displayedEmails.map((mail) => (
isSpamOk ? (
<a
key={mail.id}
@@ -239,6 +269,18 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) => {
</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,320 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDb } from '@/entrypoints/popup/context/DbContext';
type EmailDomainFieldProps = {
id: string;
label: string;
value: string;
onChange: (value: string) => void;
error?: string;
required?: boolean;
}
// Hardcoded public email domains (same as in AliasVault.Client)
const PUBLIC_EMAIL_DOMAINS = [
'spamok.com',
'solarflarecorp.com',
'spamok.nl',
'3060.nl',
'landmail.nl',
'asdasd.nl',
'spamok.de',
'spamok.com.ua',
'spamok.es',
'spamok.fr',
];
/**
* Email domain field component with domain chooser functionality.
* Allows users to select from private/public domains or enter custom email addresses.
*/
const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
id,
label,
value,
onChange,
error,
required = false
}) => {
const { t } = useTranslation();
const dbContext = useDb();
const [isCustomDomain, setIsCustomDomain] = useState(false);
const [localPart, setLocalPart] = useState('');
const [selectedDomain, setSelectedDomain] = useState('');
const [isPopupVisible, setIsPopupVisible] = useState(false);
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
const [hiddenPrivateEmailDomains, setHiddenPrivateEmailDomains] = useState<string[]>([]);
const popupRef = useRef<HTMLDivElement>(null);
// Get private email domains from vault metadata
useEffect(() => {
/**
* Load private email domains from vault metadata, excluding hidden ones.
*/
const loadDomains = async (): Promise<void> => {
const metadata = await dbContext.getVaultMetadata();
setPrivateEmailDomains(metadata?.privateEmailDomains ?? []);
setHiddenPrivateEmailDomains(metadata?.hiddenPrivateEmailDomains ?? []);
};
loadDomains();
}, [dbContext]);
// Check if private domains are available and valid
const showPrivateDomains = useMemo(() => {
return privateEmailDomains.length > 0 &&
!(privateEmailDomains.length === 1 && (privateEmailDomains[0] === 'DISABLED.TLD' || privateEmailDomains[0] === ''));
}, [privateEmailDomains]);
// Initialize state from value prop
useEffect(() => {
if (!value) {
// Set default domain
if (showPrivateDomains && privateEmailDomains[0]) {
setSelectedDomain(privateEmailDomains[0]);
} else if (PUBLIC_EMAIL_DOMAINS[0]) {
setSelectedDomain(PUBLIC_EMAIL_DOMAINS[0]);
}
return;
}
if (value.includes('@')) {
const [local, domain] = value.split('@');
setLocalPart(local);
setSelectedDomain(domain);
// Check if it's a custom domain (including hidden private domains as known domains)
const isKnownDomain = PUBLIC_EMAIL_DOMAINS.includes(domain) ||
privateEmailDomains.includes(domain) ||
hiddenPrivateEmailDomains.includes(domain);
setIsCustomDomain(!isKnownDomain);
} else {
setLocalPart(value);
// Don't reset isCustomDomain here - preserve the current mode
// Set default domain if not already set
if (!selectedDomain && !value.includes('@')) {
if (showPrivateDomains && privateEmailDomains[0]) {
setSelectedDomain(privateEmailDomains[0]);
} else if (PUBLIC_EMAIL_DOMAINS[0]) {
setSelectedDomain(PUBLIC_EMAIL_DOMAINS[0]);
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value, privateEmailDomains, hiddenPrivateEmailDomains, showPrivateDomains]);
// Handle local part changes
const handleLocalPartChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const newLocalPart = e.target.value;
// If in custom domain mode, always pass through the full value
if (isCustomDomain) {
onChange(newLocalPart);
// Stay in custom domain mode - don't auto-switch back
return;
}
// Check if new value contains '@' symbol, if so, switch to custom domain mode
if (newLocalPart.includes('@')) {
setIsCustomDomain(true);
onChange(newLocalPart);
return;
}
setLocalPart(newLocalPart);
// If the local part is empty, treat the whole field as empty
if (!newLocalPart || newLocalPart.trim() === '') {
onChange('');
} else if (selectedDomain) {
onChange(`${newLocalPart}@${selectedDomain}`);
}
}, [isCustomDomain, selectedDomain, onChange]);
// Select a domain from the popup
const selectDomain = useCallback((domain: string) => {
setSelectedDomain(domain);
const cleanLocalPart = localPart.includes('@') ? localPart.split('@')[0] : localPart;
// If the local part is empty, treat the whole field as empty
if (!cleanLocalPart || cleanLocalPart.trim() === '') {
onChange('');
} else {
onChange(`${cleanLocalPart}@${domain}`);
}
setIsCustomDomain(false);
setIsPopupVisible(false);
}, [localPart, onChange]);
// Toggle between custom domain and domain chooser
const toggleCustomDomain = useCallback(() => {
const newIsCustom = !isCustomDomain;
setIsCustomDomain(newIsCustom);
if (newIsCustom) {
/*
* Switching to custom domain mode
* If we have a domain-based value, extract just the local part
*/
if (value && value.includes('@')) {
const [local] = value.split('@');
onChange(local);
setLocalPart(local);
}
} else {
// Switching to domain chooser mode
const defaultDomain = showPrivateDomains && privateEmailDomains[0]
? privateEmailDomains[0]
: PUBLIC_EMAIL_DOMAINS[0];
setSelectedDomain(defaultDomain);
// Only add domain if we have a local part
if (localPart && localPart.trim()) {
onChange(`${localPart}@${defaultDomain}`);
} else if (value && !value.includes('@')) {
// If we have a value without @, add the domain
onChange(`${value}@${defaultDomain}`);
}
}
}, [isCustomDomain, value, localPart, showPrivateDomains, privateEmailDomains, onChange]);
// Handle clicks outside the popup
useEffect(() => {
/**
* Handle clicks outside the popup to close it.
*/
const handleClickOutside = (event: MouseEvent): void => {
if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
setIsPopupVisible(false);
}
};
if (isPopupVisible) {
document.addEventListener('mousedown', handleClickOutside);
return (): void => {
document.removeEventListener('mousedown', handleClickOutside);
};
}
}, [isPopupVisible]);
return (
<div className="space-y-2">
<label htmlFor={id} className="block font-medium text-gray-700 dark:text-gray-300">
{label}
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<div className="relative w-full">
<div className="flex w-full">
<input
type="text"
id={id}
className={`flex-1 min-w-0 px-3 py-2 border text-sm ${
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'
} ${
!isCustomDomain ? 'rounded-l-md' : 'rounded-md'
} focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white`}
value={isCustomDomain ? value : localPart}
onChange={handleLocalPartChange}
placeholder={isCustomDomain ? t('credentials.enterFullEmail') : t('credentials.enterEmailPrefix')}
/>
{!isCustomDomain && (
<button
type="button"
onClick={() => setIsPopupVisible(!isPopupVisible)}
className="inline-flex items-center px-2 py-2 border border-l-0 border-gray-300 dark:border-gray-600 rounded-r-md bg-gray-50 dark:bg-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-500 cursor-pointer text-sm truncate max-w-[120px]"
>
<span className="text-gray-500 dark:text-gray-400">@</span>
<span className="truncate ml-0.5">{selectedDomain}</span>
</button>
)}
</div>
{/* Domain selection popup */}
{isPopupVisible && !isCustomDomain && (
<div
ref={popupRef}
className="absolute z-50 mt-2 w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 max-h-96 overflow-y-auto"
>
<div className="p-4">
{showPrivateDomains && (
<div className="mb-4">
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
{t('credentials.privateEmailTitle')} <span className="text-gray-500 dark:text-gray-400">({t('credentials.privateEmailAliasVaultServer')})</span>
</h4>
<p className="text-gray-500 dark:text-gray-400 mb-3">
{t('credentials.privateEmailDescription')}
</p>
<div className="flex flex-wrap gap-2">
{privateEmailDomains
.filter((domain) => !hiddenPrivateEmailDomains.includes(domain))
.map((domain) => (
<button
key={domain}
type="button"
onClick={() => selectDomain(domain)}
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
selectedDomain === domain
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
}`}
>
{domain}
</button>
))}
</div>
</div>
)}
<div className={showPrivateDomains ? 'border-t border-gray-200 dark:border-gray-600 pt-4' : ''}>
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
{t('credentials.publicEmailTitle')}
</h4>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3">
{t('credentials.publicEmailDescription')}
</p>
<div className="flex flex-wrap gap-2">
{PUBLIC_EMAIL_DOMAINS.map((domain) => (
<button
key={domain}
type="button"
onClick={() => selectDomain(domain)}
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
selectedDomain === domain
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
}`}
>
{domain}
</button>
))}
</div>
</div>
</div>
</div>
)}
</div>
{/* Toggle custom domain button */}
<div>
<button
type="button"
onClick={toggleCustomDomain}
className="text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
>
{isCustomDomain
? t('credentials.useDomainChooser')
: t('credentials.enterCustomDomain')}
</button>
</div>
{/* Error message */}
{error && (
<p className="text-sm text-red-500 mt-1">{error}</p>
)}
</div>
);
};
export default EmailDomainField;

View File

@@ -1,4 +1,5 @@
import React, { forwardRef } from 'react';
import { useTranslation } from 'react-i18next';
/**
* Button configuration for form input.
@@ -36,6 +37,13 @@ const Icon: React.FC<{ name: string }> = ({ name }) => {
<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;
}
@@ -78,6 +86,7 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(({
showPassword: controlledShowPassword,
onShowPasswordChange
}, ref) => {
const { t } = useTranslation();
const [internalShowPassword, setInternalShowPassword] = React.useState(false);
/**
@@ -100,8 +109,8 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(({
}
};
const inputClasses = `mt-1 block w-full rounded-md ${
error ? 'border-red-500' : 'border-gray-300 dark:border-gray-700'
const inputClasses = `mt-1 block text-sm 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
@@ -112,7 +121,7 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(({
* Toggle password visibility.
*/
onClick: (): void => setShowPassword(!showPassword),
title: showPassword ? 'Hide password' : 'Show password'
title: showPassword ? t('common.hidePassword') : t('common.showPassword')
}]
: buttons;

View File

@@ -1,4 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { sendMessage } from 'webext-bridge/popup';
import { ClipboardCopyService } from '@/entrypoints/popup/utils/ClipboardCopyService';
@@ -60,6 +62,7 @@ export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> =
value,
type = 'text'
}) => {
const { t } = useTranslation();
const [showPassword, setShowPassword] = useState(false);
const [copied, setCopied] = useState(false);
@@ -80,6 +83,9 @@ export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> =
await navigator.clipboard.writeText(value);
clipboardService.setCopied(id);
// Notify background script that clipboard was copied
await sendMessage('CLIPBOARD_COPIED', { value }, 'background');
// Reset copied state after 2 seconds
setTimeout(() => {
if (clipboardService.getCopiedId() === id) {
@@ -105,14 +111,14 @@ export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> =
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`}
} text-gray-900 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="Copied!"
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" />
@@ -123,7 +129,7 @@ export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> =
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="Copy to clipboard"
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" />
@@ -135,7 +141,7 @@ export const FormInputCopyToClipboard: React.FC<FormInputCopyToClipboardProps> =
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 ? 'Hide password' : 'Show password'}
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'} />

View File

@@ -0,0 +1,237 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import PasswordConfigDialog from '@/entrypoints/popup/components/Dialogs/PasswordConfigDialog';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import type { PasswordSettings } from '@/utils/dist/shared/models/vault';
import { CreatePasswordGenerator } from '@/utils/dist/shared/password-generator';
interface IPasswordFieldProps {
id: string;
label: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
error?: string;
showPassword?: boolean;
onShowPasswordChange?: (show: boolean) => void;
}
/**
* Password field component with inline length slider and advanced configuration.
*/
const PasswordField: React.FC<IPasswordFieldProps> = ({
id,
label,
value,
onChange,
placeholder,
error,
showPassword: controlledShowPassword,
onShowPasswordChange
}) => {
const { t } = useTranslation();
const dbContext = useDb();
const [internalShowPassword, setInternalShowPassword] = useState(false);
const [showConfigDialog, setShowConfigDialog] = useState(false);
const [currentSettings, setCurrentSettings] = useState<PasswordSettings | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
// Use controlled or uncontrolled showPassword state
const showPassword = controlledShowPassword !== undefined ? controlledShowPassword : internalShowPassword;
/**
* Set the showPassword state.
*/
const setShowPassword = useCallback((show: boolean): void => {
if (controlledShowPassword !== undefined) {
onShowPasswordChange?.(show);
} else {
setInternalShowPassword(show);
}
}, [controlledShowPassword, onShowPasswordChange]);
// Load password settings from database
useEffect(() => {
/**
* Load password settings from the database.
*/
const loadSettings = async (): Promise<void> => {
try {
if (dbContext.sqliteClient) {
const settings = dbContext.sqliteClient.getPasswordSettings();
setCurrentSettings(settings);
setIsLoaded(true);
}
} catch (error) {
console.error('Error loading password settings:', error);
}
};
void loadSettings();
}, [dbContext.sqliteClient]);
const generatePassword = useCallback((settings: PasswordSettings) => {
try {
const passwordGenerator = CreatePasswordGenerator(settings);
const password = passwordGenerator.generateRandomPassword();
onChange(password);
setShowPassword(true);
} catch (error) {
console.error('Error generating password:', error);
}
}, [onChange, setShowPassword]);
const handleLengthChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (!currentSettings) {
return;
}
const length = parseInt(e.target.value, 10);
const newSettings = { ...currentSettings, Length: length };
setCurrentSettings(newSettings);
// Always generate password when length changes
generatePassword(newSettings);
}, [currentSettings, generatePassword]);
const handleRegeneratePassword = useCallback(() => {
if (!currentSettings) {
return;
}
generatePassword(currentSettings);
}, [generatePassword, currentSettings]);
const handleConfiguredPassword = useCallback((password: string) => {
onChange(password);
setShowPassword(true);
}, [onChange, setShowPassword]);
const handleAdvancedSettingsChange = useCallback((newSettings: PasswordSettings) => {
setCurrentSettings(newSettings);
}, []);
const togglePasswordVisibility = useCallback(() => {
setShowPassword(!showPassword);
}, [showPassword, setShowPassword]);
const openConfigDialog = useCallback(() => {
setShowConfigDialog(true);
}, []);
// Don't render until settings are loaded
if (!currentSettings || !isLoaded) {
return (
<div className="space-y-2">
<label htmlFor={id} className="block text-sm font-medium text-gray-900 dark:text-white">
{label}
</label>
<div className="animate-pulse bg-gray-200 dark:bg-gray-700 h-10 rounded-lg"></div>
</div>
);
}
return (
<div className="space-y-2">
{/* Label */}
<label htmlFor={id} className="block text-sm font-medium text-gray-900 dark:text-white">
{label}
</label>
{/* Password Input with Buttons */}
<div className="flex">
<div className="relative flex-grow">
<input
type={showPassword ? 'text' : 'password'}
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="outline-0 text-sm shadow-sm border border-gray-300 bg-gray-50 text-gray-900 sm:text-sm rounded-l-lg block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div className="flex">
{/* Show/Hide Password Button */}
<button
type="button"
onClick={togglePasswordVisibility}
className="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium text-sm dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
title={showPassword ? t('common.hidePassword') : t('common.showPassword')}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{showPassword ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
) : (
<>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</>
)}
</svg>
</button>
{/* Generate Password Button */}
<button
type="button"
onClick={handleRegeneratePassword}
className="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-r-lg text-sm border-l border-gray-300 dark:border-gray-700 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
title={t('credentials.generateRandomPassword')}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</button>
</div>
</div>
{/* Inline Password Length Slider */}
<div className="pt-2">
<div className="flex items-center justify-between mb-2">
<label htmlFor={`${id}-length`} className="text-sm font-medium text-gray-700 dark:text-gray-300">
{t('credentials.passwordLength')}
</label>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400 font-mono">
{currentSettings.Length}
</span>
<button
type="button"
onClick={openConfigDialog}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
title={t('credentials.changePasswordComplexity')}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</button>
</div>
</div>
<input
type="range"
id={`${id}-length`}
min="8"
max="64"
value={currentSettings.Length}
onChange={handleLengthChange}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
{/* Error Message */}
{error && (
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
)}
{/* Advanced Configuration Dialog */}
<PasswordConfigDialog
isOpen={showConfigDialog}
onClose={() => setShowConfigDialog(false)}
onSave={handleConfiguredPassword}
onSettingsChange={handleAdvancedSettingsChange}
initialSettings={currentSettings}
/>
</div>
);
};
export default PasswordField;

View File

@@ -0,0 +1,78 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
interface IUsernameFieldProps {
id: string;
label: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
error?: string;
onRegenerate: () => void;
}
/**
* Username field component with regenerate functionality.
*/
const UsernameField: React.FC<IUsernameFieldProps> = ({
id,
label,
value,
onChange,
placeholder,
error,
onRegenerate
}) => {
const { t } = useTranslation();
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
}, [onChange]);
const handleRegenerate = useCallback(() => {
onRegenerate();
}, [onRegenerate]);
return (
<div className="space-y-2">
{/* Label */}
<label htmlFor={id} className="block text-sm font-medium text-gray-900 dark:text-white">
{label}
</label>
{/* Username Input with Button */}
<div className="flex">
<div className="relative flex-grow">
<input
type="text"
id={id}
value={value}
onChange={handleInputChange}
placeholder={placeholder}
className="outline-0 text-sm shadow-sm bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-l-lg block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white"
/>
</div>
<div className="flex">
{/* Generate Username Button */}
<button
type="button"
onClick={handleRegenerate}
className="px-3 text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-r-lg text-sm dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
title={t('credentials.generateRandomUsername')}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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>
</button>
</div>
</div>
{/* Error Message */}
{error && (
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
)}
</div>
);
};
export default UsernameField;

View File

@@ -1,41 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
/**
* Global state change handler component which listens for global state changes and e.g. redirects user to login
* page if login state changes.
*/
const GlobalStateChangeHandler: React.FC = () => {
const authContext = useAuth();
const navigate = useNavigate();
const lastLoginState = useRef(authContext.isLoggedIn);
const initialRender = useRef(true);
/**
* Listen for auth logged in changes and redirect to home page if logged in state changes to handle logins and logouts.
*/
useEffect(() => {
// Only navigate when auth state is different from the last state we acted on.
if (lastLoginState.current !== authContext.isLoggedIn) {
lastLoginState.current = authContext.isLoggedIn;
/**
* Skip the first auth state change to avoid redirecting when popup opens for the first time
* which already causes the auth state to change from false to true.
*/
if (initialRender.current) {
initialRender.current = false;
return;
}
// Redirect to home page if logged in state changes.
navigate('/');
}
}, [authContext.isLoggedIn]); // eslint-disable-line react-hooks/exhaustive-deps
return null;
};
export default GlobalStateChangeHandler;

View File

@@ -8,7 +8,10 @@ export enum HeaderIconType {
RELOAD = 'reload',
EXTERNAL_LINK = 'external_link',
SAVE = 'save',
PLUS = 'plus'
PLUS = 'plus',
TAB = 'tab',
EYE = 'eye',
EYE_OFF = 'eye_off'
}
type HeaderIconProps = {
@@ -130,19 +133,7 @@ export const HeaderIcon: React.FC<HeaderIconProps> = ({ type, className = 'w-5 h
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"
d="M5 13l4 4L19 7"
/>
</svg>
),
@@ -156,6 +147,66 @@ export const HeaderIcon: React.FC<HeaderIconProps> = ({ type, className = 'w-5 h
>
<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>
),
[HeaderIconType.EYE]: (
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
),
[HeaderIconType.EYE_OFF]: (
<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="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
)
};

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,17 +1,14 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import { useDb } from '@/entrypoints/popup/context/DbContext';
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');
@@ -36,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;
}
@@ -61,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-sm mt-1">{t('menu.credentials')}</span>
</button>
<button
onClick={() => handleTabChange('emails')}
@@ -72,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-sm mt-1">{t('menu.emails')}</span>
</button>
<button
onClick={() => handleTabChange('settings')}
@@ -84,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-sm mt-1">{t('menu.settings')}</span>
</button>
</div>
</div>

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { ClipboardCountdownBar } from '@/entrypoints/popup/components/ClipboardCountdownBar';
import BottomNav from '@/entrypoints/popup/components/Layout/BottomNav';
import Header from '@/entrypoints/popup/components/Layout/Header';
/**
* Route configuration type.
*/
type RouteConfig = {
path: string;
element: React.ReactNode;
showBackButton?: boolean;
title?: string;
};
/**
* DefaultLayout props.
*/
type DefaultLayoutProps = {
routes: RouteConfig[];
headerButtons: React.ReactNode;
message?: string | null;
children?: React.ReactNode;
};
/**
* DefaultLayout - Standard layout with full header, footer navigation, and complete UI.
* This is the main layout used for most pages in the extension.
*/
const DefaultLayout: React.FC<DefaultLayoutProps> = ({ routes, headerButtons, message, children }) => {
return (
<div className="min-h-screen min-w-[350px] bg-white dark:bg-gray-900 flex flex-col max-h-[600px]">
<ClipboardCountdownBar />
<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="px-4 pb-4 pt-2 mb-16">
{message && (
<p className="text-red-500 mb-4">{message}</p>
)}
{children || (
<Routes>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
element={route.element}
/>
))}
</Routes>
)}
</div>
</main>
<BottomNav />
</div>
);
};
export default DefaultLayout;

View File

@@ -1,7 +1,9 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
import Logo from '@/entrypoints/popup/components/Logo';
import { useApp } from '@/entrypoints/popup/context/AppContext';
/**
* Header props.
@@ -22,7 +24,8 @@ const Header: React.FC<HeaderProps> = ({
routes = [],
rightButtons
}) => {
const authContext = useAuth();
const { t } = useTranslation();
const app = useApp();
const navigate = useNavigate();
const location = useLocation();
@@ -45,8 +48,13 @@ const Header: React.FC<HeaderProps> = ({
* Handle logo click.
*/
const logoClick = () : void => {
// Don't navigate if on upgrade page or login page
if (location.pathname === '/upgrade' || location.pathname === '/login' || location.pathname === '/unlock') {
return;
}
// If logged in, navigate to credentials.
if (authContext.isLoggedIn) {
if (app.isLoggedIn) {
navigate('/credentials');
} else {
// If not logged in, navigate to index.
@@ -80,11 +88,15 @@ const Header: React.FC<HeaderProps> = ({
onClick={() => logoClick()}
className="flex items-center hover:opacity-80 transition-opacity"
>
<img src="/assets/images/logo.svg" alt="AliasVault" className="h-8 w-8 mr-2" />
<h1 className="text-gray-900 dark:text-white text-xl font-bold">AliasVault</h1>
<Logo
width={125}
height={40}
showText={true}
className="text-gray-900 dark:text-white"
/>
{/* Hide beta badge on Safari as it's not allowed to show non-production badges */}
{!import.meta.env.SAFARI && (
<span className="text-primary-500 text-[10px] ml-1 font-normal">BETA</span>
<span className="text-primary-500 text-[10px] font-normal">BETA</span>
)}
</button>
</div>
@@ -93,17 +105,20 @@ const Header: React.FC<HeaderProps> = ({
<div className="flex-grow" />
<div className="flex items-center gap-2">
{!authContext.isLoggedIn ? (
<button
id="settings"
onClick={(handleSettings)}
className="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
>
<span className="sr-only">Settings</span>
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
</svg>
</button>
{!app.isLoggedIn ? (
<>
{rightButtons}
<button
id="settings"
onClick={(handleSettings)}
className="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"
>
<span className="sr-only">{t('common.settings')}</span>
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
</svg>
</button>
</>
) : (
rightButtons
)}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import Logo from '@/entrypoints/popup/components/Logo';
/**
* PasskeyLayout - Minimal layout for passkey create/authenticate pages.
* Shows only the AliasVault logo header, no navigation, no footer.
*/
const PasskeyLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="min-h-screen min-w-[350px] bg-white dark:bg-gray-900 flex flex-col max-h-[600px]">
{/* Minimal header with just logo */}
<header className="fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<div className="flex items-center justify-center h-16 px-4">
<Logo
width={125}
height={40}
showText={true}
className="text-gray-900 dark:text-white"
/>
{/* Hide beta badge on Safari as it's not allowed to show non-production badges */}
{!import.meta.env.SAFARI && (
<span className="text-primary-500 text-[10px] font-normal">BETA</span>
)}
</div>
</header>
{/* Main content without footer padding */}
<main
className="flex-1 overflow-y-auto bg-gray-100 dark:bg-gray-900"
style={{
paddingTop: '64px',
}}
>
<div className="p-4">
{children}
</div>
</main>
</div>
);
};
export default PasskeyLayout;

View File

@@ -1,52 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/entrypoints/popup/context/AuthContext';
/**
* User menu component.
*/
const UserMenu: React.FC = () => {
const authContext = useAuth();
const navigate = useNavigate();
/**
* Handle logout.
*/
const handleLogout = async () : Promise<void> => {
await authContext.logout();
navigate('/');
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex items-center space-x-3">
<div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center">
<span className="text-primary-600 dark:text-primary-400 text-lg font-medium">
{authContext.username?.[0]?.toUpperCase() || '?'}
</span>
</div>
</div>
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{authContext.username}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
Logged in
</p>
</div>
</div>
<button
onClick={handleLogout}
className="px-4 py-2 text-sm font-medium text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
>
Logout
</button>
</div>
</div>
);
};
export default UserMenu;

View File

@@ -1,30 +1,23 @@
import React, { useState, useEffect } from 'react';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AppInfo } from '@/utils/AppInfo';
import { storage } from '#imports';
import { useApiUrl } from '@/entrypoints/popup/utils/ApiUrlUtility';
/**
* Component for displaying the login server information.
*/
const LoginServerInfo: React.FC = () => {
const [baseUrl, setBaseUrl] = useState<string>('');
const { loadApiUrl, getDisplayUrl } = useApiUrl();
const navigate = useNavigate();
const { t } = useTranslation();
useEffect(() => {
/**
* Loads the base URL for the login server.
*/
const loadApiUrl = async () : Promise<void> => {
const apiUrl = await storage.getItem('local:apiUrl') as string;
setBaseUrl(apiUrl ?? AppInfo.DEFAULT_API_URL);
};
loadApiUrl();
}, []);
const isDefaultServer = !baseUrl || baseUrl === AppInfo.DEFAULT_API_URL;
const displayUrl = isDefaultServer ? 'aliasvault.net' : new URL(baseUrl).hostname;
}, [loadApiUrl]);
/**
* Handles the click event for the login server information.
@@ -34,14 +27,14 @@ const LoginServerInfo: React.FC = () => {
};
return (
<div className="text-xs text-gray-600 dark:text-gray-400 mb-4">
(Connecting to{' '}
<div className="text-sm text-gray-600 dark:text-gray-400 mb-4">
({t('auth.connectingTo')}{' '}
<button
onClick={handleClick}
type="button"
className="text-orange-500 hover:text-orange-600 dark:text-orange-400 dark:hover:text-orange-500 underline"
>
{displayUrl}
{getDisplayUrl()}
</button>)
</div>
);

View File

@@ -0,0 +1,71 @@
import React from 'react';
type LogoProps = {
className?: string;
width?: number;
height?: number;
showText?: boolean;
color?: string;
}
/**
* Logo component.
*/
const Logo: React.FC<LogoProps> = ({
className = '',
width = 200,
height = 50,
showText = true,
color = 'currentColor'
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
version="1.1"
viewBox="0 0 2000 500"
width={width}
height={height}
className={className}
>
{/* Logo mark */}
<path
d="m459.87 294.95c0.016205 5.4005 0.03241 10.801-0.35022 16.873-1.111 6.3392-1.1941 12.173-2.6351 17.649-10.922 41.508-36.731 69.481-77.351 83.408-7.2157 2.4739-14.972 3.3702-22.479 4.995-23.629 0.042205-47.257 0.11453-70.886 0.12027-46.762 0.011322-93.523-0.01416-140.95-0.43411-8.59-2.0024-16.766-2.8352-24.398-5.3326-21.595-7.0666-39.523-19.656-53.708-37.552-10.227-12.903-17.579-27.17-21.28-43.221-1.475-6.3967-2.4711-12.904-3.6852-19.361-0.051849-5.747-0.1037-11.494 0.26915-17.886 4.159-42.973 27.68-71.638 63.562-92.153 0-0.70761-0.001961-1.6988 3.12e-4 -2.69 0.022484-9.8293-1.3071-19.894 0.35664-29.438 3.2391-18.579 11.08-35.272 23.763-49.773 12.098-13.832 26.457-23.989 43.609-30.029 7.813-2.7512 16.14-4.0417 24.234-5.9948 7.392-0.025734 14.784-0.05146 22.835 0.32253 4.1959 0.95392 7.7946 1.2538 11.258 2.1053 17.16 4.2192 32.287 12.176 45.469 24.104 2.2558 2.0411 4.372 6.6241 9.621 3.868 16.839-8.8419 34.718-11.597 53.603-8.594 16.791 2.6699 31.602 9.4308 44.236 20.636 11.531 10.227 19.84 22.841 25.393 37.236 6.3436 16.445 10.389 33.163 6.0798 49.389 7.9587 8.9321 15.807 16.704 22.421 25.414 9.162 12.065 15.33 25.746 18.144 40.776 0.97046 5.1848 1.9111 10.375 2.8654 15.563m-71.597 71.012c5.5615-5.2284 12.002-9.7986 16.508-15.817 10.474-13.992 14.333-29.916 11.288-47.446-2.2496-12.95-8.1973-24.076-17.243-33.063-12.746-12.663-28.865-18.614-46.786-18.569-69.912 0.17712-139.82 0.56831-209.74 0.96176-15.922 0.089599-29.168 7.4209-39.685 18.296-14.45 14.944-20.408 33.343-16.655 54.368 2.2763 12.754 8.2167 23.748 17.158 32.66 13.299 13.255 30.097 18.653 48.728 18.651 59.321-0.005188 118.64 0.042358 177.96-0.046601 9.5912-0.014374 19.181-0.86588 28.773-0.88855 10.649-0.025146 19.978-3.825 29.687-9.1074z"
fill="#EEC170"
/>
<path
d="m162.77 293c15.654 4.3883 20.627 22.967 10.304 34.98-5.3104 6.1795-14.817 8.3208-24.278 5.0472-7.0723-2.4471-12.332-10.362-12.876-17.933-1.0451-14.542 11.089-23.176 21.705-23.046 1.5794 0.019287 3.1517 0.61566 5.1461 0.95184z"
fill="#EEC170"
/>
<path
d="m227.18 293.64c7.8499 2.3973 11.938 8.2143 13.524 15.077 1.8591 8.0439-0.44817 15.706-7.1588 21.121-6.7633 5.4572-14.417 6.8794-22.578 3.1483-8.2972-3.7933-12.836-10.849-12.736-19.438 0.1687-14.497 14.13-25.368 28.948-19.908z"
fill="#EEC170"
/>
<path
d="m261.57 319.07c-2.495-14.418 4.6853-22.603 14.596-26.108 9.8945-3.4995 23.181 3.4303 26.267 13.779 4.6504 15.591-7.1651 29.064-21.665 28.161-8.5254-0.53088-17.202-6.5094-19.198-15.831z"
fill="#EEC170"
/>
<path
d="m336.91 333.41c-9.0175-4.2491-15.337-14.349-13.829-21.682 3.0825-14.989 13.341-20.304 23.018-19.585 10.653 0.79141 17.93 7.407 19.765 17.547 1.9588 10.824-4.1171 19.939-13.494 23.703-5.272 2.1162-10.091 1.5086-15.46 0.017883z"
fill="#EEC170"
/>
{/* Wordmark - only show if showText is true */}
{showText && (
<text
x="550"
y="355"
fontFamily="Arial, Helvetica, sans-serif"
fontWeight="700"
fontSize="290"
letterSpacing="-7"
fill={color}
>
AliasVault
</text>
)}
</svg>
);
};
export default Logo;

Some files were not shown because too many files have changed in this diff Show More