mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-01-03 03:28:25 -05:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14fe3463c6 | ||
|
|
721f2a6c81 | ||
|
|
9dd1aec4d5 | ||
|
|
23ec8f8634 | ||
|
|
c3332649a2 | ||
|
|
d56c3c1864 | ||
|
|
11700d80df | ||
|
|
d0022924e4 | ||
|
|
054fe401b4 | ||
|
|
2fbf75e6ec | ||
|
|
148650811f | ||
|
|
aed386e0b7 | ||
|
|
2ebf98671e | ||
|
|
842c8b212c | ||
|
|
f07520ed7e | ||
|
|
55009512ed | ||
|
|
f272281fb9 | ||
|
|
6f4b0614cd | ||
|
|
8dec47b5e4 | ||
|
|
edcfc2f82f | ||
|
|
26e28c84bc | ||
|
|
be8361111b | ||
|
|
fe504765a5 | ||
|
|
56ea00e1dc | ||
|
|
331921efea | ||
|
|
e23a1397c9 | ||
|
|
11d3651f10 | ||
|
|
6ab01ddbfd | ||
|
|
e746d2789a | ||
|
|
8af4f45fbd | ||
|
|
93e0f2bdba | ||
|
|
1a3024609a | ||
|
|
88ccf2b53e | ||
|
|
410f7fcf41 | ||
|
|
e8ca9f2c34 | ||
|
|
0a8e1947bb | ||
|
|
5204249db6 | ||
|
|
430741a569 | ||
|
|
4d807840a6 | ||
|
|
68e699710a | ||
|
|
7d8ad8ac73 | ||
|
|
3eb6a78772 | ||
|
|
e0c3ea0918 | ||
|
|
7c52c7ed47 | ||
|
|
472d957f3f | ||
|
|
44951e79ec | ||
|
|
b2e28dced6 | ||
|
|
07a7dc7de3 | ||
|
|
6fdef88c4f | ||
|
|
e6087db660 | ||
|
|
93fb1960d3 | ||
|
|
425b4d5152 | ||
|
|
c8cf64cbbc | ||
|
|
75df6900a3 | ||
|
|
413dc5fd0a | ||
|
|
b335047310 | ||
|
|
ad67bb80c7 | ||
|
|
7c14c2a3f9 | ||
|
|
44c16f4cd1 | ||
|
|
4bf103d261 | ||
|
|
68b19b9545 | ||
|
|
e6d51ca1b1 | ||
|
|
13e7f1ddd9 | ||
|
|
e5d342b961 | ||
|
|
a58426abcb | ||
|
|
819385bc0a | ||
|
|
c0cbc0be7b | ||
|
|
40686f97e0 | ||
|
|
f10fb989ce | ||
|
|
ca85c04c75 | ||
|
|
fd9eb9d653 | ||
|
|
0a70902d69 | ||
|
|
eee41df9a4 | ||
|
|
d563d6d448 | ||
|
|
db1474397c | ||
|
|
e881f9486a | ||
|
|
645fd605e6 | ||
|
|
254f0a1212 | ||
|
|
64d29ebcd4 | ||
|
|
df0d74595f | ||
|
|
2131e4922c | ||
|
|
d846825b84 | ||
|
|
2a902eeb97 | ||
|
|
d9a6dfab03 | ||
|
|
3da99ed4b1 | ||
|
|
5414f40c98 | ||
|
|
6c561e8ece | ||
|
|
3654b12cd7 | ||
|
|
266e7b36d4 | ||
|
|
cbe9978367 | ||
|
|
6b949bcb2f | ||
|
|
6a4fbb9193 | ||
|
|
c459a48927 | ||
|
|
d3f132df63 | ||
|
|
b5edc6ef76 | ||
|
|
4e0db87bc3 | ||
|
|
62cc0e7c2b | ||
|
|
dad3a6fa2c | ||
|
|
9560d550e4 | ||
|
|
0930ae03cd | ||
|
|
23c9bf2fc9 | ||
|
|
6ebaf8e1b8 |
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -27,7 +27,33 @@ on:
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
# Guard job to prevent releases from main branch
|
||||
valid-release:
|
||||
if: github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check tag target
|
||||
run: |
|
||||
BRANCHES=$(git branch -r --contains $GITHUB_SHA)
|
||||
|
||||
echo "Tag is contained in:"
|
||||
echo "$BRANCHES"
|
||||
|
||||
if ! echo "$BRANCHES" | grep -q "origin/release/"; then
|
||||
echo "❌ Releases must come from a release/* branch, please recreate the release from a release branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Tag is on a release branch"
|
||||
|
||||
upload-install-script:
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -43,7 +69,8 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-chrome-extension:
|
||||
if: github.event_name == 'release' || inputs.build_browser_extensions
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -58,7 +85,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-firefox-extension:
|
||||
if: github.event_name == 'release' || inputs.build_browser_extensions
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -73,7 +101,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-edge-extension:
|
||||
if: github.event_name == 'release' || inputs.build_browser_extensions
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -88,7 +117,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-android-release:
|
||||
if: github.event_name == 'release' || inputs.build_mobile_apps
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_mobile_apps)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -107,7 +137,8 @@ jobs:
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
|
||||
build-and-push-docker-multi-container:
|
||||
if: github.event_name == 'release' || inputs.build_multi_container
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_multi_container)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -372,7 +403,8 @@ jobs:
|
||||
annotations: ${{ steps.installcli-meta.outputs.annotations }}
|
||||
|
||||
build-and-push-docker-all-in-one:
|
||||
if: github.event_name == 'release' || inputs.build_all_in_one
|
||||
needs: [valid-release]
|
||||
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_all_in_one)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -1 +1 @@
|
||||
25
|
||||
26
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
-alpha
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.25.0
|
||||
0.26.0-alpha
|
||||
|
||||
34
apps/browser-extension/package-lock.json
generated
34
apps/browser-extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "aliasvault-browser-extension",
|
||||
"version": "0.25.0",
|
||||
"version": "0.26.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "aliasvault-browser-extension",
|
||||
"version": "0.25.0",
|
||||
"version": "0.26.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
@@ -229,6 +229,7 @@
|
||||
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -607,6 +608,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -630,6 +632,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -2134,6 +2137,7 @@
|
||||
"integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -2215,6 +2219,7 @@
|
||||
"integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.32.1",
|
||||
"@typescript-eslint/types": "8.32.1",
|
||||
@@ -2843,6 +2848,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3550,6 +3556,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001716",
|
||||
"electron-to-chromium": "^1.5.149",
|
||||
@@ -5105,6 +5112,7 @@
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -5275,6 +5283,7 @@
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.8",
|
||||
@@ -6477,6 +6486,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -7332,6 +7342,7 @@
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -7378,6 +7389,7 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -8251,9 +8263,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"dev": true,
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
@@ -9058,6 +9070,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -9609,6 +9622,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -9618,6 +9632,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -9630,6 +9645,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz",
|
||||
"integrity": "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -9979,6 +9995,7 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
|
||||
"integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
@@ -11111,6 +11128,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -11389,6 +11407,7 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -11536,6 +11555,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.2.2"
|
||||
},
|
||||
@@ -11663,6 +11683,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -11793,6 +11814,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -11805,6 +11827,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz",
|
||||
"integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "3.1.3",
|
||||
"@vitest/mocker": "3.1.3",
|
||||
@@ -12376,6 +12399,7 @@
|
||||
"integrity": "sha512-DqqHc/5COs8GR21ii99bANXf/mu6zuDpiXFV1YKNsqO5/PvkrCx5arY0aVPL5IATsuacAnNzdj4eMc1qbzS53Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@1natsu/wait-element": "^4.1.2",
|
||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "aliasvault-browser-extension",
|
||||
"description": "AliasVault Browser Extension",
|
||||
"private": true,
|
||||
"version": "0.25.0",
|
||||
"version": "0.26.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:chrome": "wxt -b chrome",
|
||||
|
||||
@@ -463,7 +463,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500900;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -476,7 +476,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@@ -495,7 +495,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "AliasVault Extension/AliasVault_Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500900;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -508,7 +508,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@@ -532,7 +532,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2500900;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -547,7 +547,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@@ -571,7 +571,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2500900;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -586,7 +586,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { handleClipboardCopied, handleCancelClipboardClear, handleGetClipboardCl
|
||||
import { setupContextMenus } from '@/entrypoints/background/ContextMenu';
|
||||
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, handleGetDefaultEmailDomain, handleGetDefaultIdentitySettings, handleGetEncryptionKey, handleGetEncryptionKeyDerivationParams, handleGetPasswordSettings, handleGetPersistedFormValues, handleGetVault, handlePersistFormValues, handleStoreEncryptionKey, handleStoreEncryptionKeyDerivationParams, handleStoreVault, handleSyncVault, handleUploadVault } from '@/entrypoints/background/VaultMessageHandler';
|
||||
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";
|
||||
@@ -28,6 +28,8 @@ export default defineBackground({
|
||||
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());
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { handleGetEncryptionKey } from '@/entrypoints/background/VaultMessageHandler';
|
||||
import { extractDomain, extractRootDomain } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
|
||||
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 {
|
||||
|
||||
@@ -18,6 +18,21 @@ import { WebApiService } from '@/utils/WebApiService';
|
||||
|
||||
import { t } from '@/i18n/StandaloneI18n';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -58,8 +73,6 @@ export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, i
|
||||
hasPendingMigrations
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking pending migrations:', 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)
|
||||
@@ -92,6 +105,10 @@ 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.
|
||||
@@ -155,7 +172,7 @@ export async function handleStoreEncryptionKeyDerivationParams(
|
||||
*/
|
||||
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) {
|
||||
@@ -175,6 +192,10 @@ export async function handleSyncVault(
|
||||
{ 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 };
|
||||
@@ -240,6 +261,10 @@ export function handleClearVault(
|
||||
'session:vaultRevisionNumber'
|
||||
]);
|
||||
|
||||
// Clear cached client since vault was cleared
|
||||
cachedSqliteClient = null;
|
||||
cachedVaultBlob = null;
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -264,6 +289,100 @@ export async function handleGetCredentials(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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') };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an identity.
|
||||
*/
|
||||
@@ -405,13 +524,11 @@ 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) {
|
||||
@@ -486,10 +603,17 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
|
||||
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;
|
||||
|
||||
@@ -510,7 +634,7 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
|
||||
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)
|
||||
@@ -525,6 +649,7 @@ 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;
|
||||
@@ -533,15 +658,24 @@ async function createVaultSqliteClient() : Promise<SqliteClient> {
|
||||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { sendMessage } from 'webext-bridge/content-script';
|
||||
|
||||
import { filterCredentials, AutofillMatchingMode } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
import { fillCredential } from '@/entrypoints/contentScript/Form';
|
||||
|
||||
import { DISABLED_SITES_KEY, TEMPORARY_DISABLED_SITES_KEY, GLOBAL_AUTOFILL_POPUP_ENABLED_KEY, VAULT_LOCKED_DISMISS_UNTIL_KEY, AUTOFILL_MATCHING_MODE_KEY, CUSTOM_EMAIL_HISTORY_KEY, CUSTOM_USERNAME_HISTORY_KEY } from '@/utils/Constants';
|
||||
import { AutofillMatchingMode } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
import { CreateIdentityGenerator } from '@/utils/dist/shared/identity-generator';
|
||||
import type { Credential } from '@/utils/dist/shared/models/vault';
|
||||
import { CreatePasswordGenerator, PasswordGenerator, PasswordSettings } from '@/utils/dist/shared/password-generator';
|
||||
@@ -49,7 +49,14 @@ export function openAutofillPopup(input: HTMLInputElement, container: HTMLElemen
|
||||
document.addEventListener('keydown', handleEnterKey);
|
||||
|
||||
(async () : Promise<void> => {
|
||||
const response = await sendMessage('GET_CREDENTIALS', { }, 'background') as CredentialsResponse;
|
||||
// Load autofill matching mode setting to send to background for filtering
|
||||
const matchingMode = await storage.getItem(AUTOFILL_MATCHING_MODE_KEY) as AutofillMatchingMode ?? AutofillMatchingMode.DEFAULT;
|
||||
|
||||
const response = await sendMessage('GET_FILTERED_CREDENTIALS', {
|
||||
currentUrl: window.location.href,
|
||||
pageTitle: document.title,
|
||||
matchingMode: matchingMode
|
||||
}, 'background') as CredentialsResponse;
|
||||
|
||||
if (response.success) {
|
||||
await createAutofillPopup(input, response.credentials, container);
|
||||
@@ -182,22 +189,12 @@ export async function createAutofillPopup(input: HTMLInputElement, credentials:
|
||||
credentialList.className = 'av-credential-list';
|
||||
popup.appendChild(credentialList);
|
||||
|
||||
// Add initial credentials
|
||||
// Add initial credentials (already filtered by background script for performance)
|
||||
if (!credentials) {
|
||||
credentials = [];
|
||||
}
|
||||
|
||||
// Load autofill matching mode setting
|
||||
const matchingMode = await storage.getItem(AUTOFILL_MATCHING_MODE_KEY) as AutofillMatchingMode ?? AutofillMatchingMode.DEFAULT;
|
||||
|
||||
const filteredCredentials = filterCredentials(
|
||||
credentials,
|
||||
window.location.href,
|
||||
document.title,
|
||||
matchingMode
|
||||
);
|
||||
|
||||
updatePopupContent(filteredCredentials, credentialList, input, rootContainer, noMatchesText);
|
||||
updatePopupContent(credentials, credentialList, input, rootContainer, noMatchesText);
|
||||
|
||||
// Add divider
|
||||
const divider = document.createElement('div');
|
||||
@@ -549,62 +546,41 @@ export async function createVaultLockedPopup(input: HTMLInputElement, rootContai
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle popup search input by filtering credentials based on the search term.
|
||||
* Handle popup search input - searches entire vault when user types.
|
||||
* When empty, shows the initially URL-filtered credentials.
|
||||
* When user types, searches ALL credentials in vault (not just the pre-filtered set).
|
||||
*
|
||||
* @param searchInput - The search input element
|
||||
* @param initialCredentials - The initially URL-filtered credentials to show when search is empty
|
||||
* @param rootContainer - The root container element
|
||||
* @param searchTimeout - Timeout for debouncing search
|
||||
* @param credentialList - The credential list element to update
|
||||
* @param input - The input field that triggered the popup
|
||||
* @param noMatchesText - Text to show when no matches found
|
||||
*/
|
||||
async function handleSearchInput(searchInput: HTMLInputElement, credentials: Credential[], rootContainer: HTMLElement, searchTimeout: NodeJS.Timeout | null, credentialList: HTMLElement | null, input: HTMLInputElement, noMatchesText?: string) : Promise<void> {
|
||||
async function handleSearchInput(searchInput: HTMLInputElement, initialCredentials: Credential[], rootContainer: HTMLElement, searchTimeout: NodeJS.Timeout | null, credentialList: HTMLElement | null, input: HTMLInputElement, noMatchesText?: string) : Promise<void> {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
|
||||
// Ensure we have unique credentials
|
||||
const uniqueCredentials = Array.from(new Map(credentials.map(cred => [cred.Id, cred])).values());
|
||||
let filteredCredentials;
|
||||
const searchTerm = searchInput.value.trim();
|
||||
|
||||
if (searchTerm === '') {
|
||||
// Load autofill matching mode setting
|
||||
const matchingMode = await storage.getItem(AUTOFILL_MATCHING_MODE_KEY) as AutofillMatchingMode ?? AutofillMatchingMode.DEFAULT;
|
||||
|
||||
// If search is empty, use original URL-based filtering
|
||||
filteredCredentials = filterCredentials(
|
||||
uniqueCredentials,
|
||||
window.location.href,
|
||||
document.title,
|
||||
matchingMode
|
||||
).sort((a, b) => {
|
||||
// First compare by service name
|
||||
const serviceNameComparison = (a.ServiceName ?? '').localeCompare(b.ServiceName ?? '');
|
||||
if (serviceNameComparison !== 0) {
|
||||
return serviceNameComparison;
|
||||
}
|
||||
|
||||
// If service names are equal, compare by username/nickname
|
||||
return (a.Username ?? '').localeCompare(b.Username ?? '');
|
||||
});
|
||||
// If search is empty, show the initially URL-filtered credentials
|
||||
updatePopupContent(initialCredentials, credentialList, input, rootContainer, noMatchesText);
|
||||
} else {
|
||||
// Otherwise filter based on search term
|
||||
filteredCredentials = uniqueCredentials.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) => {
|
||||
// First compare by service name
|
||||
const serviceNameComparison = (a.ServiceName ?? '').localeCompare(b.ServiceName ?? '');
|
||||
if (serviceNameComparison !== 0) {
|
||||
return serviceNameComparison;
|
||||
}
|
||||
// Search in full vault with search term
|
||||
const response = await sendMessage('GET_SEARCH_CREDENTIALS', {
|
||||
searchTerm: searchTerm
|
||||
}, 'background') as CredentialsResponse;
|
||||
|
||||
// If service names are equal, compare by username/nickname
|
||||
return (a.Username ?? '').localeCompare(b.Username ?? '');
|
||||
});
|
||||
if (response.success && response.credentials) {
|
||||
updatePopupContent(response.credentials, credentialList, input, rootContainer, noMatchesText);
|
||||
} else {
|
||||
// On error, fallback to showing initial filtered credentials
|
||||
updatePopupContent(initialCredentials, credentialList, input, rootContainer, noMatchesText);
|
||||
}
|
||||
}
|
||||
|
||||
// Update popup content with filtered results
|
||||
updatePopupContent(filteredCredentials, credentialList, input, rootContainer, noMatchesText);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { sendMessage } from 'webext-bridge/popup';
|
||||
|
||||
import { extractDomain, extractRootDomain } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
import Button from '@/entrypoints/popup/components/Button';
|
||||
import PasskeyBypassDialog from '@/entrypoints/popup/components/Dialogs/PasskeyBypassDialog';
|
||||
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
|
||||
@@ -12,6 +11,7 @@ import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
|
||||
import { useVaultLockRedirect } from '@/entrypoints/popup/hooks/useVaultLockRedirect';
|
||||
|
||||
import { PASSKEY_DISABLED_SITES_KEY } from '@/utils/Constants';
|
||||
import { extractDomain, extractRootDomain } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
import { PasskeyAuthenticator } from '@/utils/passkey/PasskeyAuthenticator';
|
||||
import { PasskeyHelper } from '@/utils/passkey/PasskeyHelper';
|
||||
import type { GetRequest, PasskeyGetCredentialResponse, PendingPasskeyGetRequest, StoredPasskeyRecord } from '@/utils/passkey/types';
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { sendMessage } from 'webext-bridge/popup';
|
||||
|
||||
import { extractDomain, extractRootDomain } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
import Alert from '@/entrypoints/popup/components/Alert';
|
||||
import Button from '@/entrypoints/popup/components/Button';
|
||||
import PasskeyBypassDialog from '@/entrypoints/popup/components/Dialogs/PasskeyBypassDialog';
|
||||
@@ -16,6 +15,7 @@ import { useVaultLockRedirect } from '@/entrypoints/popup/hooks/useVaultLockRedi
|
||||
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
|
||||
|
||||
import { PASSKEY_DISABLED_SITES_KEY } from '@/utils/Constants';
|
||||
import { extractDomain, extractRootDomain } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
import type { Passkey } from '@/utils/dist/shared/models/vault';
|
||||
import { PasskeyAuthenticator } from '@/utils/passkey/PasskeyAuthenticator';
|
||||
import { PasskeyHelper } from '@/utils/passkey/PasskeyHelper';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AutofillMatchingMode } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
|
||||
|
||||
import {
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
TEMPORARY_DISABLED_SITES_KEY,
|
||||
AUTOFILL_MATCHING_MODE_KEY
|
||||
} from '@/utils/Constants';
|
||||
import { AutofillMatchingMode } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
|
||||
import { storage, browser } from "#imports";
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { extractDomain, extractRootDomain } from '@/entrypoints/contentScript/CredentialMatcher';
|
||||
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
|
||||
|
||||
import {
|
||||
PASSKEY_PROVIDER_ENABLED_KEY,
|
||||
PASSKEY_DISABLED_SITES_KEY
|
||||
} from '@/utils/Constants';
|
||||
import { extractDomain, extractRootDomain } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
|
||||
import { storage, browser } from "#imports";
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
import deTranslations from './locales/de.json';
|
||||
import enTranslations from './locales/en.json';
|
||||
import esTranslations from './locales/es.json';
|
||||
import fiTranslations from './locales/fi.json';
|
||||
import frTranslations from './locales/fr.json';
|
||||
import heTranslations from './locales/he.json';
|
||||
import itTranslations from './locales/it.json';
|
||||
import nlTranslations from './locales/nl.json';
|
||||
@@ -26,9 +28,15 @@ export const LANGUAGE_RESOURCES = {
|
||||
en: {
|
||||
translation: enTranslations
|
||||
},
|
||||
es: {
|
||||
translation: esTranslations
|
||||
},
|
||||
fi: {
|
||||
translation: fiTranslations
|
||||
},
|
||||
fr: {
|
||||
translation: frTranslations
|
||||
},
|
||||
he: {
|
||||
translation: heTranslations
|
||||
},
|
||||
@@ -72,12 +80,24 @@ export const AVAILABLE_LANGUAGES: ILanguageConfig[] = [
|
||||
nativeName: 'English',
|
||||
flag: '🇺🇸'
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
name: 'Spanish',
|
||||
nativeName: 'Español',
|
||||
flag: '🇪🇸'
|
||||
},
|
||||
{
|
||||
code: 'fi',
|
||||
name: 'Finnish',
|
||||
nativeName: 'Suomi',
|
||||
flag: '🇫🇮'
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'French',
|
||||
nativeName: 'Français',
|
||||
flag: '🇫🇷'
|
||||
},
|
||||
{
|
||||
code: 'he',
|
||||
name: 'Hebrew',
|
||||
|
||||
@@ -1,441 +1,441 @@
|
||||
{
|
||||
"auth": {
|
||||
"loginTitle": "Log in to AliasVault",
|
||||
"username": "Username or email",
|
||||
"usernamePlaceholder": "name / name@company.com",
|
||||
"loginTitle": "Iniciar sesión en AliasVault",
|
||||
"username": "Nombre de usuario o correo electrónico",
|
||||
"usernamePlaceholder": "nombre / nombre@empresa.com",
|
||||
"password": "Contraseña",
|
||||
"passwordPlaceholder": "Enter your password",
|
||||
"rememberMe": "Remember me",
|
||||
"loginButton": "Log in",
|
||||
"noAccount": "No account yet?",
|
||||
"createVault": "Create new vault",
|
||||
"twoFactorTitle": "Please enter the authentication code from your authenticator app.",
|
||||
"authCode": "Authentication Code",
|
||||
"authCodePlaceholder": "Enter 6-digit code",
|
||||
"verify": "Verify",
|
||||
"twoFactorNote": "Note: if you don't have access to your authenticator device, you can reset your 2FA with a recovery code by logging in via the website.",
|
||||
"masterPassword": "Contraseña maestra",
|
||||
"unlockVault": "Unlock",
|
||||
"unlockWithPin": "Unlock with PIN",
|
||||
"enterPinToUnlock": "Enter your PIN to unlock your vault",
|
||||
"useMasterPassword": "Use Master Password",
|
||||
"unlockTitle": "Unlock Your Vault",
|
||||
"logout": "Logout",
|
||||
"logoutConfirm": "Are you sure you want to logout?",
|
||||
"unlockSuccessTitle": "Your vault is successfully unlocked",
|
||||
"unlockSuccessDescription": "You can now use autofill in login forms in your browser.",
|
||||
"closePopup": "Close this popup",
|
||||
"browseVault": "Browse vault contents",
|
||||
"connectingTo": "Connecting to",
|
||||
"switchAccounts": "Switch accounts?",
|
||||
"loggedIn": "Logged in",
|
||||
"loginWithMobile": "Log in using Mobile App",
|
||||
"unlockWithMobile": "Unlock using Mobile App",
|
||||
"scanQrCode": "Scan this QR code with your AliasVault mobile app to log in and unlock your vault.",
|
||||
"passwordPlaceholder": "Introduzca su contraseña",
|
||||
"rememberMe": "Recordar mis datos",
|
||||
"loginButton": "Iniciar sesión",
|
||||
"noAccount": "¿Sin cuenta todavía?",
|
||||
"createVault": "Crear nueva bóveda",
|
||||
"twoFactorTitle": "Por favor, introduzca el código de autenticación de su aplicación de autenticación.",
|
||||
"authCode": "Código de autenticación",
|
||||
"authCodePlaceholder": "Introduzca código de 6 dígitos",
|
||||
"verify": "Verificar",
|
||||
"twoFactorNote": "Nota: si no tiene acceso a su dispositivo de autenticación, puede restablecer su 2FA con un código de recuperación iniciando sesión a través del sitio web.",
|
||||
"masterPassword": "Contraseña Maestra",
|
||||
"unlockVault": "Desbloquear",
|
||||
"unlockWithPin": "Desbloquear con PIN",
|
||||
"enterPinToUnlock": "Introduzca su PIN para desbloquear su bóveda",
|
||||
"useMasterPassword": "Usar Contraseña Maestra",
|
||||
"unlockTitle": "Desbloquear tu Bóveda",
|
||||
"logout": "Cerrar sesión",
|
||||
"logoutConfirm": "¿Estás seguro de que quieres cerrar sesión?",
|
||||
"unlockSuccessTitle": "Tu bóveda se ha desbloqueado correctamente",
|
||||
"unlockSuccessDescription": "Ahora puede utilizar el autocompletado en los formularios de inicio de sesión en su navegador.",
|
||||
"closePopup": "Cerrar esta ventana emergente",
|
||||
"browseVault": "Navegar en el contenido de la bóveda",
|
||||
"connectingTo": "Conectando con",
|
||||
"switchAccounts": "¿Cambiar de cuenta?",
|
||||
"loggedIn": "Sesión iniciada",
|
||||
"loginWithMobile": "Iniciar sesión con la aplicación móvil",
|
||||
"unlockWithMobile": "Desbloquear con la aplicación móvil",
|
||||
"scanQrCode": "Escanea este código QR con tu aplicación móvil de AliasVault para iniciar sesión y desbloquear tu bóveda.",
|
||||
"errors": {
|
||||
"invalidCode": "Please enter a valid 6-digit authentication code.",
|
||||
"serverError": "Could not reach AliasVault server. Please try again later or contact support if the problem persists.",
|
||||
"wrongPassword": "Incorrect password. Please try again.",
|
||||
"accountLocked": "Account temporarily locked due to too many failed attempts.",
|
||||
"networkError": "Network error. Please check your connection and try again.",
|
||||
"sessionExpired": "Your session has expired. Please log in again.",
|
||||
"mobileLoginRequestExpired": "Mobile login request timed out. Please reload the page and try again."
|
||||
"invalidCode": "Por favor, introduzca un código de autenticación de 6 dígitos válido.",
|
||||
"serverError": "No se pudo llegar al servidor AliasVault. Por favor, inténtelo de nuevo más tarde o póngase en contacto con el soporte si el problema persiste.",
|
||||
"wrongPassword": "Contraseña incorrecta. Por favor, inténtelo de nuevo.",
|
||||
"accountLocked": "Cuenta bloqueada temporalmente debido a demasiados intentos fallidos.",
|
||||
"networkError": "Error de red. Por favor, compruebe su conexión y vuelva a intentarlo.",
|
||||
"sessionExpired": "Su sesión ha caducado. Por favor, inicie sesión de nuevo.",
|
||||
"mobileLoginRequestExpired": "Se ha agotado el tiempo de inicio de sesión del móvil. Por favor, vuelva a cargar la página e inténtelo de nuevo."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
"credentials": "Credentials",
|
||||
"emails": "Emails",
|
||||
"settings": "Settings"
|
||||
"credentials": "Credenciales",
|
||||
"emails": "Correos electrónicos",
|
||||
"settings": "Configuración"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"notice": "Notice",
|
||||
"loading": "Cargando...",
|
||||
"notice": "Aviso",
|
||||
"error": "Error",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"use": "Use",
|
||||
"delete": "Delete",
|
||||
"save": "Save",
|
||||
"or": "Or",
|
||||
"close": "Close",
|
||||
"copied": "Copied!",
|
||||
"openInNewWindow": "Open in new window",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"showPassword": "Show password",
|
||||
"hidePassword": "Hide password",
|
||||
"showDetails": "Show details",
|
||||
"hideDetails": "Hide details",
|
||||
"copyToClipboard": "Copy to clipboard",
|
||||
"loadingEmails": "Loading emails...",
|
||||
"loadingTotpCodes": "Loading TOTP codes...",
|
||||
"attachments": "Attachments",
|
||||
"loadingAttachments": "Loading attachments...",
|
||||
"settings": "Settings",
|
||||
"recentEmails": "Recent emails",
|
||||
"loginCredentials": "Login credentials",
|
||||
"twoFactorAuthentication": "Two-factor authentication",
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"back": "Atrás",
|
||||
"next": "Siguiente",
|
||||
"use": "Usar",
|
||||
"delete": "Borrar",
|
||||
"save": "Guardar",
|
||||
"or": "O",
|
||||
"close": "Cerrar",
|
||||
"copied": "¡Copiado!",
|
||||
"openInNewWindow": "Abrir en una ventana nueva",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Desactivado",
|
||||
"showPassword": "Mostrar contraseña",
|
||||
"hidePassword": "Ocultar contraseña",
|
||||
"showDetails": "Mostrar detalles",
|
||||
"hideDetails": "Ocultar detalles",
|
||||
"copyToClipboard": "Copiar al portapapeles",
|
||||
"loadingEmails": "Cargando email...",
|
||||
"loadingTotpCodes": "Cargando códigos TOTP...",
|
||||
"attachments": "Archivos adjuntos",
|
||||
"loadingAttachments": "Cargando archivos adjuntos...",
|
||||
"settings": "Configuración",
|
||||
"recentEmails": "Correos recientes",
|
||||
"loginCredentials": "Credenciales de inicio de sesión",
|
||||
"twoFactorAuthentication": "Autenticación de doble factor",
|
||||
"alias": "Alias",
|
||||
"notes": "Notes",
|
||||
"fullName": "Full Name",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"birthDate": "Birth Date",
|
||||
"nickname": "Nickname",
|
||||
"notes": "Notas",
|
||||
"fullName": "Nombre completo",
|
||||
"firstName": "Nombre",
|
||||
"lastName": "Apellido",
|
||||
"birthDate": "Fecha de nacimiento",
|
||||
"nickname": "Alias",
|
||||
"email": "Email",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"syncingVault": "Syncing vault",
|
||||
"savingChangesToVault": "Saving changes to vault",
|
||||
"uploadingVaultToServer": "Uploading vault to server",
|
||||
"checkingVaultUpdates": "Checking for vault updates",
|
||||
"syncingUpdatedVault": "Syncing updated vault",
|
||||
"executingOperation": "Executing operation...",
|
||||
"loadMore": "Load more",
|
||||
"username": "Nombre de usuario",
|
||||
"password": "Contraseña",
|
||||
"syncingVault": "Sincronizando bóveda",
|
||||
"savingChangesToVault": "Guardando cambios en la bóveda",
|
||||
"uploadingVaultToServer": "Subiendo bóveda al servidor",
|
||||
"checkingVaultUpdates": "Comprobando actualizaciones de bóveda",
|
||||
"syncingUpdatedVault": "Sincronizando bóveda actualizada",
|
||||
"executingOperation": "Ejecutando operación...",
|
||||
"loadMore": "Cargar más",
|
||||
"errors": {
|
||||
"serverNotAvailable": "The AliasVault server is not available. Please try again later or contact support if the problem persists.",
|
||||
"clientVersionNotSupported": "This version of the AliasVault browser extension is not supported by the server anymore. Please update your browser extension to the latest version.",
|
||||
"browserExtensionOutdated": "This browser extension is outdated and cannot be used to access this vault. Please update this browser extension to continue.",
|
||||
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this browser extension. Please contact support if you need help.",
|
||||
"serverVersionTooOld": "The AliasVault server needs to be updated to a newer version in order to use this feature. Please contact the server admin if you need help.",
|
||||
"unknownError": "An unknown error occurred",
|
||||
"unknownErrorTryAgain": "An unknown error occurred. Please try again.",
|
||||
"vaultNotAvailable": "Vault not available",
|
||||
"vaultIsLocked": "Vault is locked",
|
||||
"passwordChanged": "Your password has changed since the last time you logged in. Please login again for security reasons."
|
||||
"serverNotAvailable": "El servidor AliasVault no está disponible. Por favor, inténtelo de nuevo más tarde o póngase en contacto con el soporte técnico si el problema persiste.",
|
||||
"clientVersionNotSupported": "Esta versión de la extensión del navegador AliasVault ya no es compatible con el servidor. Por favor, actualice la extensión de su navegador a la última versión.",
|
||||
"browserExtensionOutdated": "Esta extensión del navegador está desactualizada y no se puede utilizar para acceder a esta bóveda. Por favor, actualice esta extensión del navegador para continuar.",
|
||||
"serverVersionNotSupported": "El servidor AliasVault necesita ser actualizado a una versión más reciente para poder usar esta extensión del navegador. Póngase en contacto con el soporte si necesita ayuda.",
|
||||
"serverVersionTooOld": "El servidor AliasVault necesita ser actualizado a una versión más reciente para poder utilizar esta característica. Por favor, contacte con el administrador del servidor si necesita ayuda.",
|
||||
"unknownError": "Se produjo un error desconocido",
|
||||
"unknownErrorTryAgain": "Se ha producido un error desconocido. Por favor, inténtelo de nuevo.",
|
||||
"vaultNotAvailable": "Bóveda no disponible",
|
||||
"vaultIsLocked": "La bóveda está bloqueada",
|
||||
"passwordChanged": "Tu contraseña ha cambiado desde la última vez que iniciaste sesión. Por favor, inicia sesión de nuevo por razones de seguridad."
|
||||
},
|
||||
"apiErrors": {
|
||||
"UNKNOWN_ERROR": "An unknown error occurred. Please try again.",
|
||||
"ACCOUNT_LOCKED": "Account temporarily locked due to too many failed attempts. Please try again later.",
|
||||
"ACCOUNT_BLOCKED": "Your account has been disabled. If you believe this is a mistake, please contact support.",
|
||||
"USER_NOT_FOUND": "Invalid username or password. Please try again.",
|
||||
"INVALID_AUTHENTICATOR_CODE": "Invalid authenticator code. Please try again.",
|
||||
"INVALID_RECOVERY_CODE": "Invalid recovery code. Please try again.",
|
||||
"REFRESH_TOKEN_REQUIRED": "Refresh token is required.",
|
||||
"INVALID_REFRESH_TOKEN": "Invalid refresh token.",
|
||||
"PUBLIC_REGISTRATION_DISABLED": "New account registration is currently disabled on this server. Please contact the administrator.",
|
||||
"USERNAME_REQUIRED": "Username is required.",
|
||||
"USERNAME_ALREADY_IN_USE": "Username is already in use.",
|
||||
"USERNAME_AVAILABLE": "Username is available.",
|
||||
"USERNAME_MISMATCH": "Username does not match the current user.",
|
||||
"PASSWORD_MISMATCH": "The provided password does not match your current password.",
|
||||
"ACCOUNT_SUCCESSFULLY_DELETED": "Account successfully deleted.",
|
||||
"USERNAME_EMPTY_OR_WHITESPACE": "Username cannot be empty or whitespace.",
|
||||
"USERNAME_TOO_SHORT": "Username too short: must be at least 3 characters long.",
|
||||
"USERNAME_TOO_LONG": "Username too long: cannot be longer than 40 characters.",
|
||||
"USERNAME_INVALID_EMAIL": "Invalid email address.",
|
||||
"USERNAME_INVALID_CHARACTERS": "Username is invalid, can only contain letters or digits.",
|
||||
"VAULT_NOT_UP_TO_DATE": "Your vault is not up-to-date. Please synchronize your vault and try again.",
|
||||
"INTERNAL_SERVER_ERROR": "Internal server error.",
|
||||
"VAULT_ERROR": "The local vault is not up-to-date. Please synchronize your vault by refreshing the page and try again."
|
||||
"UNKNOWN_ERROR": "Se ha producido un error desconocido. Por favor, inténtelo de nuevo.",
|
||||
"ACCOUNT_LOCKED": "Cuenta bloqueada temporalmente debido a demasiados intentos fallidos. Por favor, inténtelo de nuevo.",
|
||||
"ACCOUNT_BLOCKED": "Tu cuenta ha sido desactivada. Si crees que esto es un error, ponte en contacto con soporte.",
|
||||
"USER_NOT_FOUND": "Nombre de usuario o contraseña no válidos. Por favor, inténtelo de nuevo.",
|
||||
"INVALID_AUTHENTICATOR_CODE": "Código de autenticación inválido. Por favor, inténtelo de nuevo.",
|
||||
"INVALID_RECOVERY_CODE": "Código de recuperación inválido. Por favor, inténtelo de nuevo.",
|
||||
"REFRESH_TOKEN_REQUIRED": "Se requiere un token de actualización.",
|
||||
"INVALID_REFRESH_TOKEN": "Token de actualización inválido.",
|
||||
"PUBLIC_REGISTRATION_DISABLED": "El registro de nueva cuenta está actualmente deshabilitado en este servidor. Por favor, contacte con el administrador.",
|
||||
"USERNAME_REQUIRED": "Se requiere un nombre de usuario.",
|
||||
"USERNAME_ALREADY_IN_USE": "El nombre de usuario ya está en uso.",
|
||||
"USERNAME_AVAILABLE": "El nombre de usuario está disponible.",
|
||||
"USERNAME_MISMATCH": "El nombre de usuario no coincide con el usuario actual.",
|
||||
"PASSWORD_MISMATCH": "La contraseña proporcionada no coincide con su contraseña actual.",
|
||||
"ACCOUNT_SUCCESSFULLY_DELETED": "Cuenta eliminada con éxito.",
|
||||
"USERNAME_EMPTY_OR_WHITESPACE": "El nombre de usuario no puede estar vacío o en blanco.",
|
||||
"USERNAME_TOO_SHORT": "Nombre de usuario demasiado corto: debe tener al menos 3 caracteres.",
|
||||
"USERNAME_TOO_LONG": "Nombre de usuario demasiado largo: no puede tener más de 40 caracteres.",
|
||||
"USERNAME_INVALID_EMAIL": "Dirección de correo electrónico inválida.",
|
||||
"USERNAME_INVALID_CHARACTERS": "El nombre de usuario inválido, solo puede contener letras o dígitos.",
|
||||
"VAULT_NOT_UP_TO_DATE": "Su bóveda no está actualizada. Por favor, sincronice su bóveda e inténtelo de nuevo.",
|
||||
"INTERNAL_SERVER_ERROR": "Error interno del servidor.",
|
||||
"VAULT_ERROR": "La bóveda local no está actualizada. Por favor, sincronice su bóveda actualizando la página e inténtelo de nuevo."
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"or": "or",
|
||||
"new": "New",
|
||||
"cancel": "Cancel",
|
||||
"vaultLocked": "AliasVault is locked.",
|
||||
"creatingNewAlias": "Creating new alias...",
|
||||
"noMatchesFound": "No matches found",
|
||||
"searchVault": "Search vault...",
|
||||
"serviceName": "Service name",
|
||||
"email": "Email",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"enterServiceName": "Enter service name",
|
||||
"enterEmailAddress": "Enter email address",
|
||||
"enterUsername": "Enter username",
|
||||
"hideFor1Hour": "Hide for 1 hour (current site)",
|
||||
"hidePermanently": "Hide permanently (current site)",
|
||||
"createRandomAlias": "Create random alias",
|
||||
"createUsernamePassword": "Create username/password",
|
||||
"randomAlias": "Random alias",
|
||||
"usernamePassword": "Username/password",
|
||||
"createAndSaveAlias": "Create and save alias",
|
||||
"createAndSaveCredential": "Create and save credential",
|
||||
"randomIdentityDescription": "Generate a random identity with a random email address accessible in AliasVault.",
|
||||
"randomIdentityDescriptionDropdown": "Random identity with random email",
|
||||
"manualCredentialDescription": "Specify your own email address and username.",
|
||||
"manualCredentialDescriptionDropdown": "Manual username and password",
|
||||
"failedToCreateIdentity": "Failed to create identity. Please try again.",
|
||||
"enterEmailAndOrUsername": "Enter email and/or username",
|
||||
"autofillWithAliasVault": "Autofill with AliasVault",
|
||||
"generateRandomPassword": "Generate random password (copy to clipboard)",
|
||||
"generateNewPassword": "Generate new password",
|
||||
"togglePasswordVisibility": "Toggle password visibility",
|
||||
"passwordCopiedToClipboard": "Password copied to clipboard",
|
||||
"openAliasVaultToUpgrade": "Open AliasVault to upgrade",
|
||||
"vaultUpgradeRequired": "Vault upgrade required.",
|
||||
"dismissPopup": "Dismiss popup"
|
||||
"or": "o",
|
||||
"new": "Nuevo",
|
||||
"cancel": "Cancelar",
|
||||
"vaultLocked": "AliasVault está bloqueado.",
|
||||
"creatingNewAlias": "Creando nuevo alias...",
|
||||
"noMatchesFound": "No se han encontrado resultados",
|
||||
"searchVault": "Buscar bóveda...",
|
||||
"serviceName": "Nombre del servicio",
|
||||
"email": "Correo electrónico",
|
||||
"username": "Nombre de usuario",
|
||||
"password": "Contraseña",
|
||||
"enterServiceName": "Introduzca el nombre del servicio",
|
||||
"enterEmailAddress": "Introduzca la dirección de correo electrónico",
|
||||
"enterUsername": "Introduzca nombre de usuario",
|
||||
"hideFor1Hour": "Ocultar durante 1 hora (sitio actual)",
|
||||
"hidePermanently": "Ocultar permanentemente (sitio actual)",
|
||||
"createRandomAlias": "Crear alias aleatorio",
|
||||
"createUsernamePassword": "Crear nombre de usuario / contraseña",
|
||||
"randomAlias": "Alias aleatorio",
|
||||
"usernamePassword": "Nombre de usuario/Contraseña",
|
||||
"createAndSaveAlias": "Crear y guardar alias",
|
||||
"createAndSaveCredential": "Crear y guardar credencial",
|
||||
"randomIdentityDescription": "Generar una identidad aleatoria con una dirección de correo electrónico aleatoria accesible en AliasVault.",
|
||||
"randomIdentityDescriptionDropdown": "Identidad aleatoria con correo electrónico aleatorio",
|
||||
"manualCredentialDescription": "Especifique su propia dirección de correo electrónico y nombre de usuario.",
|
||||
"manualCredentialDescriptionDropdown": "Nombre de usuario y contraseña manual",
|
||||
"failedToCreateIdentity": "Error al crear la identidad. Por favor, inténtalo de nuevo.",
|
||||
"enterEmailAndOrUsername": "Introduzca email y/o nombre de usuario",
|
||||
"autofillWithAliasVault": "AutoFill con AliasVault",
|
||||
"generateRandomPassword": "Generar contraseña aleatoria (copiar al portapapeles)",
|
||||
"generateNewPassword": "Generar nueva contraseña",
|
||||
"togglePasswordVisibility": "Cambiar la visibilidad de la contraseña",
|
||||
"passwordCopiedToClipboard": "Contraseña copiada al portapapeles",
|
||||
"openAliasVaultToUpgrade": "Abrir AliasVault para actualizar",
|
||||
"vaultUpgradeRequired": "Actualización de bóveda requerida.",
|
||||
"dismissPopup": "Descartar aviso"
|
||||
},
|
||||
"credentials": {
|
||||
"title": "Credentials",
|
||||
"addCredential": "Add Credential",
|
||||
"editCredential": "Edit Credential",
|
||||
"deleteCredential": "Delete Credential",
|
||||
"credentialDetails": "Credential Details",
|
||||
"serviceName": "Service Name",
|
||||
"notes": "Notes",
|
||||
"totp": "Two-Factor Authentication",
|
||||
"totpCode": "TOTP Code",
|
||||
"copyTotp": "Copy TOTP",
|
||||
"totpSecret": "TOTP Secret",
|
||||
"totpSecretPlaceholder": "Enter TOTP secret key",
|
||||
"welcomeTitle": "Welcome to AliasVault!",
|
||||
"welcomeDescription": "To use the AliasVault browser extension: navigate to a website and use the AliasVault autofill popup to create a new credential.",
|
||||
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
|
||||
"noAttachmentsFound": "No credentials with attachments found",
|
||||
"noMatchingCredentials": "No matching credentials found",
|
||||
"createdAt": "Created",
|
||||
"updatedAt": "Last updated",
|
||||
"saveCredential": "Save credential",
|
||||
"deleteCredentialTitle": "Delete Credential",
|
||||
"deleteCredentialConfirm": "Are you sure you want to delete this credential? This action cannot be undone.",
|
||||
"title": "Credenciales",
|
||||
"addCredential": "Añadir credencial",
|
||||
"editCredential": "Editar credencial",
|
||||
"deleteCredential": "Borrar credencial",
|
||||
"credentialDetails": "Detalles de la credencial",
|
||||
"serviceName": "Nombre del servicio",
|
||||
"notes": "Notas",
|
||||
"totp": "Autenticación de Doble Factor",
|
||||
"totpCode": "Código TOTP",
|
||||
"copyTotp": "Copiar TOTP",
|
||||
"totpSecret": "Secreto TOTP",
|
||||
"totpSecretPlaceholder": "Introduzca la clave secreta TOTP",
|
||||
"welcomeTitle": "¡Bienvenido a AliasVault!",
|
||||
"welcomeDescription": "Para utilizar la extensión del navegador AliasVault: vaya a un sitio web y utilice la ventana de autocompletado de AliasVault para crear una nueva credencial.",
|
||||
"noPasskeysFound": "No se han creado llaves de acceso todavía. Las llaves se crean visitando un sitio web que ofrece llaves de acceso como método de autenticación.",
|
||||
"noAttachmentsFound": "No se encontraron credenciales con archivos adjuntos",
|
||||
"noMatchingCredentials": "No se han encontrado credenciales coincidentes",
|
||||
"createdAt": "Creado",
|
||||
"updatedAt": "Última actualización",
|
||||
"saveCredential": "Guardar credencial",
|
||||
"deleteCredentialTitle": "Borrar credencial",
|
||||
"deleteCredentialConfirm": "¿Está seguro de que desea eliminar estas credenciales? Esta acción no se puede deshacer.",
|
||||
"filters": {
|
||||
"all": "(All) Credentials",
|
||||
"passkeys": "Passkeys",
|
||||
"aliases": "Aliases",
|
||||
"userpass": "Passwords",
|
||||
"attachments": "Attachments"
|
||||
"all": "(Todas) Credenciales",
|
||||
"passkeys": "Llaves de acceso",
|
||||
"aliases": "Alias",
|
||||
"userpass": "Contraseñas",
|
||||
"attachments": "Archivos adjuntos"
|
||||
},
|
||||
"randomAlias": "Random Alias",
|
||||
"randomAlias": "Alias aleatorio",
|
||||
"manual": "Manual",
|
||||
"service": "Service",
|
||||
"serviceUrl": "Service URL",
|
||||
"loginCredentials": "Login Credentials",
|
||||
"generateRandomUsername": "Generate random username",
|
||||
"generateRandomPassword": "Generate random password",
|
||||
"changePasswordComplexity": "Change password complexity",
|
||||
"passwordLength": "Password length",
|
||||
"includeLowercase": "Include lowercase letters",
|
||||
"includeUppercase": "Include uppercase letters",
|
||||
"includeNumbers": "Include numbers",
|
||||
"includeSpecialChars": "Include special characters",
|
||||
"avoidAmbiguousChars": "Avoid ambiguous characters (o, 0, etc.)",
|
||||
"generateNewPreview": "Generate new preview",
|
||||
"generateRandomAlias": "Generate Random Alias",
|
||||
"clearAliasFields": "Clear Alias Fields",
|
||||
"service": "Servicio",
|
||||
"serviceUrl": "URL del servicio",
|
||||
"loginCredentials": "Credenciales de inicio de sesión",
|
||||
"generateRandomUsername": "Generar nombre de usuario aleatorio",
|
||||
"generateRandomPassword": "Generar contraseña aleatoria",
|
||||
"changePasswordComplexity": "Cambiar complejidad de contraseña",
|
||||
"passwordLength": "Longitud de la contraseña",
|
||||
"includeLowercase": "Incluye letras minúsculas",
|
||||
"includeUppercase": "Incluye letras mayúsculas",
|
||||
"includeNumbers": "Incluye números",
|
||||
"includeSpecialChars": "Incluye caracteres especiales",
|
||||
"avoidAmbiguousChars": "Evita caracteres ambiguos (o, 0, etc.)",
|
||||
"generateNewPreview": "Crear nueva vista previa",
|
||||
"generateRandomAlias": "Crear alias aleatorio",
|
||||
"clearAliasFields": "Limpiar campos de alias",
|
||||
"alias": "Alias",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"nickName": "Nick Name",
|
||||
"gender": "Gender",
|
||||
"birthDate": "Birth Date",
|
||||
"birthDatePlaceholder": "YYYY-MM-DD",
|
||||
"metadata": "Metadata",
|
||||
"firstName": "Nombre",
|
||||
"lastName": "Apellido",
|
||||
"nickName": "Apodo",
|
||||
"gender": "Género",
|
||||
"birthDate": "Fecha de nacimiento",
|
||||
"birthDatePlaceholder": "AAAA-MM-DD",
|
||||
"metadata": "Metadatos",
|
||||
"validation": {
|
||||
"required": "This field is required",
|
||||
"serviceNameRequired": "Service name is required",
|
||||
"invalidEmail": "Invalid email format",
|
||||
"invalidDateFormat": "Date must be in YYYY-MM-DD format"
|
||||
"required": "Se requiere este campo",
|
||||
"serviceNameRequired": "Se requiere Nombre del servicio",
|
||||
"invalidEmail": "Formato de correo electrónico inválido",
|
||||
"invalidDateFormat": "La fecha debe estar en formato AAAA-MM-DD"
|
||||
},
|
||||
"privateEmailTitle": "Private Email",
|
||||
"privateEmailAliasVaultServer": "AliasVault server",
|
||||
"privateEmailDescription": "E2E encrypted, fully private.",
|
||||
"publicEmailTitle": "Public Temp Email Providers",
|
||||
"publicEmailDescription": "Anonymous but limited privacy. Email content is readable by anyone that knows the address.",
|
||||
"useDomainChooser": "Use domain chooser",
|
||||
"enterCustomDomain": "Enter custom domain",
|
||||
"enterFullEmail": "Enter full email address",
|
||||
"enterEmailPrefix": "Enter email prefix"
|
||||
"privateEmailTitle": "Correo electrónico privado",
|
||||
"privateEmailAliasVaultServer": "Servidor AliasVault",
|
||||
"privateEmailDescription": "E2E cifrado, totalmente privado.",
|
||||
"publicEmailTitle": "Proveedores de Correo Temporal Públicos",
|
||||
"publicEmailDescription": "Privacidad anónima pero limitada. Contenido de correo electrónico puede ser leído por cualquiera que conozca la dirección.",
|
||||
"useDomainChooser": "Usar selector de dominio",
|
||||
"enterCustomDomain": "Introduzca dominio personalizado",
|
||||
"enterFullEmail": "Introduzca la dirección de correo completa",
|
||||
"enterEmailPrefix": "Introduzca prefijo de correo"
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Añadir código 2FA",
|
||||
"instructions": "Introduzca la clave secreta mostrada por el sitio web donde desea añadir autenticación de dos factores.",
|
||||
"nameOptional": "Nombre (Opcional)",
|
||||
"secretKey": "Clave secreta",
|
||||
"saveToViewCode": "Guardar para ver el código",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Formato de clave secreta inválida."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"title": "Emails",
|
||||
"deleteEmailTitle": "Delete Email",
|
||||
"deleteEmailConfirm": "Are you sure you want to permanently delete this email?",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"date": "Date",
|
||||
"emailContent": "Email content",
|
||||
"attachments": "Attachments",
|
||||
"emailNotFound": "Email not found",
|
||||
"noEmails": "No emails found",
|
||||
"noEmailsDescription": "You have not received any emails at your private email addresses yet. When you receive a new email, it will appear here.",
|
||||
"title": "Correos electrónicos",
|
||||
"deleteEmailTitle": "Eliminar correo",
|
||||
"deleteEmailConfirm": "¿Está seguro que desea eliminar permanentemente este correo electrónico?",
|
||||
"from": "De",
|
||||
"to": "Para",
|
||||
"date": "Fecha",
|
||||
"emailContent": "Contenido del correo electrónico",
|
||||
"attachments": "Archivos adjuntos",
|
||||
"emailNotFound": "Correo electrónico no encontrado",
|
||||
"noEmails": "No se han encontrado correos electrónicos",
|
||||
"noEmailsDescription": "No has recibido ningún correo electrónico en tus direcciones privadas todavía. Cuando recibas un nuevo correo electrónico, aparecerá aquí.",
|
||||
"dateFormat": {
|
||||
"justNow": "just now",
|
||||
"minutesAgo_single": "{{count}} min ago",
|
||||
"minutesAgo_plural": "{{count}} mins ago",
|
||||
"hoursAgo_single": "{{count}} hr ago",
|
||||
"hoursAgo_plural": "{{count}} hrs ago",
|
||||
"yesterday": "yesterday"
|
||||
"justNow": "ahora mismo",
|
||||
"minutesAgo_single": "Hace {{count}} min",
|
||||
"minutesAgo_plural": "Hace {{count}} min",
|
||||
"hoursAgo_single": "Hace {{count}} h",
|
||||
"hoursAgo_plural": "Hace {{count}} h",
|
||||
"yesterday": "ayer"
|
||||
},
|
||||
"errors": {
|
||||
"emailLoadError": "An error occurred while loading emails. Please try again later.",
|
||||
"emailUnexpectedError": "An unexpected error occurred while loading emails. Please try again later."
|
||||
"emailLoadError": "Se ha producido un error al cargar los correos electrónicos. Por favor, inténtalo de nuevo más tarde.",
|
||||
"emailUnexpectedError": "Se ha producido un error inesperado al cargar los correos electrónicos. Por favor, inténtalo de nuevo más tarde."
|
||||
},
|
||||
"apiErrors": {
|
||||
"CLAIM_DOES_NOT_MATCH_USER": "The current chosen email address is already in use. Please change the email address by editing this credential.",
|
||||
"CLAIM_DOES_NOT_EXIST": "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."
|
||||
"CLAIM_DOES_NOT_MATCH_USER": "La dirección de correo electrónico elegida actualmente ya está en uso. Por favor, cambie la dirección de correo electrónico editando esta credencial.",
|
||||
"CLAIM_DOES_NOT_EXIST": "Ocurrió un error mientras se trataba de cargar los correos electrónicos. Por favor, intente editar y guardar la entrada de credenciales para sincronizar la base de datos, y vuelva a intentarlo."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"serverUrl": "Server URL",
|
||||
"language": "Language",
|
||||
"autofillEnabled": "Enable Autofill",
|
||||
"version": "Version",
|
||||
"openInNewWindow": "Open in new window",
|
||||
"openWebApp": "Open web app",
|
||||
"loggedIn": "Logged in",
|
||||
"logout": "Logout",
|
||||
"lock": "Lock",
|
||||
"globalSettings": "Global Settings",
|
||||
"autofillPopup": "Autofill popup",
|
||||
"activeOnAllSites": "Active on all sites (unless disabled below)",
|
||||
"disabledOnAllSites": "Disabled on all sites",
|
||||
"rightClickContextMenu": "Right-click context menu",
|
||||
"autofillMatching": "Autofill Matching",
|
||||
"autofillMatchingMode": "Autofill matching mode",
|
||||
"autofillMatchingModeDescription": "Determines which credentials are considered a match and shown as suggestions in the autofill popup for a given website.",
|
||||
"autofillMatchingDefault": "URL + subdomain + name wildcard",
|
||||
"autofillMatchingUrlSubdomain": "URL + subdomain",
|
||||
"autofillMatchingUrlExact": "Exact URL domain only",
|
||||
"siteSpecificSettings": "Site-Specific Settings",
|
||||
"autofillPopupOn": "Autofill popup on: ",
|
||||
"enabledForThisSite": "Enabled for this site",
|
||||
"disabledForThisSite": "Disabled for this site",
|
||||
"temporarilyDisabledUntil": "Temporarily disabled until ",
|
||||
"resetAllSiteSettings": "Reset all site-specific settings",
|
||||
"appearance": "Appearance",
|
||||
"theme": "Theme",
|
||||
"useDefault": "Use default",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"keyboardShortcuts": "Keyboard Shortcuts",
|
||||
"configureKeyboardShortcuts": "Configure keyboard shortcuts",
|
||||
"configure": "Configure",
|
||||
"clipboardClearTimeout": "Clear clipboard after copying",
|
||||
"clipboardClearTimeoutDescription": "Automatically clear the clipboard after copying sensitive data",
|
||||
"clipboardClearDisabled": "Never clear",
|
||||
"clipboardClear5Seconds": "Clear after 5 seconds",
|
||||
"clipboardClear10Seconds": "Clear after 10 seconds",
|
||||
"clipboardClear15Seconds": "Clear after 15 seconds",
|
||||
"autoLockTimeout": "Auto-lock Timeout",
|
||||
"autoLockTimeoutDescription": "Automatically lock the vault after a period of inactivity",
|
||||
"autoLockTimeoutHelp": "The vault will only lock after the specified period of inactivity (no autofill usage or extension popup opened). The vault will always lock when the browser is closed, regardless of this setting.",
|
||||
"autoLockNever": "Never",
|
||||
"autoLock15Seconds": "15 seconds",
|
||||
"autoLock1Minute": "1 minute",
|
||||
"autoLock5Minutes": "5 minutes",
|
||||
"autoLock15Minutes": "15 minutes",
|
||||
"autoLock30Minutes": "30 minutes",
|
||||
"autoLock1Hour": "1 hour",
|
||||
"autoLock4Hours": "4 hours",
|
||||
"autoLock8Hours": "8 hours",
|
||||
"autoLock24Hours": "24 hours",
|
||||
"versionPrefix": "Version ",
|
||||
"autofillSettings": "Autofill Settings",
|
||||
"clipboardSettings": "Clipboard Settings",
|
||||
"contextMenuSettings": "Context Menu Settings",
|
||||
"passkeySettings": "Passkey Settings",
|
||||
"contextMenu": "Context Menu",
|
||||
"contextMenuEnabled": "Context menu is enabled",
|
||||
"contextMenuDisabled": "Context menu is disabled",
|
||||
"contextMenuDescription": "Right-click on input fields to access AliasVault options",
|
||||
"selectLanguage": "Select Language",
|
||||
"serverConfiguration": "Server Configuration",
|
||||
"serverConfigurationDescription": "Configure the AliasVault server URL for self-hosted instances",
|
||||
"title": "Ajustes",
|
||||
"serverUrl": "URL del servidor",
|
||||
"language": "Idioma",
|
||||
"autofillEnabled": "Activar autocompletado",
|
||||
"version": "Versión",
|
||||
"openInNewWindow": "Abrir en una ventana nueva",
|
||||
"openWebApp": "Abrir la aplicación web",
|
||||
"loggedIn": "Sesión iniciada",
|
||||
"logout": "Cerrar sesión",
|
||||
"lock": "Bloquear",
|
||||
"globalSettings": "Ajustes globales",
|
||||
"autofillPopup": "Ventana de autorrellenado",
|
||||
"activeOnAllSites": "Activo en todos los sitios (a menos que se deshabilite debajo)",
|
||||
"disabledOnAllSites": "Deshabilitado en todos los sitios",
|
||||
"rightClickContextMenu": "Menú contextual del clic derecho",
|
||||
"autofillMatching": "Coincidencia de autocompletado",
|
||||
"autofillMatchingMode": "Modo de coincidencia de autocompletado",
|
||||
"autofillMatchingModeDescription": "Determina qué credenciales se consideran coincidentes y se muestran como sugerencias en el popup de autorrelleno de un sitio web determinado.",
|
||||
"autofillMatchingDefault": "URL + subdominio + nombre wildcard",
|
||||
"autofillMatchingUrlSubdomain": "URL + subdominio",
|
||||
"autofillMatchingUrlExact": "Solo URL exacta",
|
||||
"siteSpecificSettings": "Ajustes específicos del sitio",
|
||||
"autofillPopupOn": "Ventana de autorrelleno ",
|
||||
"enabledForThisSite": "Habilitado para este sitio",
|
||||
"disabledForThisSite": "Deshabilitado para este sitio",
|
||||
"temporarilyDisabledUntil": "Deshabilitado temporalmente hasta ",
|
||||
"resetAllSiteSettings": "Reiniciar todos los ajustes específicos del sitio",
|
||||
"appearance": "Apariencia",
|
||||
"theme": "Tema",
|
||||
"useDefault": "Uso por defecto",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"keyboardShortcuts": "Atajos de teclado",
|
||||
"configureKeyboardShortcuts": "Configurar atajos de teclado",
|
||||
"configure": "Configurar",
|
||||
"clipboardClearTimeout": "Limpiar portapapeles después de copiar",
|
||||
"clipboardClearTimeoutDescription": "Limpiar automáticamente el portapapeles después de copiar datos sensibles",
|
||||
"clipboardClearDisabled": "Nunca limpiar",
|
||||
"clipboardClear5Seconds": "Limpiar después 5 segundos",
|
||||
"clipboardClear10Seconds": "Limpiar después 10 segundos",
|
||||
"clipboardClear15Seconds": "Limpiar después 15 segundos",
|
||||
"autoLockTimeout": "Tiempo de bloqueo automático",
|
||||
"autoLockTimeoutDescription": "Bloquear automáticamente la bóveda después de un período de inactividad",
|
||||
"autoLockTimeoutHelp": "La bóveda sólo se bloqueará después del período especificado de inactividad (no se abrirá el uso de autocompletado o la ventana emergente). La bóveda siempre se bloqueará cuando el navegador esté cerrado, independientemente de esta configuración.",
|
||||
"autoLockNever": "Nunca",
|
||||
"autoLock15Seconds": "15 segundos",
|
||||
"autoLock1Minute": "1 minuto",
|
||||
"autoLock5Minutes": "5 minutos",
|
||||
"autoLock15Minutes": "15 minutos",
|
||||
"autoLock30Minutes": "30 minutos",
|
||||
"autoLock1Hour": "1 hora",
|
||||
"autoLock4Hours": "4 horas",
|
||||
"autoLock8Hours": "8 horas",
|
||||
"autoLock24Hours": "24 horas",
|
||||
"versionPrefix": "Versión ",
|
||||
"autofillSettings": "Ajustes de autocompletado",
|
||||
"clipboardSettings": "Ajustes del portapapeles",
|
||||
"contextMenuSettings": "Ajustes del menú contextual",
|
||||
"passkeySettings": "Ajustes de llaves de acceso",
|
||||
"contextMenu": "Menú Contextual",
|
||||
"contextMenuEnabled": "El menú contextual está activado",
|
||||
"contextMenuDisabled": "El menú contextual está desactivado",
|
||||
"contextMenuDescription": "Haga clic derecho en los campos de entrada para acceder a las opciones de AliasVault",
|
||||
"selectLanguage": "Seleccionar idioma",
|
||||
"serverConfiguration": "Configuración del servidor",
|
||||
"serverConfigurationDescription": "Configurar la URL del servidor AliasVault para instancias self-hosted",
|
||||
"customApiUrl": "API URL",
|
||||
"customClientUrl": "Client URL",
|
||||
"apiUrlHint": "The API endpoint URL (usually client URL + /api)",
|
||||
"clientUrlHint": "The web interface URL of your self-hosted instance",
|
||||
"autofillSettingsDescription": "Enable or disable the autofill popup on web pages",
|
||||
"autofillEnabledDescription": "Autofill suggestions will appear on login forms",
|
||||
"autofillDisabledDescription": "Autofill suggestions are disabled globally",
|
||||
"languageSettings": "Language",
|
||||
"customClientUrl": "URL de Cliente",
|
||||
"apiUrlHint": "La URL del endpoint de la API (generalmente URL del cliente + /api)",
|
||||
"clientUrlHint": "La URL de la interfaz web de su instancia self-hosted",
|
||||
"autofillSettingsDescription": "Activar o desactivar la ventana emergente de autorrelleno en páginas web",
|
||||
"autofillEnabledDescription": "Las sugerencias de autorrelleno aparecerán en los formularios de inicio de sesión",
|
||||
"autofillDisabledDescription": "Las sugerencias de autorrelleno están desactivadas globalmente",
|
||||
"languageSettings": "Idioma",
|
||||
"validation": {
|
||||
"apiUrlRequired": "API URL is required",
|
||||
"apiUrlInvalid": "Please enter a valid API URL",
|
||||
"clientUrlRequired": "Client URL is required",
|
||||
"clientUrlInvalid": "Please enter a valid client URL"
|
||||
"apiUrlRequired": "La URL de la API es necesaria",
|
||||
"apiUrlInvalid": "Por favor, introduzca una URL de API válida",
|
||||
"clientUrlRequired": "La URL de cliente es necesaria",
|
||||
"clientUrlInvalid": "Por favor, introduzca una URL de cliente válida"
|
||||
},
|
||||
"unlockMethod": {
|
||||
"title": "Vault Unlock Method",
|
||||
"introText": "Choose how you want to unlock your vault. You can use your master password (always available) or set up a PIN code for faster access. After 3 failed PIN attempts, you'll need to use your master password.",
|
||||
"password": "Master Password",
|
||||
"pin": "PIN Code",
|
||||
"pinDescription": "Unlock vault with PIN code",
|
||||
"setupPin": "Setup PIN Code",
|
||||
"enterNewPinDescription": "Enter a PIN code consisting of minimum 6 digits",
|
||||
"confirmPin": "Confirm PIN",
|
||||
"confirmPinDescription": "Enter your PIN again to confirm",
|
||||
"invalidPinFormat": "Invalid PIN format",
|
||||
"pinMismatch": "PINs do not match",
|
||||
"incorrectPin": "Incorrect PIN. {{attemptsRemaining}} attempts remaining.",
|
||||
"incorrectPinSingular": "Incorrect PIN. 1 attempt remaining.",
|
||||
"enableSuccess": "PIN unlock enabled successfully!",
|
||||
"pinLocked": "PIN unlock has been disabled. Please use your master password to unlock your vault.",
|
||||
"pinSecurityWarning": "PIN unlock in the browser extension can be less secure than your master password, as PINs typically have lower entropy and may be brute-forced if your device is compromised. Use it only on devices you fully trust."
|
||||
"title": "Método de desbloqueo de la bóveda",
|
||||
"introText": "Elija cómo desea desbloquear su bóveda. Puede utilizar su contraseña maestra (siempre disponible) o configurar un código PIN para un acceso más rápido. Después de 3 intentos fallidos de PIN, necesitarás usar tu contraseña maestra.",
|
||||
"password": "Contraseña Maestra",
|
||||
"pin": "Código PIN",
|
||||
"pinDescription": "Desbloquear bóveda con código PIN",
|
||||
"setupPin": "Configurar código PIN",
|
||||
"enterNewPinDescription": "Introduzca un código PIN que contenga un mínimo de 6 dígitos",
|
||||
"confirmPin": "Confirmar PIN",
|
||||
"confirmPinDescription": "Introduzca su PIN de nuevo para confirmar",
|
||||
"invalidPinFormat": "Formato PIN inválido",
|
||||
"pinMismatch": "Los PINs no coinciden",
|
||||
"incorrectPin": "PIN incorrecto, {{attemptsRemaining}} intentos restantes.",
|
||||
"incorrectPinSingular": "PIN incorrecto. 1 intento restante.",
|
||||
"enableSuccess": "¡PIN activado con éxito!",
|
||||
"pinLocked": "El desbloqueo con PIN ha sido deshabilitado. Por favor, utiliza tu contraseña maestra para desbloquear tu bóveda.",
|
||||
"pinSecurityWarning": "El PIN de desbloqueo en la extensión del navegador puede ser menos seguro que su contraseña maestra, ya que los PIN típicamente tienen menos entropía y se pueden obtener por fuerza bruta si su dispositivo está en peligro. Úselo sólo en dispositivos en los que confíe plenamente."
|
||||
}
|
||||
},
|
||||
"passkeys": {
|
||||
"passkey": "Passkey",
|
||||
"site": "Site",
|
||||
"displayName": "Name",
|
||||
"helpText": "Passkeys are created on the website when prompted. They cannot be manually edited. To remove this passkey, you can delete it from this credential. To replace this passkey or create a new one, visit the website and follow its prompts.",
|
||||
"passkeyMarkedForDeletion": "Passkey marked for deletion",
|
||||
"passkeyWillBeDeleted": "This passkey will be deleted when you save this credential.",
|
||||
"passkey": "Llave de acceso",
|
||||
"site": "Sitio",
|
||||
"displayName": "Nombre",
|
||||
"helpText": "Las llaves de acceso se crean en el sitio web cuando se le solicite. No pueden ser editadas manualmente. Para eliminar esta clave, puede eliminarla de esta credencial. Para reemplazar esta clave de acceso o crear una nueva, visite el sitio web y siga sus indicaciones.",
|
||||
"passkeyMarkedForDeletion": "Llave de acceso marcada para eliminar",
|
||||
"passkeyWillBeDeleted": "Esta llave de acceso se eliminará cuando guarde esta credencial.",
|
||||
"bypass": {
|
||||
"title": "Use Browser Passkey",
|
||||
"description": "How long would you like to use the browser's passkey provider for {{origin}}?",
|
||||
"thisTimeOnly": "This time only",
|
||||
"alwaysForSite": "Always for this site"
|
||||
"title": "Usar llave de acceso del navegador",
|
||||
"description": "¿Cuánto tiempo quieres usar el proveedor de llaves de acceso del navegador para {{origin}}?",
|
||||
"thisTimeOnly": "Solo esta vez",
|
||||
"alwaysForSite": "Siempre para este sitio"
|
||||
},
|
||||
"authenticate": {
|
||||
"title": "Sign in with Passkey",
|
||||
"signInFor": "Sign in with passkey for",
|
||||
"selectPasskey": "Select a passkey to sign in:",
|
||||
"noPasskeysFound": "No passkeys found for this site",
|
||||
"useBrowserPasskey": "Use Browser Passkey"
|
||||
"title": "Iniciar sesión con llave de acceso",
|
||||
"signInFor": "Iniciar sesión con llave para",
|
||||
"selectPasskey": "Seleccione una llave de acceso para iniciar sesión:",
|
||||
"noPasskeysFound": "No se encontraron llaves de acceso para este sitio",
|
||||
"useBrowserPasskey": "Usar llave de acceso del navegador"
|
||||
},
|
||||
"create": {
|
||||
"title": "Create Passkey",
|
||||
"createFor": "Create a new passkey for",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Enter a name for this passkey",
|
||||
"createButton": "Create Passkey",
|
||||
"useBrowserPasskey": "Use Browser Passkey",
|
||||
"selectPasskeyToReplace": "Select a passkey to replace:",
|
||||
"createNewPasskey": "Create New Passkey",
|
||||
"replacingPasskey": "Replacing passkey: {{displayName}}",
|
||||
"confirmReplace": "Confirm Replace"
|
||||
"title": "Crear llave de acceso",
|
||||
"createFor": "Crear nueva llave de acceso para",
|
||||
"titleLabel": "Título",
|
||||
"titlePlaceholder": "Introduzca un nombre para esta llave de acceso",
|
||||
"createButton": "Crear llave de acceso",
|
||||
"useBrowserPasskey": "Usar llave de acceso del navegador",
|
||||
"selectPasskeyToReplace": "Seleccione una llave de acceso para reemplazar:",
|
||||
"createNewPasskey": "Crear nueva llave de acceso",
|
||||
"replacingPasskey": "Reemplazando llave de acceso: {{displayName}}",
|
||||
"confirmReplace": "Confirmar reemplazo"
|
||||
},
|
||||
"settings": {
|
||||
"passkeyProvider": "Passkey Provider",
|
||||
"passkeyProviderOn": "Passkey Provider on "
|
||||
"passkeyProvider": "Proveedor de llave de acceso",
|
||||
"passkeyProviderOn": "Proveedor de llave de acceso en "
|
||||
}
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "Upgrade Vault",
|
||||
"subtitle": "AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.",
|
||||
"versionInformation": "Version Information",
|
||||
"yourVault": "Your vault version:",
|
||||
"newVersion": "New available version:",
|
||||
"upgrade": "Upgrade Vault",
|
||||
"upgrading": "Upgrading...",
|
||||
"logout": "Logout",
|
||||
"whatsNew": "What's New",
|
||||
"whatsNewDescription": "An upgrade is required to support the following changes:",
|
||||
"noDescriptionAvailable": "No description available for this version.",
|
||||
"title": "Actualizar bóveda",
|
||||
"subtitle": "AliasVault se ha actualizado y tu bóveda necesita ser actualizada. Esto solo debería tardar unos segundos.",
|
||||
"versionInformation": "Información de versión",
|
||||
"yourVault": "Tu versión de bóveda:",
|
||||
"newVersion": "Nueva versión disponible:",
|
||||
"upgrade": "Actualizar bóveda",
|
||||
"upgrading": "Actualizando...",
|
||||
"logout": "Cerrar sesión",
|
||||
"whatsNew": "Novedades",
|
||||
"whatsNewDescription": "Se requiere una actualización para soportar los siguientes cambios:",
|
||||
"noDescriptionAvailable": "Ninguna descripción disponible para esta versión.",
|
||||
"alerts": {
|
||||
"unableToGetVersionInfo": "Unable to get version information. Please try again.",
|
||||
"selfHostedServer": "Self-Hosted Server",
|
||||
"selfHostedWarning": "If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working.",
|
||||
"continueUpgrade": "Continue Upgrade",
|
||||
"upgradeFailed": "Upgrade Failed",
|
||||
"failedToApplyMigration": "Failed to apply migration ({{current}} of {{total}})"
|
||||
"unableToGetVersionInfo": "No se pudo obtener información de la versión. Por favor, inténtalo de nuevo.",
|
||||
"selfHostedServer": "Servidor autoalojado",
|
||||
"selfHostedWarning": "Si está utilizando un servidor autoalojado, asegúrese de actualizar su instancia autoalojada, ya que de lo contrario iniciar sesión en el cliente web dejará de funcionar.",
|
||||
"continueUpgrade": "Continuar actualización",
|
||||
"upgradeFailed": "Actualización fallida",
|
||||
"failedToApplyMigration": "Error al migrar de ({{current}} a {{total}})"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@
|
||||
"next": "Seuraava",
|
||||
"use": "Käytä",
|
||||
"delete": "Poista",
|
||||
"save": "Save",
|
||||
"save": "Tallenna",
|
||||
"or": "Tai",
|
||||
"close": "Sulje",
|
||||
"copied": "Kopioitu!",
|
||||
@@ -242,13 +242,13 @@
|
||||
"enterEmailPrefix": "Syötä sähköpostin etuliite"
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Lisää 2FA TOTP -koodi",
|
||||
"instructions": "Syötä salainen avain, joka näkyy sivustossa, jossa haluat lisätä kaksivaiheisen tunnistautumisen",
|
||||
"nameOptional": "Nimi (valinnainen)",
|
||||
"secretKey": "Salainen avain",
|
||||
"saveToViewCode": "Tallenna nähdäksesi koodin",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Virheellinen salatun avaimen muoto."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"password": "Mot de passe",
|
||||
"passwordPlaceholder": "Saisissez votre mot de passe",
|
||||
"rememberMe": "Se souvenir de moi",
|
||||
"loginButton": "Log in",
|
||||
"loginButton": "Se connecter",
|
||||
"noAccount": "Pas de compte?",
|
||||
"createVault": "Créer un nouveau coffre",
|
||||
"twoFactorTitle": "Veuillez entrer le code d'authentification de votre application d'authentification.",
|
||||
@@ -15,7 +15,7 @@
|
||||
"verify": "Vérifier",
|
||||
"twoFactorNote": "Remarque : si vous n'avez pas accès à votre appareil d'authentification, vous pouvez réinitialiser votre authentification à double facteur avec un code de récupération en vous connectant via le site web.",
|
||||
"masterPassword": "Mot de passe principal",
|
||||
"unlockVault": "Unlock",
|
||||
"unlockVault": "Déverrouiller",
|
||||
"unlockWithPin": "Déverrouiller avec un code PIN",
|
||||
"enterPinToUnlock": "Entrez votre code PIN pour déverrouiller votre coffre",
|
||||
"useMasterPassword": "Utiliser le mot de passe principal",
|
||||
@@ -29,9 +29,9 @@
|
||||
"connectingTo": "Connexion à",
|
||||
"switchAccounts": "Changer de compte ?",
|
||||
"loggedIn": "Connecté(e)",
|
||||
"loginWithMobile": "Log in using Mobile App",
|
||||
"unlockWithMobile": "Unlock using Mobile App",
|
||||
"scanQrCode": "Scan this QR code with your AliasVault mobile app to log in and unlock your vault.",
|
||||
"loginWithMobile": "Se connecter à l'aide de l'application mobile",
|
||||
"unlockWithMobile": "Déverrouiller en utilisant l'application mobile",
|
||||
"scanQrCode": "Scannez ce code QR avec votre application mobile AliasVault pour vous connecter et déverrouiller votre coffre.",
|
||||
"errors": {
|
||||
"invalidCode": "Veuillez entrer un code d'authentification valide à 6 chiffres.",
|
||||
"serverError": "Impossible d'accéder au serveur AliasVault. Veuillez réessayer plus tard ou contacter le support si le problème persiste.",
|
||||
@@ -39,7 +39,7 @@
|
||||
"accountLocked": "Compte temporairement verrouillé en raison d'un trop grand nombre de tentatives échouées.",
|
||||
"networkError": "Erreur réseau. Vérifiez votre connexion et réessayez.",
|
||||
"sessionExpired": "Votre session a expiré. Veuillez vous reconnecter.",
|
||||
"mobileLoginRequestExpired": "Mobile login request timed out. Please reload the page and try again."
|
||||
"mobileLoginRequestExpired": "La demande de connexion de l'application mobile a expiré. Veuillez recharger la page et réessayer."
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"next": "Suivant",
|
||||
"use": "Utiliser",
|
||||
"delete": "Supprimer",
|
||||
"save": "Save",
|
||||
"save": "Sauvegarder",
|
||||
"or": "Ou",
|
||||
"close": "Fermer",
|
||||
"copied": "Copié !",
|
||||
@@ -66,8 +66,8 @@
|
||||
"disabled": "Désactivé",
|
||||
"showPassword": "Afficher le mot de passe",
|
||||
"hidePassword": "Cacher le mot de passe",
|
||||
"showDetails": "Show details",
|
||||
"hideDetails": "Hide details",
|
||||
"showDetails": "Afficher les détails",
|
||||
"hideDetails": "Masquer les détails",
|
||||
"copyToClipboard": "Copier dans le presse-papiers",
|
||||
"loadingEmails": "Chargement des emails...",
|
||||
"loadingTotpCodes": "Chargement des codes TOTP...",
|
||||
@@ -99,9 +99,9 @@
|
||||
"clientVersionNotSupported": "Cette version de l'extension de navigateur AliasVault n'est plus prise en charge par le serveur. Veuillez mettre à jour votre extension de navigateur à la dernière version.",
|
||||
"browserExtensionOutdated": "Cette extension de navigateur est obsolète et ne peut pas être utilisée pour accéder à ce coffre-fort. Veuillez la mettre à jour pour continuer.",
|
||||
"serverVersionNotSupported": "Le serveur d'AliasVault doit être mis à jour vers une version plus récente afin d'utiliser cette extension de navigateur. Veuillez contacter le support si vous avez besoin d'aide.",
|
||||
"serverVersionTooOld": "The AliasVault server needs to be updated to a newer version in order to use this feature. Please contact the server admin if you need help.",
|
||||
"serverVersionTooOld": "Le serveur AliasVault doit être mis à jour vers une version plus récente pour pouvoir utiliser cette fonctionnalité. Veuillez contacter l'administrateur du serveur si vous avez besoin d'aide.",
|
||||
"unknownError": "Une erreur inconnue s'est produite",
|
||||
"unknownErrorTryAgain": "An unknown error occurred. Please try again.",
|
||||
"unknownErrorTryAgain": "Une erreur inconnue s'est produite. Merci de réessayer.",
|
||||
"vaultNotAvailable": "Coffre non disponible",
|
||||
"vaultIsLocked": "Le coffre est verrouillé",
|
||||
"passwordChanged": "Votre mot de passe a changé depuis la dernière fois que vous vous êtes connecté. Veuillez vous reconnecter pour des raisons de sécurité."
|
||||
@@ -185,20 +185,20 @@
|
||||
"totpSecretPlaceholder": "Entrez le mot de passe à usage unique",
|
||||
"welcomeTitle": "Bienvenue dans AliasVault !",
|
||||
"welcomeDescription": "Pour utiliser l'extension de navigateur AliasVault : accédez à un site web et utilisez la fenêtre de saisie automatique AliasVault pour créer un nouvel identifiant.",
|
||||
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
|
||||
"noAttachmentsFound": "No credentials with attachments found",
|
||||
"noMatchingCredentials": "No matching credentials found",
|
||||
"noPasskeysFound": "Aucune clé d'accès n'a encore été créée. Les clés d'accès sont créés en visitant un site Web qui propose des clés d'accès comme méthode d'authentification.",
|
||||
"noAttachmentsFound": "Aucun identifiant avec des pièces jointes trouvé",
|
||||
"noMatchingCredentials": "Aucun identifiant correspondant trouvé",
|
||||
"createdAt": "Créé",
|
||||
"updatedAt": "Dernière mise à jour",
|
||||
"saveCredential": "Enregistrer les identifiants",
|
||||
"deleteCredentialTitle": "Supprimer les identifiants",
|
||||
"deleteCredentialConfirm": "Êtes-vous sûr de vouloir supprimer ces identifiants ? Cette action est irréversible.",
|
||||
"filters": {
|
||||
"all": "(All) Credentials",
|
||||
"passkeys": "Passkeys",
|
||||
"aliases": "Aliases",
|
||||
"userpass": "Passwords",
|
||||
"attachments": "Attachments"
|
||||
"all": "(Tous) Identifiants",
|
||||
"passkeys": "Clés d'accès",
|
||||
"aliases": "Alias",
|
||||
"userpass": "Mots de passe",
|
||||
"attachments": "Pièces jointes"
|
||||
},
|
||||
"randomAlias": "Alias aléatoire",
|
||||
"manual": "Manuel",
|
||||
@@ -242,13 +242,13 @@
|
||||
"enterEmailPrefix": "Entrez le préfixe de l'email"
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Ajouter un code 2FA",
|
||||
"instructions": "Entrez la clé secrète affichée par le site Web où vous souhaitez ajouter l'authentification à deux facteurs.",
|
||||
"nameOptional": "Nom (facultatif)",
|
||||
"secretKey": "Clé secrète",
|
||||
"saveToViewCode": "Enregistrer pour afficher le code",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Format de clé secrète invalide."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
@@ -290,7 +290,7 @@
|
||||
"openWebApp": "Ouvrir l’application web",
|
||||
"loggedIn": "Connecté(e)",
|
||||
"logout": "Se déconnecter",
|
||||
"lock": "Lock",
|
||||
"lock": "Verrouiller",
|
||||
"globalSettings": "Paramètres généraux",
|
||||
"autofillPopup": "Remplissage automatique de la popup",
|
||||
"activeOnAllSites": "Activé sur tous les sites (sauf si désactivé ci-dessous)",
|
||||
@@ -339,22 +339,22 @@
|
||||
"autofillSettings": "Paramètres du remplissage automatique",
|
||||
"clipboardSettings": "Paramètres du presse-papiers",
|
||||
"contextMenuSettings": "Paramètres du menu contextuel",
|
||||
"passkeySettings": "Passkey Settings",
|
||||
"passkeySettings": "Paramètres de la clé d'accès",
|
||||
"contextMenu": "Menu contextuel",
|
||||
"contextMenuEnabled": "Le menu contextuel est activé",
|
||||
"contextMenuDisabled": "Le menu contextuel est désactivé",
|
||||
"contextMenuDescription": "Faites un clic droit sur les champs de saisie pour accéder aux options d'AliasVault",
|
||||
"selectLanguage": "Sélectionner une langue",
|
||||
"serverConfiguration": "Server Configuration",
|
||||
"serverConfigurationDescription": "Configure the AliasVault server URL for self-hosted instances",
|
||||
"customApiUrl": "API URL",
|
||||
"customClientUrl": "Client URL",
|
||||
"apiUrlHint": "The API endpoint URL (usually client URL + /api)",
|
||||
"clientUrlHint": "The web interface URL of your self-hosted instance",
|
||||
"autofillSettingsDescription": "Enable or disable the autofill popup on web pages",
|
||||
"autofillEnabledDescription": "Autofill suggestions will appear on login forms",
|
||||
"autofillDisabledDescription": "Autofill suggestions are disabled globally",
|
||||
"languageSettings": "Language",
|
||||
"serverConfiguration": "Configuration du serveur",
|
||||
"serverConfigurationDescription": "Configurer l'URL du serveur AliasVault pour les instances auto-hébergées",
|
||||
"customApiUrl": "URL de l'API",
|
||||
"customClientUrl": "URL du client",
|
||||
"apiUrlHint": "L'URL du point de terminaison de l'API (généralement l'URL du client + /api)",
|
||||
"clientUrlHint": "L'URL de l'interface web de votre instance auto-hébergée",
|
||||
"autofillSettingsDescription": "Activer ou désactiver la popup de saisie automatique sur les pages web",
|
||||
"autofillEnabledDescription": "Les suggestions de saisie automatique apparaîtront dans les formulaires de connexion",
|
||||
"autofillDisabledDescription": "Les suggestions de saisie automatique sont désactivées globalement",
|
||||
"languageSettings": "Langue",
|
||||
"validation": {
|
||||
"apiUrlRequired": "L'URL de l'API est requise",
|
||||
"apiUrlInvalid": "Veuillez entrer une URL d'API valide",
|
||||
@@ -362,59 +362,59 @@
|
||||
"clientUrlInvalid": "Veuillez entrer une URL de client valide"
|
||||
},
|
||||
"unlockMethod": {
|
||||
"title": "Vault Unlock Method",
|
||||
"introText": "Choose how you want to unlock your vault. You can use your master password (always available) or set up a PIN code for faster access. After 3 failed PIN attempts, you'll need to use your master password.",
|
||||
"password": "Master Password",
|
||||
"pin": "PIN Code",
|
||||
"pinDescription": "Unlock vault with PIN code",
|
||||
"setupPin": "Setup PIN Code",
|
||||
"enterNewPinDescription": "Enter a PIN code consisting of minimum 6 digits",
|
||||
"confirmPin": "Confirm PIN",
|
||||
"confirmPinDescription": "Enter your PIN again to confirm",
|
||||
"invalidPinFormat": "Invalid PIN format",
|
||||
"pinMismatch": "PINs do not match",
|
||||
"incorrectPin": "Incorrect PIN. {{attemptsRemaining}} attempts remaining.",
|
||||
"incorrectPinSingular": "Incorrect PIN. 1 attempt remaining.",
|
||||
"enableSuccess": "PIN unlock enabled successfully!",
|
||||
"pinLocked": "PIN unlock has been disabled. Please use your master password to unlock your vault.",
|
||||
"pinSecurityWarning": "PIN unlock in the browser extension can be less secure than your master password, as PINs typically have lower entropy and may be brute-forced if your device is compromised. Use it only on devices you fully trust."
|
||||
"title": "Méthode de déverrouillage du coffre",
|
||||
"introText": "Choisissez comment vous voulez déverrouiller votre coffre. Vous pouvez utiliser votre mot de passe principal (toujours disponible) ou configurer un code PIN pour un accès rapide. Après 3 tentatives de PIN échouées, vous devrez utiliser votre mot de passe maître.",
|
||||
"password": "Mot de passe maître",
|
||||
"pin": "Code PIN",
|
||||
"pinDescription": "Déverrouiller le coffre avec le code PIN",
|
||||
"setupPin": "Configurer le code PIN",
|
||||
"enterNewPinDescription": "Entrez un code PIN composé d'au moins 6 chiffres",
|
||||
"confirmPin": "Confirmer le code PIN",
|
||||
"confirmPinDescription": "Saisissez à nouveau votre code PIN pour confirmer",
|
||||
"invalidPinFormat": "Format de code PIN invalide",
|
||||
"pinMismatch": "Les codes PIN ne correspondent pas",
|
||||
"incorrectPin": "Code PIN incorrect, {{attemptsRemaining}} tentatives restantes.",
|
||||
"incorrectPinSingular": "Code PIN incorrect. 1 tentative restante.",
|
||||
"enableSuccess": "Déverrouillage par code PIN activé avec succès!",
|
||||
"pinLocked": "Le déverrouillage par code PIN a été désactivé. Veuillez utiliser votre mot de passe maître pour déverrouiller votre coffre.",
|
||||
"pinSecurityWarning": "Le déverrouillage par code PIN dans l'extension du navigateur peut être moins sécurisé que votre mot de passe principal, car les codes PIN ont généralement une entropie plus faible et peuvent être trouvés par brute force si votre appareil est compromis. Ne l'utilisez que sur les appareils de confiance totale."
|
||||
}
|
||||
},
|
||||
"passkeys": {
|
||||
"passkey": "Passkey",
|
||||
"passkey": "Clé d'accès",
|
||||
"site": "Site",
|
||||
"displayName": "Name",
|
||||
"helpText": "Passkeys are created on the website when prompted. They cannot be manually edited. To remove this passkey, you can delete it from this credential. To replace this passkey or create a new one, visit the website and follow its prompts.",
|
||||
"passkeyMarkedForDeletion": "Passkey marked for deletion",
|
||||
"passkeyWillBeDeleted": "This passkey will be deleted when you save this credential.",
|
||||
"displayName": "Nom",
|
||||
"helpText": "Les clés d'accès sont créées sur le site Web lorsque vous y êtes invité. Elles ne peuvent pas être modifiées manuellement. Pour supprimer cette clé d'accès, vous pouvez la supprimer de cet identifiant. Pour remplacer cette clé d'accès ou en créer une nouvelle, visitez le site Web et suivez ses instructions.",
|
||||
"passkeyMarkedForDeletion": "Clé d'accès marquée pour suppression",
|
||||
"passkeyWillBeDeleted": "Cette clé d'accès sera supprimée lorsque vous enregistrerez cet identifiant.",
|
||||
"bypass": {
|
||||
"title": "Use Browser Passkey",
|
||||
"description": "How long would you like to use the browser's passkey provider for {{origin}}?",
|
||||
"thisTimeOnly": "This time only",
|
||||
"alwaysForSite": "Always for this site"
|
||||
"title": "Utiliser la clé d'accès du navigateur",
|
||||
"description": "Combien de temps souhaitez-vous utiliser le fournisseur de clé d'accès du navigateur pour {{origin}}?",
|
||||
"thisTimeOnly": "Cette fois seulement",
|
||||
"alwaysForSite": "Toujours pour ce site"
|
||||
},
|
||||
"authenticate": {
|
||||
"title": "Sign in with Passkey",
|
||||
"signInFor": "Sign in with passkey for",
|
||||
"selectPasskey": "Select a passkey to sign in:",
|
||||
"noPasskeysFound": "No passkeys found for this site",
|
||||
"useBrowserPasskey": "Use Browser Passkey"
|
||||
"title": "Se connecter avec une clé d’accès",
|
||||
"signInFor": "Se connecter avec une clé d’accès pour",
|
||||
"selectPasskey": "Sélectionnez une clé d'accès pour vous connecter:",
|
||||
"noPasskeysFound": "Aucune clé d'accès trouvée pour ce site",
|
||||
"useBrowserPasskey": "Utiliser la clé d'accès du navigateur"
|
||||
},
|
||||
"create": {
|
||||
"title": "Create Passkey",
|
||||
"createFor": "Create a new passkey for",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "Enter a name for this passkey",
|
||||
"createButton": "Create Passkey",
|
||||
"useBrowserPasskey": "Use Browser Passkey",
|
||||
"selectPasskeyToReplace": "Select a passkey to replace:",
|
||||
"createNewPasskey": "Create New Passkey",
|
||||
"replacingPasskey": "Replacing passkey: {{displayName}}",
|
||||
"confirmReplace": "Confirm Replace"
|
||||
"title": "Créer une clé d'accès",
|
||||
"createFor": "Créer une nouvelle clé d'accès pour",
|
||||
"titleLabel": "Titre",
|
||||
"titlePlaceholder": "Entrez un nom pour cette clé d'accès",
|
||||
"createButton": "Créer clé d'accès",
|
||||
"useBrowserPasskey": "Utiliser la clé d'accès du navigateur",
|
||||
"selectPasskeyToReplace": "Sélectionnez une clé d'accès à remplacer:",
|
||||
"createNewPasskey": "Créer une nouvelle clé d'accès",
|
||||
"replacingPasskey": "Remplacement de la clé d'accès : {{displayName}}",
|
||||
"confirmReplace": "Confirmer le remplacement"
|
||||
},
|
||||
"settings": {
|
||||
"passkeyProvider": "Passkey Provider",
|
||||
"passkeyProviderOn": "Passkey Provider on "
|
||||
"passkeyProvider": "Fournisseur de clés d'accès",
|
||||
"passkeyProviderOn": "Fournisseur de clé d'accès activé "
|
||||
}
|
||||
},
|
||||
"upgrade": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"password": "סיסמה",
|
||||
"passwordPlaceholder": "נא למלא את הסיסמה שלך",
|
||||
"rememberMe": "לזכור אותי",
|
||||
"loginButton": "Log in",
|
||||
"loginButton": "כניסה",
|
||||
"noAccount": "אין לך חשבון עדיין?",
|
||||
"createVault": "יצירת כספת חדשה",
|
||||
"twoFactorTitle": "נא למלא את קוד האימות מיישומון המאמת שלך.",
|
||||
@@ -57,7 +57,7 @@
|
||||
"next": "הבא",
|
||||
"use": "להשתמש",
|
||||
"delete": "מחיקה",
|
||||
"save": "Save",
|
||||
"save": "שמירה",
|
||||
"or": "או",
|
||||
"close": "סגירה",
|
||||
"copied": "הועתק!",
|
||||
@@ -244,8 +244,8 @@
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"nameOptional": "שם (רשות)",
|
||||
"secretKey": "מפתח סודי",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"next": "Dalej",
|
||||
"use": "Użyj",
|
||||
"delete": "Usuń",
|
||||
"save": "Save",
|
||||
"save": "Zapisz",
|
||||
"or": "lub",
|
||||
"close": "Zamknąć",
|
||||
"copied": "Skopiowano",
|
||||
@@ -238,22 +238,22 @@
|
||||
"publicEmailDescription": "Anonimowa, ale ograniczona prywatność. Treści e-mail są czytelne dla każdego, kto zna adres.",
|
||||
"useDomainChooser": "Użyj wybierania domen",
|
||||
"enterCustomDomain": "Wprowadź własną domenę",
|
||||
"enterFullEmail": "Wprowadź pełny adres e-mail",
|
||||
"enterFullEmail": "Wprowadź adres e-mail",
|
||||
"enterEmailPrefix": "Wprowadź prefiks e-mail"
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Dodaj kod 2FA",
|
||||
"instructions": "Wprowadź tajny klucz wyświetlony na stronie internetowej, na której chcesz dodać uwierzytelnianie dwuskładnikowe.",
|
||||
"nameOptional": "Nazwa (opcjonalnie)",
|
||||
"secretKey": "Tajny klucz",
|
||||
"saveToViewCode": "Zapisz, aby wyświetlić kod",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Nieprawidłowy format tajnego klucza."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
"title": "Skrzynka odbiorcza",
|
||||
"deleteEmailTitle": "Usuń adres e-mail",
|
||||
"deleteEmailTitle": "Usuń e-mail",
|
||||
"deleteEmailConfirm": "Czy na pewno chcesz trwale usunąć ten e-mail?",
|
||||
"from": "Od",
|
||||
"to": "Do",
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"next": "Далее",
|
||||
"use": "Использовать",
|
||||
"delete": "Удалить",
|
||||
"save": "Save",
|
||||
"save": "Сохранить",
|
||||
"or": "Или",
|
||||
"close": "Закрыть",
|
||||
"copied": "Скопировано!",
|
||||
@@ -242,13 +242,13 @@
|
||||
"enterEmailPrefix": "Введите префикс электронной почты"
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Добавить код 2FA",
|
||||
"instructions": "Введите секретный ключ, указанный на веб-сайте, где вы хотите добавить двухфакторную аутентификацию.",
|
||||
"nameOptional": "Имя (необязательно)",
|
||||
"secretKey": "Секретный ключ",
|
||||
"saveToViewCode": "Сохранить для просмотра кода",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Неверный формат секретного ключа."
|
||||
}
|
||||
},
|
||||
"emails": {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"loggedIn": "已登录",
|
||||
"loginWithMobile": "使用移动应用登录",
|
||||
"unlockWithMobile": "使用移动应用解锁",
|
||||
"scanQrCode": "Scan this QR code with your AliasVault mobile app to log in and unlock your vault.",
|
||||
"scanQrCode": "使用 AliasVault 移动端应用扫描该二维码,即可登录并解锁您的密码库。",
|
||||
"errors": {
|
||||
"invalidCode": "请输入有效的6位动态验证码。",
|
||||
"serverError": "无法连接到AliasVault服务器。请稍后重试,若问题依旧,请联系支持人员。",
|
||||
@@ -39,7 +39,7 @@
|
||||
"accountLocked": "由于多次尝试失败,账户已暂时锁定。",
|
||||
"networkError": "网络错误。请检查您的连接后重试。",
|
||||
"sessionExpired": "您的会话已过期。请重新登录。",
|
||||
"mobileLoginRequestExpired": "Mobile login request timed out. Please reload the page and try again."
|
||||
"mobileLoginRequestExpired": "移动端登录请求超时。请重新加载页面后重试。"
|
||||
}
|
||||
},
|
||||
"menu": {
|
||||
@@ -243,7 +243,7 @@
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "添加两步验证码",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"instructions": "输入要添加两步验证的网站显示的密钥。",
|
||||
"nameOptional": "名称(可选)",
|
||||
"secretKey": "密钥",
|
||||
"saveToViewCode": "保存以查看验证码",
|
||||
@@ -346,14 +346,14 @@
|
||||
"contextMenuDescription": "右键点击输入字段即可访问 AliasVault 选项",
|
||||
"selectLanguage": "选择语言",
|
||||
"serverConfiguration": "服务器配置",
|
||||
"serverConfigurationDescription": "Configure the AliasVault server URL for self-hosted instances",
|
||||
"serverConfigurationDescription": "为自托管实例配置 AliasVault 服务器 URL",
|
||||
"customApiUrl": "API URL",
|
||||
"customClientUrl": "客户端 URL",
|
||||
"apiUrlHint": "The API endpoint URL (usually client URL + /api)",
|
||||
"clientUrlHint": "The web interface URL of your self-hosted instance",
|
||||
"autofillSettingsDescription": "Enable or disable the autofill popup on web pages",
|
||||
"autofillEnabledDescription": "Autofill suggestions will appear on login forms",
|
||||
"autofillDisabledDescription": "Autofill suggestions are disabled globally",
|
||||
"apiUrlHint": "API 端点 URL(通常为客户端 URL + /api)",
|
||||
"clientUrlHint": "您自托管实例的 Web 界面 URL",
|
||||
"autofillSettingsDescription": "启用或禁用网页上的自动填充弹出窗口",
|
||||
"autofillEnabledDescription": "自动填充建议将显示在登录表单上",
|
||||
"autofillDisabledDescription": "自动填充建议已全局禁用",
|
||||
"languageSettings": "语言",
|
||||
"validation": {
|
||||
"apiUrlRequired": "API URL 为必填项",
|
||||
@@ -363,33 +363,33 @@
|
||||
},
|
||||
"unlockMethod": {
|
||||
"title": "密码库解锁方式",
|
||||
"introText": "Choose how you want to unlock your vault. You can use your master password (always available) or set up a PIN code for faster access. After 3 failed PIN attempts, you'll need to use your master password.",
|
||||
"introText": "选择您解锁密码库的方式。您可以使用主密码(始终可用),也可以设置 PIN 码以便快捷访问。PIN 码输入错误 3 次后,您需要使用主密码。",
|
||||
"password": "主密码",
|
||||
"pin": "PIN 码",
|
||||
"pinDescription": "使用 PIN 码解锁密码库",
|
||||
"setupPin": "设置 PIN 码",
|
||||
"enterNewPinDescription": "Enter a PIN code consisting of minimum 6 digits",
|
||||
"enterNewPinDescription": "请输入至少 6 位数的 PIN 码",
|
||||
"confirmPin": "确认 PIN",
|
||||
"confirmPinDescription": "再次输入您的 PIN 以确认",
|
||||
"invalidPinFormat": "PIN 格式无效",
|
||||
"pinMismatch": "PIN 不一致",
|
||||
"incorrectPin": "Incorrect PIN. {{attemptsRemaining}} attempts remaining.",
|
||||
"incorrectPinSingular": "Incorrect PIN. 1 attempt remaining.",
|
||||
"enableSuccess": "PIN unlock enabled successfully!",
|
||||
"pinLocked": "PIN unlock has been disabled. Please use your master password to unlock your vault.",
|
||||
"pinSecurityWarning": "PIN unlock in the browser extension can be less secure than your master password, as PINs typically have lower entropy and may be brute-forced if your device is compromised. Use it only on devices you fully trust."
|
||||
"incorrectPin": "PIN 码错误,剩余 {{attemptsRemaining}} 次尝试。",
|
||||
"incorrectPinSingular": "PIN 码错误,剩余 1 次尝试。",
|
||||
"enableSuccess": "PIN 码解锁已成功启用!",
|
||||
"pinLocked": "PIN 码解锁已禁用,请使用您的主密码解锁您的密码库。",
|
||||
"pinSecurityWarning": "浏览器扩展中的 PIN 码解锁可能不如您的主密码安全,因为 PIN 码的熵值通常较低,若您的设备遭到入侵,则可能被暴力破解。请仅在您完全信任的设备上使用该方式。"
|
||||
}
|
||||
},
|
||||
"passkeys": {
|
||||
"passkey": "通行密钥",
|
||||
"site": "网站",
|
||||
"displayName": "名称",
|
||||
"helpText": "Passkeys are created on the website when prompted. They cannot be manually edited. To remove this passkey, you can delete it from this credential. To replace this passkey or create a new one, visit the website and follow its prompts.",
|
||||
"helpText": "通行密钥会在网站提示时自动生成,无法手动编辑。要移除此通行密钥,您可以在此凭据中进行删除。要替换或创建新的通行密钥,请访问网站并按照其提示操作。",
|
||||
"passkeyMarkedForDeletion": "通行密钥已标记为删除",
|
||||
"passkeyWillBeDeleted": "This passkey will be deleted when you save this credential.",
|
||||
"passkeyWillBeDeleted": "保存此凭据后,此通行密钥将被删除。",
|
||||
"bypass": {
|
||||
"title": "使用浏览器通行密钥",
|
||||
"description": "How long would you like to use the browser's passkey provider for {{origin}}?",
|
||||
"description": "对于 {{origin}},您希望使用浏览器的密码提供程序多长时间?",
|
||||
"thisTimeOnly": "仅一次",
|
||||
"alwaysForSite": "始终为此网站"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export class AppInfo {
|
||||
/**
|
||||
* The current extension version. This should be updated with each release of the extension.
|
||||
*/
|
||||
public static readonly VERSION = '0.25.0';
|
||||
public static readonly VERSION = '0.26.0-alpha';
|
||||
|
||||
/**
|
||||
* The API version to send to the server (base semver without stage suffixes).
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
|
||||
import { filterCredentials } from '@/utils/credentialMatcher/CredentialMatcher';
|
||||
import type { Credential } from '@/utils/dist/shared/models/vault';
|
||||
|
||||
import { filterCredentials } from '../CredentialMatcher';
|
||||
|
||||
describe('CredentialMatcher - Credential URL Matching', () => {
|
||||
let testCredentials: Credential[];
|
||||
|
||||
@@ -36,6 +36,20 @@ declare class PasswordGenerator {
|
||||
private readonly uppercaseChars;
|
||||
private readonly numberChars;
|
||||
private readonly specialChars;
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
private readonly ambiguousChars;
|
||||
private length;
|
||||
private useLowercase;
|
||||
|
||||
@@ -39,7 +39,21 @@ var PasswordGenerator = class {
|
||||
this.uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
this.numberChars = "0123456789";
|
||||
this.specialChars = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
this.ambiguousChars = "Il1O0o";
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
this.ambiguousChars = "Il1O0oZzSsBbGg2568|[]{}()<>;:,.`'\"_-";
|
||||
this.length = 18;
|
||||
this.useLowercase = true;
|
||||
this.useUppercase = true;
|
||||
|
||||
@@ -13,7 +13,21 @@ var PasswordGenerator = class {
|
||||
this.uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
this.numberChars = "0123456789";
|
||||
this.specialChars = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
this.ambiguousChars = "Il1O0o";
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
this.ambiguousChars = "Il1O0oZzSsBbGg2568|[]{}()<>;:,.`'\"_-";
|
||||
this.length = 18;
|
||||
this.useLowercase = true;
|
||||
this.useUppercase = true;
|
||||
|
||||
@@ -27,18 +27,16 @@ export class FormFiller {
|
||||
* @param credential The credential to fill the form with.
|
||||
*/
|
||||
public async fillFields(credential: Credential): Promise<void> {
|
||||
// Perform security validation before filling any fields
|
||||
if (!await this.validateFormSecurity()) {
|
||||
console.warn('[AliasVault Security] Autofill blocked due to security validation failure');
|
||||
return;
|
||||
}
|
||||
// Perform security validation to identify safe fields
|
||||
const securityResults = await this.validateFormSecurity();
|
||||
|
||||
/*
|
||||
* Fill fields sequentially to avoid race conditions and conflicts.
|
||||
* Some websites have event handlers that can interfere with parallel filling.
|
||||
* Only fill fields that passed security validation.
|
||||
*/
|
||||
await this.fillBasicFields(credential);
|
||||
await this.fillPasswordFields(credential);
|
||||
await this.fillBasicFields(credential, securityResults);
|
||||
await this.fillPasswordFields(credential, securityResults);
|
||||
|
||||
this.fillBirthdateFields(credential);
|
||||
this.fillGenderFields(credential);
|
||||
@@ -51,12 +49,18 @@ export class FormFiller {
|
||||
* - Form field obstruction via overlays
|
||||
* - Suspicious element positioning
|
||||
* - Multiple forms with identical fields (potential decoy attacks)
|
||||
*
|
||||
* @returns A map of field elements to their security validation result (true = safe, false = unsafe)
|
||||
*/
|
||||
private async validateFormSecurity(): Promise<boolean> {
|
||||
private async validateFormSecurity(): Promise<Map<HTMLElement, boolean>> {
|
||||
const results = new Map<HTMLElement, boolean>();
|
||||
|
||||
try {
|
||||
// Skip security validation in test environments where browser APIs may not be available
|
||||
if (typeof window === 'undefined' || typeof MouseEvent === 'undefined') {
|
||||
return true;
|
||||
// In test environments, mark all fields as safe
|
||||
this.getAllFormFields().forEach(field => results.set(field, true));
|
||||
return results;
|
||||
}
|
||||
|
||||
// 1. Check page-wide security using ClickValidator (detects body/HTML opacity tricks)
|
||||
@@ -68,30 +72,40 @@ export class FormFiller {
|
||||
});
|
||||
// Note: isTrusted is read-only and set by the browser
|
||||
|
||||
if (!await this.clickValidator.validateClick(dummyEvent)) {
|
||||
console.warn('[AliasVault Security] Form autofill blocked: Page-wide attack detected');
|
||||
return false;
|
||||
const pageWideSecure = await this.clickValidator.validateClick(dummyEvent);
|
||||
if (!pageWideSecure) {
|
||||
console.warn('[AliasVault Security] Page-wide attack detected - blocking all autofill');
|
||||
// Mark all fields as unsafe
|
||||
this.getAllFormFields().forEach(field => results.set(field, false));
|
||||
return results;
|
||||
}
|
||||
|
||||
// 2. Check form field obstruction and positioning
|
||||
// 2. Check for suspicious form duplication (decoy attack)
|
||||
const hasDecoyForms = this.detectDecoyForms();
|
||||
if (hasDecoyForms) {
|
||||
console.warn('[AliasVault Security] Multiple suspicious forms detected - blocking all autofill');
|
||||
// Mark all fields as unsafe
|
||||
this.getAllFormFields().forEach(field => results.set(field, false));
|
||||
return results;
|
||||
}
|
||||
|
||||
// 3. Check individual form field obstruction and positioning
|
||||
const formFields = this.getAllFormFields();
|
||||
for (const field of formFields) {
|
||||
if (!this.validateFieldSecurity(field)) {
|
||||
console.warn('[AliasVault Security] Form autofill blocked: Field obstruction detected', field);
|
||||
return false;
|
||||
const isFieldSecure = this.validateFieldSecurity(field);
|
||||
results.set(field, isFieldSecure);
|
||||
|
||||
if (!isFieldSecure) {
|
||||
console.warn('[AliasVault Security] Field failed security check (will be skipped):', field);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check for suspicious form duplication (decoy attack)
|
||||
if (this.detectDecoyForms()) {
|
||||
console.warn('[AliasVault Security] Form autofill blocked: Multiple suspicious forms detected');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('[AliasVault Security] Form security validation error:', error);
|
||||
return false; // Fail safely - block autofill if validation fails
|
||||
// Fail safely - mark all fields as unsafe if validation fails
|
||||
this.getAllFormFields().forEach(field => results.set(field, false));
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,13 +305,14 @@ export class FormFiller {
|
||||
/**
|
||||
* Fill the basic fields of the form.
|
||||
* @param credential The credential to fill the form with.
|
||||
* @param securityResults Security validation results for each field.
|
||||
*/
|
||||
private async fillBasicFields(credential: Credential): Promise<void> {
|
||||
if (this.form.usernameField && credential.Username) {
|
||||
private async fillBasicFields(credential: Credential, securityResults: Map<HTMLElement, boolean>): Promise<void> {
|
||||
if (this.form.usernameField && credential.Username && securityResults.get(this.form.usernameField) !== false) {
|
||||
await this.fillTextFieldWithTyping(this.form.usernameField, credential.Username);
|
||||
}
|
||||
|
||||
if (this.form.emailField && (credential.Alias?.Email !== undefined || credential.Username !== undefined)) {
|
||||
if (this.form.emailField && (credential.Alias?.Email !== undefined || credential.Username !== undefined) && securityResults.get(this.form.emailField) !== false) {
|
||||
if (credential.Alias?.Email) {
|
||||
this.setElementValue(this.form.emailField, credential.Alias.Email);
|
||||
this.triggerInputEvents(this.form.emailField);
|
||||
@@ -317,7 +332,7 @@ export class FormFiller {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.form.emailConfirmField && credential.Alias?.Email) {
|
||||
if (this.form.emailConfirmField && credential.Alias?.Email && securityResults.get(this.form.emailConfirmField) !== false) {
|
||||
this.setElementValue(this.form.emailConfirmField, credential.Alias.Email);
|
||||
this.triggerInputEvents(this.form.emailConfirmField);
|
||||
}
|
||||
@@ -388,19 +403,20 @@ export class FormFiller {
|
||||
* Fill password fields sequentially to avoid visual conflicts.
|
||||
* First fills the main password field, then the confirm field if present.
|
||||
* @param credential The credential containing the password.
|
||||
* @param securityResults Security validation results for each field.
|
||||
*/
|
||||
private async fillPasswordFields(credential: Credential): Promise<void> {
|
||||
private async fillPasswordFields(credential: Credential, securityResults: Map<HTMLElement, boolean>): Promise<void> {
|
||||
if (!credential.Password) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill main password field first
|
||||
if (this.form.passwordField) {
|
||||
// Fill main password field first (only if it passed security check)
|
||||
if (this.form.passwordField && securityResults.get(this.form.passwordField) !== false) {
|
||||
await this.fillPasswordField(this.form.passwordField, credential.Password);
|
||||
}
|
||||
|
||||
// Then fill password confirm field after main field is complete
|
||||
if (this.form.passwordConfirmField) {
|
||||
// Then fill password confirm field after main field is complete (only if it passed security check)
|
||||
if (this.form.passwordConfirmField && securityResults.get(this.form.passwordConfirmField) !== false) {
|
||||
await this.fillPasswordField(this.form.passwordConfirmField, credential.Password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export default defineConfig({
|
||||
return {
|
||||
name: "AliasVault",
|
||||
description: "AliasVault Browser AutoFill Extension. Keeping your personal information private.",
|
||||
version: "0.25.0",
|
||||
version: "0.26.0",
|
||||
content_security_policy: {
|
||||
extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
|
||||
},
|
||||
|
||||
@@ -93,8 +93,8 @@ android {
|
||||
applicationId 'net.aliasvault.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2500901
|
||||
versionName "0.25.0"
|
||||
versionCode 2600100
|
||||
versionName "0.26.0-alpha"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -202,6 +202,9 @@ dependencies {
|
||||
// Add modern SQLite library with VACUUM INTO and backup API support
|
||||
implementation("com.github.requery:sqlite-android:3.49.0")
|
||||
|
||||
// Add ZXing library for QR code scanning (F-Droid compatible)
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
|
||||
|
||||
// Test dependencies
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.0.0'
|
||||
|
||||
@@ -40,13 +40,13 @@
|
||||
<!-- Passkey Authentication Activity -->
|
||||
<activity
|
||||
android:name=".credentialprovider.PasskeyAuthenticationActivity"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:theme="@style/PasskeyRegistrationTheme" />
|
||||
|
||||
<!-- Passkey Registration Activity -->
|
||||
<activity
|
||||
android:name=".credentialprovider.PasskeyRegistrationActivity"
|
||||
android:exported="true"
|
||||
android:exported="false"
|
||||
android:theme="@style/PasskeyRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:fitsSystemWindows="true" />
|
||||
@@ -58,6 +58,13 @@
|
||||
android:theme="@style/PasskeyRegistrationTheme"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<!-- QR Scanner Activity -->
|
||||
<activity
|
||||
android:name=".qrscanner.QRScannerActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/zxing_CaptureTheme"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
@@ -113,6 +113,8 @@ class MainActivity : ReactActivity() {
|
||||
handlePinUnlockResult(resultCode, data)
|
||||
} else if (requestCode == net.aliasvault.app.nativevaultmanager.NativeVaultManager.PIN_SETUP_REQUEST_CODE) {
|
||||
handlePinSetupResult(resultCode, data)
|
||||
} else if (requestCode == net.aliasvault.app.nativevaultmanager.NativeVaultManager.QR_SCANNER_REQUEST_CODE) {
|
||||
handleQRScannerResult(resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +196,31 @@ class MainActivity : ReactActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle QR scanner result.
|
||||
* @param resultCode The result code from the QR scanner activity.
|
||||
* @param data The intent data containing the scanned QR code.
|
||||
*/
|
||||
private fun handleQRScannerResult(resultCode: Int, data: Intent?) {
|
||||
val promise = net.aliasvault.app.nativevaultmanager.NativeVaultManager.pendingActivityResultPromise
|
||||
net.aliasvault.app.nativevaultmanager.NativeVaultManager.pendingActivityResultPromise = null
|
||||
|
||||
if (promise == null) {
|
||||
return
|
||||
}
|
||||
|
||||
when (resultCode) {
|
||||
RESULT_OK -> {
|
||||
val scannedData = data?.getStringExtra("SCAN_RESULT")
|
||||
promise.resolve(scannedData)
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
promise.resolve(null)
|
||||
}
|
||||
else -> {
|
||||
promise.resolve(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package net.aliasvault.app.credentialprovider
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
@@ -8,6 +9,10 @@ import android.widget.TextView
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.credentials.provider.ProviderGetCredentialRequest
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.aliasvault.app.R
|
||||
import net.aliasvault.app.utils.Helpers
|
||||
import net.aliasvault.app.vaultstore.VaultStore
|
||||
@@ -109,6 +114,19 @@ class PasskeyAuthenticationActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loading message displayed to the user.
|
||||
*/
|
||||
private fun updateLoadingMessage(messageResId: Int) {
|
||||
runOnUiThread {
|
||||
try {
|
||||
findViewById<TextView>(R.id.loadingMessage)?.text = getString(messageResId)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Could not update loading message", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the passkey authentication request and generate assertion.
|
||||
* Called after authentication (biometric or PIN) succeeds and vault is unlocked.
|
||||
@@ -120,107 +138,149 @@ class PasskeyAuthenticationActivity : FragmentActivity() {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
try {
|
||||
// Extract passkey ID from intent
|
||||
val passkeyIdString = intent.getStringExtra(
|
||||
AliasVaultCredentialProviderService.EXTRA_PASSKEY_ID,
|
||||
)
|
||||
if (passkeyIdString == null) {
|
||||
Log.e(TAG, "No passkey ID in intent")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val passkeyId = UUID.fromString(passkeyIdString.uppercase())
|
||||
|
||||
// Get database connection from vault (should be unlocked at this point)
|
||||
val db = vaultStore.database
|
||||
if (db == null) {
|
||||
Log.e(TAG, "Database not available - vault may not be unlocked")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val passkey = vaultStore.getPasskeyById(passkeyId, db)
|
||||
if (passkey == null) {
|
||||
Log.e(TAG, "Passkey not found: $passkeyId")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val requestJson = intent.getStringExtra(
|
||||
AliasVaultCredentialProviderService.EXTRA_REQUEST_JSON,
|
||||
) ?: ""
|
||||
val requestObj = JSONObject(requestJson)
|
||||
|
||||
// Extract clientDataHash from the calling app's request
|
||||
// Browsers (Chrome, Firefox, Edge, etc.) provide this, native apps typically don't
|
||||
val providedClientDataHash: ByteArray? = providerRequest.credentialOptions
|
||||
.filterIsInstance<androidx.credentials.GetPublicKeyCredentialOption>()
|
||||
.firstOrNull()?.clientDataHash
|
||||
|
||||
// Determine clientDataHash and clientDataJson based on what caller provided
|
||||
val clientDataHash: ByteArray
|
||||
val clientDataJson: String?
|
||||
if (providedClientDataHash != null) {
|
||||
// Browser provided clientDataHash - use it directly
|
||||
// The browser has its own clientDataJSON with the web origin
|
||||
clientDataHash = providedClientDataHash
|
||||
clientDataJson = null
|
||||
} else {
|
||||
// Native app scenario - build clientDataJSON ourselves and hash it
|
||||
val challenge = requestObj.optString("challenge", "")
|
||||
val origin = requestObj.optString("origin", "https://${passkey.rpId}")
|
||||
val json = buildClientDataJson(challenge, origin)
|
||||
clientDataHash = sha256(json.toByteArray(Charsets.UTF_8))
|
||||
clientDataJson = json
|
||||
}
|
||||
|
||||
// Use PasskeyAuthenticator.getAssertion for signing
|
||||
val credentialId = PasskeyHelper.guidToBytes(passkey.id.toString())
|
||||
val prfInputs = extractPrfInputs(requestObj)
|
||||
val assertion = PasskeyAuthenticator.getAssertion(
|
||||
credentialId = credentialId,
|
||||
clientDataHash = clientDataHash,
|
||||
rpId = passkey.rpId,
|
||||
privateKeyJWK = passkey.privateKey,
|
||||
userId = passkey.userHandle,
|
||||
uvPerformed = true,
|
||||
prfInputs = prfInputs,
|
||||
prfSecret = passkey.prfKey,
|
||||
)
|
||||
|
||||
// Build response JSON
|
||||
val response = buildPublicKeyCredentialResponse(
|
||||
assertion = assertion,
|
||||
clientDataJson = clientDataJson,
|
||||
)
|
||||
|
||||
val resultIntent = Intent()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
PendingIntentHandler.setGetCredentialResponse(resultIntent, response)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting credential response", e)
|
||||
try {
|
||||
PendingIntentHandler.setGetCredentialException(
|
||||
resultIntent,
|
||||
androidx.credentials.exceptions.GetCredentialUnknownException("Failed to generate assertion: ${e.message}"),
|
||||
)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
} catch (e2: Exception) {
|
||||
Log.e(TAG, "Error setting exception", e2)
|
||||
// Show retrieving status to user
|
||||
updateLoadingMessage(R.string.passkey_retrieving)
|
||||
|
||||
// Extract passkey ID from intent
|
||||
val passkeyIdString = intent.getStringExtra(
|
||||
AliasVaultCredentialProviderService.EXTRA_PASSKEY_ID,
|
||||
)
|
||||
if (passkeyIdString == null) {
|
||||
Log.e(TAG, "No passkey ID in intent")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return@launch
|
||||
}
|
||||
|
||||
val passkeyId = UUID.fromString(passkeyIdString.uppercase())
|
||||
|
||||
// Get database connection from vault
|
||||
val db = vaultStore.database
|
||||
if (db == null) {
|
||||
Log.e(TAG, "Database not available - vault may not be unlocked")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return@launch
|
||||
}
|
||||
|
||||
val passkey = vaultStore.getPasskeyById(passkeyId, db)
|
||||
if (passkey == null) {
|
||||
Log.e(TAG, "Passkey not found: $passkeyId")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
return@launch
|
||||
}
|
||||
|
||||
val requestJson = intent.getStringExtra(
|
||||
AliasVaultCredentialProviderService.EXTRA_REQUEST_JSON,
|
||||
) ?: ""
|
||||
val requestObj = JSONObject(requestJson)
|
||||
|
||||
// Extract clientDataHash from the calling app's request
|
||||
// Browsers (Chrome, Firefox, Edge, etc.) provide this, native apps typically don't
|
||||
val providedClientDataHash: ByteArray? = providerRequest.credentialOptions
|
||||
.filterIsInstance<androidx.credentials.GetPublicKeyCredentialOption>()
|
||||
.firstOrNull()?.clientDataHash
|
||||
|
||||
// Show verifying status to user
|
||||
updateLoadingMessage(R.string.passkey_verifying)
|
||||
|
||||
// Verify origin of the calling app
|
||||
val originVerifier = OriginVerifier()
|
||||
val callingAppInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
providerRequest.callingAppInfo
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Run origin verification on IO thread
|
||||
val originResult = withContext(Dispatchers.IO) {
|
||||
originVerifier.verifyOrigin(
|
||||
callingAppInfo = callingAppInfo,
|
||||
requestedRpId = passkey.rpId,
|
||||
)
|
||||
}
|
||||
|
||||
val verifiedOrigin: String
|
||||
val isPrivilegedCaller: Boolean
|
||||
|
||||
when (originResult) {
|
||||
is OriginVerifier.OriginResult.Success -> {
|
||||
verifiedOrigin = originResult.origin
|
||||
isPrivilegedCaller = originResult.isPrivileged
|
||||
Log.d(TAG, "Origin verified: $verifiedOrigin (privileged: $isPrivilegedCaller)")
|
||||
}
|
||||
is OriginVerifier.OriginResult.Failure -> {
|
||||
Log.e(TAG, "Origin verification failed: ${originResult.reason}")
|
||||
showError("Security error: ${originResult.reason}")
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
// Show authenticating status to user
|
||||
updateLoadingMessage(R.string.passkey_authenticating)
|
||||
|
||||
// Determine clientDataHash and clientDataJson based on what caller provided
|
||||
val clientDataHash: ByteArray
|
||||
val clientDataJson: String?
|
||||
if (providedClientDataHash != null && isPrivilegedCaller) {
|
||||
// Browser provided clientDataHash - use it directly
|
||||
clientDataHash = providedClientDataHash
|
||||
clientDataJson = null
|
||||
} else {
|
||||
// Native app scenario - build clientDataJSON ourselves
|
||||
val challenge = requestObj.optString("challenge", "")
|
||||
val json = buildClientDataJson(challenge, verifiedOrigin)
|
||||
clientDataHash = sha256(json.toByteArray(Charsets.UTF_8))
|
||||
clientDataJson = json
|
||||
}
|
||||
|
||||
// Use PasskeyAuthenticator.getAssertion for signing
|
||||
val credentialId = PasskeyHelper.guidToBytes(passkey.id.toString())
|
||||
val prfInputs = extractPrfInputs(requestObj)
|
||||
val assertion = PasskeyAuthenticator.getAssertion(
|
||||
credentialId = credentialId,
|
||||
clientDataHash = clientDataHash,
|
||||
rpId = passkey.rpId,
|
||||
privateKeyJWK = passkey.privateKey,
|
||||
userId = passkey.userHandle,
|
||||
uvPerformed = true,
|
||||
prfInputs = prfInputs,
|
||||
prfSecret = passkey.prfKey,
|
||||
)
|
||||
|
||||
// Build response JSON
|
||||
val response = buildPublicKeyCredentialResponse(
|
||||
assertion = assertion,
|
||||
clientDataJson = clientDataJson,
|
||||
)
|
||||
|
||||
val resultIntent = Intent()
|
||||
try {
|
||||
PendingIntentHandler.setGetCredentialResponse(resultIntent, response)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting credential response", e)
|
||||
try {
|
||||
PendingIntentHandler.setGetCredentialException(
|
||||
resultIntent,
|
||||
androidx.credentials.exceptions.GetCredentialUnknownException("Failed to generate assertion: ${e.message}"),
|
||||
)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
} catch (e2: Exception) {
|
||||
Log.e(TAG, "Error setting exception", e2)
|
||||
setResult(RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error processing authentication request", e)
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error processing authentication request", e)
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -246,8 +246,11 @@ class PasskeyFormFragment : Fragment() {
|
||||
val requestObj = JSONObject(viewModel.requestJson)
|
||||
val challenge = requestObj.optString("challenge", "")
|
||||
|
||||
// Use origin from the request, or fallback to RP id
|
||||
val requestOrigin = viewModel.origin ?: ("https://" + viewModel.rpId)
|
||||
// Use the origin set by PasskeyRegistrationActivity
|
||||
val requestOrigin = viewModel.origin
|
||||
?: throw net.aliasvault.app.exceptions.PasskeyOperationException(
|
||||
"Origin not available",
|
||||
)
|
||||
|
||||
// Extract PRF inputs if present
|
||||
val prfInputs = extractPrfInputs(requestObj)
|
||||
@@ -438,8 +441,9 @@ class PasskeyFormFragment : Fragment() {
|
||||
val requestObj = JSONObject(viewModel.requestJson)
|
||||
val challenge = requestObj.optString("challenge", "")
|
||||
|
||||
// Use origin from the request
|
||||
val requestOrigin = viewModel.origin ?: throw PasskeyOperationException("Origin not available")
|
||||
// Use the origin set by PasskeyRegistrationActivity
|
||||
val requestOrigin = viewModel.origin
|
||||
?: throw PasskeyOperationException("Origin not available")
|
||||
|
||||
// Extract PRF inputs if present
|
||||
val prfInputs = extractPrfInputs(requestObj)
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package net.aliasvault.app.credentialprovider
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.aliasvault.app.R
|
||||
import net.aliasvault.app.credentialprovider.models.PasskeyRegistrationViewModel
|
||||
import net.aliasvault.app.utils.Helpers
|
||||
@@ -65,10 +71,9 @@ class PasskeyRegistrationActivity : FragmentActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get requestJson, clientDataHash, and origin from the request
|
||||
// Get requestJson, clientDataHash from the request
|
||||
viewModel.requestJson = createRequest.requestJson
|
||||
viewModel.clientDataHash = createRequest.clientDataHash
|
||||
viewModel.origin = createRequest.origin
|
||||
|
||||
// Parse request JSON to extract RP ID and user info
|
||||
val requestObj = JSONObject(viewModel.requestJson)
|
||||
@@ -102,29 +107,18 @@ class PasskeyRegistrationActivity : FragmentActivity() {
|
||||
null
|
||||
}
|
||||
|
||||
// Show loading screen while unlock is in progress
|
||||
// Show loading screen while verification and unlock are in progress
|
||||
setContentView(R.layout.activity_loading)
|
||||
|
||||
// Initialize unlock coordinator
|
||||
unlockCoordinator = UnlockCoordinator(
|
||||
activity = this,
|
||||
vaultStore = vaultStore,
|
||||
onUnlocked = {
|
||||
// Vault unlocked successfully - proceed with passkey registration
|
||||
proceedWithPasskeyRegistration(savedInstanceState)
|
||||
},
|
||||
onCancelled = {
|
||||
// User cancelled unlock
|
||||
finish()
|
||||
},
|
||||
onError = { errorMessage ->
|
||||
// Error during unlock
|
||||
showError(errorMessage)
|
||||
},
|
||||
)
|
||||
// Get calling app info for origin verification
|
||||
val callingAppInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
providerRequest.callingAppInfo
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Start the unlock flow
|
||||
unlockCoordinator.startUnlockFlow()
|
||||
// Verify origin and start unlock flow
|
||||
verifyOriginAndStartUnlock(callingAppInfo, savedInstanceState)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onCreate", e)
|
||||
finish()
|
||||
@@ -140,6 +134,76 @@ class PasskeyRegistrationActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the loading message displayed to the user.
|
||||
*/
|
||||
private fun updateLoadingMessage(messageResId: Int) {
|
||||
runOnUiThread {
|
||||
try {
|
||||
findViewById<TextView>(R.id.loadingMessage)?.text = getString(messageResId)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Could not update loading message", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify origin on background thread and start unlock flow if successful.
|
||||
*/
|
||||
private fun verifyOriginAndStartUnlock(callingAppInfo: CallingAppInfo?, savedInstanceState: Bundle?) {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
// Show verifying status to user (network call may happen)
|
||||
updateLoadingMessage(R.string.passkey_verifying)
|
||||
|
||||
// Run origin verification on IO thread (asset links fetch requires network)
|
||||
val originVerifier = OriginVerifier()
|
||||
val originResult = withContext(Dispatchers.IO) {
|
||||
originVerifier.verifyOrigin(
|
||||
callingAppInfo = callingAppInfo,
|
||||
requestedRpId = viewModel.rpId,
|
||||
)
|
||||
}
|
||||
|
||||
when (originResult) {
|
||||
is OriginVerifier.OriginResult.Success -> {
|
||||
viewModel.origin = originResult.origin
|
||||
viewModel.isPrivilegedCaller = originResult.isPrivileged
|
||||
Log.d(TAG, "Origin verified: ${originResult.origin} (privileged: ${originResult.isPrivileged})")
|
||||
|
||||
// Initialize unlock coordinator
|
||||
unlockCoordinator = UnlockCoordinator(
|
||||
activity = this@PasskeyRegistrationActivity,
|
||||
vaultStore = vaultStore,
|
||||
onUnlocked = {
|
||||
// Vault unlocked successfully - proceed with passkey registration
|
||||
proceedWithPasskeyRegistration(savedInstanceState)
|
||||
},
|
||||
onCancelled = {
|
||||
// User cancelled unlock
|
||||
finish()
|
||||
},
|
||||
onError = { errorMessage ->
|
||||
// Error during unlock
|
||||
showError(errorMessage)
|
||||
},
|
||||
)
|
||||
|
||||
// Start the unlock flow
|
||||
unlockCoordinator.startUnlockFlow()
|
||||
}
|
||||
is OriginVerifier.OriginResult.Failure -> {
|
||||
Log.e(TAG, "Origin verification failed: ${originResult.reason}")
|
||||
showError("Security error: ${originResult.reason}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error verifying origin", e)
|
||||
showError("Error verifying application: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed with passkey registration after authentication (biometric or PIN).
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,9 @@ class PasskeyRegistrationViewModel : ViewModel() {
|
||||
/** The origin URL of the passkey request. */
|
||||
var origin: String? = null
|
||||
|
||||
/** Whether the caller is a privileged app (browser). */
|
||||
var isPrivilegedCaller: Boolean = false
|
||||
|
||||
/** The relying party identifier. */
|
||||
var rpId: String = ""
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.aliasvault.app.qrscanner.QRScannerActivity
|
||||
import net.aliasvault.app.vaultstore.VaultStore
|
||||
import net.aliasvault.app.vaultstore.VaultSyncError
|
||||
import net.aliasvault.app.vaultstore.keystoreprovider.AndroidKeystoreProvider
|
||||
@@ -63,6 +64,11 @@ class NativeVaultManager(reactContext: ReactApplicationContext) :
|
||||
*/
|
||||
const val PIN_SETUP_REQUEST_CODE = 1002
|
||||
|
||||
/**
|
||||
* Request code for QR scanner activity.
|
||||
*/
|
||||
const val QR_SCANNER_REQUEST_CODE = 1003
|
||||
|
||||
/**
|
||||
* Static holder for the pending promise from showPinUnlock.
|
||||
* This allows MainActivity to resolve/reject the promise directly without
|
||||
@@ -1436,6 +1442,42 @@ class NativeVaultManager(reactContext: ReactApplicationContext) :
|
||||
* @param subtitle The subtitle for authentication. If null or empty, uses default.
|
||||
* @param promise The promise to resolve with authentication result.
|
||||
*/
|
||||
@ReactMethod
|
||||
override fun scanQRCode(prefixes: ReadableArray?, statusText: String?, promise: Promise) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val activity = currentActivity
|
||||
if (activity == null) {
|
||||
promise.reject("NO_ACTIVITY", "No activity available", null)
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Store promise for later resolution by MainActivity
|
||||
pendingActivityResultPromise = promise
|
||||
|
||||
// Launch QR scanner activity with optional prefixes and status text
|
||||
val intent = Intent(activity, QRScannerActivity::class.java)
|
||||
if (prefixes != null && prefixes.size() > 0) {
|
||||
val prefixList = ArrayList<String>()
|
||||
for (i in 0 until prefixes.size()) {
|
||||
val prefix = prefixes.getString(i)
|
||||
if (prefix != null) {
|
||||
prefixList.add(prefix)
|
||||
}
|
||||
}
|
||||
intent.putStringArrayListExtra(QRScannerActivity.EXTRA_PREFIXES, prefixList)
|
||||
}
|
||||
if (statusText != null && statusText.isNotEmpty()) {
|
||||
intent.putExtra(QRScannerActivity.EXTRA_STATUS_TEXT, statusText)
|
||||
}
|
||||
activity.startActivityForResult(intent, QR_SCANNER_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to launch QR scanner", e)
|
||||
promise.reject("SCANNER_ERROR", "Failed to launch QR scanner: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
override fun authenticateUser(title: String?, subtitle: String?, promise: Promise) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package net.aliasvault.app.qrscanner
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.google.zxing.ResultPoint
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback
|
||||
import com.journeyapps.barcodescanner.BarcodeResult
|
||||
import com.journeyapps.barcodescanner.CaptureManager
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||
|
||||
/**
|
||||
* Activity for scanning QR codes using ZXing.
|
||||
*/
|
||||
class QRScannerActivity : Activity() {
|
||||
private lateinit var barcodeView: DecoratedBarcodeView
|
||||
private lateinit var capture: CaptureManager
|
||||
private var hasScanned = false
|
||||
private var prefixes: List<String>? = null
|
||||
|
||||
companion object {
|
||||
/** Intent extra key for prefixes. */
|
||||
const val EXTRA_PREFIXES = "EXTRA_PREFIXES"
|
||||
|
||||
/** Intent extra key for status text. */
|
||||
const val EXTRA_STATUS_TEXT = "EXTRA_STATUS_TEXT"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Get prefixes from intent if provided
|
||||
prefixes = intent.getStringArrayListExtra(EXTRA_PREFIXES)
|
||||
|
||||
// Get status text from intent, default to "Scan QR code" if not provided
|
||||
val statusText = intent.getStringExtra(EXTRA_STATUS_TEXT)?.takeIf { it.isNotEmpty() } ?: "Scan QR code"
|
||||
|
||||
// Create and configure barcode view
|
||||
barcodeView = DecoratedBarcodeView(this)
|
||||
barcodeView.setStatusText(statusText)
|
||||
setContentView(barcodeView)
|
||||
|
||||
// Initialize capture manager
|
||||
capture = CaptureManager(this, barcodeView)
|
||||
capture.initializeFromIntent(intent, savedInstanceState)
|
||||
|
||||
// Set custom callback to add visual feedback
|
||||
barcodeView.decodeContinuous(object : BarcodeCallback {
|
||||
override fun barcodeResult(result: BarcodeResult?) {
|
||||
if (result != null && !hasScanned) {
|
||||
val scannedText = result.text
|
||||
|
||||
// Check if prefixes filter is enabled
|
||||
if (prefixes != null && prefixes!!.isNotEmpty()) {
|
||||
// Check if the scanned code starts with any of the accepted prefixes
|
||||
val hasValidPrefix = prefixes!!.any { prefix ->
|
||||
scannedText.startsWith(prefix)
|
||||
}
|
||||
|
||||
if (!hasValidPrefix) {
|
||||
// Invalid QR code - continue scanning without setting hasScanned
|
||||
// Note: ZXing library continues scanning automatically
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Valid QR code
|
||||
hasScanned = true
|
||||
|
||||
// Show success animation
|
||||
showScanSuccessAnimation()
|
||||
|
||||
// Pause scanning
|
||||
barcodeView.pause()
|
||||
|
||||
// Set result and finish after animation
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("SCAN_RESULT", scannedText)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
|
||||
// Delay finish to allow animation to complete
|
||||
barcodeView.postDelayed({
|
||||
finish()
|
||||
}, 400) // 400ms delay for animation
|
||||
}
|
||||
}
|
||||
|
||||
override fun possibleResultPoints(resultPoints: List<ResultPoint>) {
|
||||
// No visualization needed
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a success animation when QR code is scanned.
|
||||
*/
|
||||
private fun showScanSuccessAnimation() {
|
||||
// Flash animation - fade viewfinder quickly
|
||||
val viewFinder: View? = barcodeView.viewFinder
|
||||
if (viewFinder != null) {
|
||||
// Create flash effect by animating alpha
|
||||
val fadeOut = ObjectAnimator.ofFloat(viewFinder, "alpha", 1f, 0.3f)
|
||||
fadeOut.duration = 100
|
||||
|
||||
val fadeIn = ObjectAnimator.ofFloat(viewFinder, "alpha", 0.3f, 1f)
|
||||
fadeIn.duration = 100
|
||||
|
||||
fadeOut.start()
|
||||
fadeOut.addListener(object : android.animation.AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: android.animation.Animator) {
|
||||
fadeIn.start()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
capture.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
capture.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
capture.onDestroy()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
capture.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
capture.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Dies wird den bestehenden Passkey durch einen neuen ersetzen. Bitte beachte, dass Dein alter Passkey überschrieben wird und nicht mehr zugänglich ist. Wenn Du stattdessen einen separaten Passkey erstellen möchtest, gehe zurück zum vorherigen Schritt.</string>
|
||||
<string name="passkey_replacing">Passkey ersetzen…</string>
|
||||
<string name="passkey_checking_connection">Verbindung wird überprüft…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Verbindungsfehler</string>
|
||||
<string name="connection_error_message">Es kann keine Verbindung zum Server hergestellt werden. Bitte überprüfe Deine Internetverbindung und versuche das Erstellen des Passkeys erneut.</string>
|
||||
|
||||
@@ -1,78 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">AliasVault</string>
|
||||
<string name="autofill_service_description" translatable="true">AliasVault AutoFill</string>
|
||||
<string name="aliasvault_icon">AliasVault icon</string>
|
||||
<string name="autofill_service_description" translatable="true">AutoFill de AliasVault</string>
|
||||
<string name="aliasvault_icon">Icono AliasVault</string>
|
||||
<!-- Common strings -->
|
||||
<string name="common_close">Close</string>
|
||||
<string name="common_next">Next</string>
|
||||
<string name="common_cancel">Cancel</string>
|
||||
<string name="unknown_error">An unknown error occurred</string>
|
||||
<string name="common_close">Cerrar</string>
|
||||
<string name="common_next">Siguiente</string>
|
||||
<string name="common_cancel">Cancelar</string>
|
||||
<string name="unknown_error">Se produjo un error desconocido</string>
|
||||
<!-- AutofillService strings -->
|
||||
<string name="autofill_failed_to_retrieve">Failed to retrieve, open app</string>
|
||||
<string name="autofill_no_match_found">No match found, create new?</string>
|
||||
<string name="autofill_open_app">Open app</string>
|
||||
<string name="autofill_vault_locked">Vault locked</string>
|
||||
<string name="autofill_failed_to_retrieve">Fallo al recuperar, abrir aplicación</string>
|
||||
<string name="autofill_no_match_found">No se han encontrado coincidencias, ¿crear nuevo?</string>
|
||||
<string name="autofill_open_app">Abrir aplicación</string>
|
||||
<string name="autofill_vault_locked">Bóveda bloqueada</string>
|
||||
<!-- Biometric prompts -->
|
||||
<string name="biometric_store_key_title">Store Encryption Key</string>
|
||||
<string name="biometric_store_key_subtitle">Authenticate to securely store your encryption key in the Android Keystore. This enables secure access to your vault.</string>
|
||||
<string name="biometric_unlock_vault_title">Unlock Vault</string>
|
||||
<string name="biometric_unlock_vault_subtitle">Authenticate to access your vault</string>
|
||||
<string name="biometric_store_key_title">Guardar clave de cifrado</string>
|
||||
<string name="biometric_store_key_subtitle">Autenticar para almacenar su clave de cifrado de forma segura en el almacén de claves Android. Esto habilita el acceso seguro a su bóveda.</string>
|
||||
<string name="biometric_unlock_vault_title">Desbloquear bóveda</string>
|
||||
<string name="biometric_unlock_vault_subtitle">Autenticar para acceder a su bóveda</string>
|
||||
<!-- Passkey registration -->
|
||||
<string name="passkey_registration_title">Create Passkey</string>
|
||||
<string name="create_passkey_title">Create New Passkey</string>
|
||||
<string name="create_passkey_subtitle">Register a new passkey for this website. It will be securely stored in your vault and automatically synced across your devices with AliasVault.</string>
|
||||
<string name="replace_passkey_title">Replace Passkey</string>
|
||||
<string name="passkey_display_name_label">Passkey Name</string>
|
||||
<string name="passkey_display_name_hint">Enter a name for this passkey</string>
|
||||
<string name="passkey_website_label">Website</string>
|
||||
<string name="passkey_username_label">Username</string>
|
||||
<string name="passkey_create_button">Create Passkey</string>
|
||||
<string name="passkey_creating">Creating passkey…</string>
|
||||
<string name="passkey_saving">Saving to vault…</string>
|
||||
<string name="passkey_syncing">Syncing with server…</string>
|
||||
<string name="passkey_registration_title">Crear llave de acceso</string>
|
||||
<string name="create_passkey_title">Crear nueva llave de acceso</string>
|
||||
<string name="create_passkey_subtitle">Registre una nueva llave de acceso para este sitio web. Se almacenará de forma segura en su bóveda y se sincronizará automáticamente en todos sus dispositivos con AliasVault.</string>
|
||||
<string name="replace_passkey_title">Reemplazar llave de acceso</string>
|
||||
<string name="passkey_display_name_label">Nombre de llave de acceso</string>
|
||||
<string name="passkey_display_name_hint">Introduzca un nombre para esta llave de acceso</string>
|
||||
<string name="passkey_website_label">Sitio Web</string>
|
||||
<string name="passkey_username_label">Nombre de usuario</string>
|
||||
<string name="passkey_create_button">Crear llave de acceso</string>
|
||||
<string name="passkey_creating">Creando llave de acceso…</string>
|
||||
<string name="passkey_saving">Guardando en la bóveda…</string>
|
||||
<string name="passkey_syncing">Sincronizando con el servidor…</string>
|
||||
<string name="passkey_error_title">Error</string>
|
||||
<string name="passkey_error_empty_name">Please enter a name for the passkey</string>
|
||||
<string name="passkey_creation_failed">Failed to create passkey</string>
|
||||
<string name="passkey_retry_button">Retry</string>
|
||||
<string name="passkey_info_icon">Info icon</string>
|
||||
<string name="passkey_create_explanation">This creates a new passkey and stores it in your vault. It will be automatically synced across all your devices that use AliasVault.</string>
|
||||
<string name="passkey_create_new_button">Create New Passkey</string>
|
||||
<string name="passkey_select_to_replace">Or, replace an existing passkey:</string>
|
||||
<string name="passkey_replace_button">Replace Passkey</string>
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_error_empty_name">Introduzca un nombre para la llave de acceso</string>
|
||||
<string name="passkey_creation_failed">Fallo al crear la llave de acceso</string>
|
||||
<string name="passkey_retry_button">Reintentar</string>
|
||||
<string name="passkey_info_icon">Icono de información</string>
|
||||
<string name="passkey_create_explanation">Esto crea una nueva llave de acceso y la almacena en tu bóveda. Se sincronizará automáticamente en todos tus dispositivos que utilicen AliasVault.</string>
|
||||
<string name="passkey_create_new_button">Crear nueva llave de acceso</string>
|
||||
<string name="passkey_select_to_replace">O, reemplazar una llave de acceso existente:</string>
|
||||
<string name="passkey_replace_button">Reemplazar llave de acceso</string>
|
||||
<string name="passkey_replace_explanation">Esto reemplazará la llave de acceso existente con una nueva. Tenga en cuenta que su llave de acceso antigua será sobrescrita y ya no será accesible. Si desea crear una llave de acceso separada en su lugar, vuelva a la pantalla anterior.</string>
|
||||
<string name="passkey_replacing">Reemplazando llave de acceso…</string>
|
||||
<string name="passkey_checking_connection">Comprobando la conexión…</string>
|
||||
<string name="passkey_retrieving">Recuperando llave…</string>
|
||||
<string name="passkey_verifying">Verificando…</string>
|
||||
<string name="passkey_authenticating">Autenticando…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
<string name="session_expired_title">Session Expired</string>
|
||||
<string name="session_expired_message">Your session has expired. Please sign in again.</string>
|
||||
<string name="password_changed_title">Password Changed</string>
|
||||
<string name="password_changed_message">Your password has been changed. Please sign in again.</string>
|
||||
<string name="version_not_supported_title">Update Required</string>
|
||||
<string name="version_not_supported_message">Your app version is no longer supported. Please update to the latest version.</string>
|
||||
<string name="server_unavailable_title">Server Unavailable</string>
|
||||
<string name="server_unavailable_message">The server is currently unavailable. Please try again later.</string>
|
||||
<string name="network_error_title">Network Error</string>
|
||||
<string name="network_error_message">A network error occurred. Please check your connection and try again.</string>
|
||||
<string name="server_version_not_supported_title">Server Update Required</string>
|
||||
<string name="server_version_not_supported_message">The server version is outdated. Please contact your administrator to update the server.</string>
|
||||
<string name="connection_error_title">Error de conexión</string>
|
||||
<string name="connection_error_message">No se puede establecer conexión con el servidor. Por favor, compruebe su conexión a Internet e intente crear la llave de acceso de nuevo.</string>
|
||||
<string name="session_expired_title">Sesión expirada</string>
|
||||
<string name="session_expired_message">Su sesión ha expirado. Por favor, inicie sesión de nuevo.</string>
|
||||
<string name="password_changed_title">Contraseña cambiada</string>
|
||||
<string name="password_changed_message">Su contraseña ha sido cambiada. Por favor, inicie sesión de nuevo.</string>
|
||||
<string name="version_not_supported_title">Actualización requerida</string>
|
||||
<string name="version_not_supported_message">La versión de su aplicación ya no es compatible. Por favor, actualice a la última versión.</string>
|
||||
<string name="server_unavailable_title">Servidor no disponible</string>
|
||||
<string name="server_unavailable_message">El servidor no está disponible actualmente. Inténtelo de nuevo más tarde.</string>
|
||||
<string name="network_error_title">Error de red</string>
|
||||
<string name="network_error_message">Se produjo un error de red. Por favor, compruebe la conexión y vuelva a intentarlo.</string>
|
||||
<string name="server_version_not_supported_title">Actualización del servidor necesaria</string>
|
||||
<string name="server_version_not_supported_message">La versión del servidor está desactualizada. Por favor, contacte con su administrador para actualizar el servidor.</string>
|
||||
<!-- Passkey authentication and unlock error messages -->
|
||||
<string name="error_unlock_method_required">Please enable biometric or PIN authentication in the main AliasVault app in order to continue</string>
|
||||
<string name="error_unlock_vault_first">Please unlock vault in AliasVault app first</string>
|
||||
<string name="error_vault_decrypt_failed">Failed to decrypt vault</string>
|
||||
<string name="error_biometric_cancelled">Biometric authentication cancelled</string>
|
||||
<string name="error_encryption_key_failed">Failed to retrieve encryption key</string>
|
||||
<string name="error_unlock_method_required">Por favor, habilite la autenticación biométrica o PIN en la aplicación principal de AliasVault para continuar</string>
|
||||
<string name="error_unlock_vault_first">Por favor, primero desbloquea bóveda en la aplicación AliasVault</string>
|
||||
<string name="error_vault_decrypt_failed">Error al descifrar la bóveda</string>
|
||||
<string name="error_biometric_cancelled">Autenticación biométrica cancelada</string>
|
||||
<string name="error_encryption_key_failed">Fallo al recuperar la clave de cifrado</string>
|
||||
<!-- PIN unlock -->
|
||||
<string name="pin_unlock_vault">Unlock Vault</string>
|
||||
<string name="pin_enter_to_unlock">Enter your PIN to unlock your vault</string>
|
||||
<string name="pin_locked_max_attempts">PIN locked after too many failed attempts</string>
|
||||
<string name="pin_incorrect_attempts_remaining">Incorrect PIN. %d attempts remaining</string>
|
||||
<string name="pin_unlock_vault">Desbloquear bóveda</string>
|
||||
<string name="pin_enter_to_unlock">Introduzca su PIN para desbloquear su bóveda</string>
|
||||
<string name="pin_locked_max_attempts">PIN bloqueado tras demasiados intentos fallidos</string>
|
||||
<string name="pin_incorrect_attempts_remaining">PIN incorrecto, %d intentos restantes</string>
|
||||
<!-- PIN setup -->
|
||||
<string name="pin_setup_title">Setup PIN</string>
|
||||
<string name="pin_setup_description">Choose a PIN to unlock your vault</string>
|
||||
<string name="pin_confirm_title">Confirm PIN</string>
|
||||
<string name="pin_confirm_description">Re-enter your PIN to confirm</string>
|
||||
<string name="pin_mismatch">PINs do not match. Please try again.</string>
|
||||
<string name="pin_setup_title">Configurar PIN</string>
|
||||
<string name="pin_setup_description">Elija un PIN para desbloquear su bóveda</string>
|
||||
<string name="pin_confirm_title">Confirmar PIN</string>
|
||||
<string name="pin_confirm_description">Vuelva a introducir su PIN para confirmar</string>
|
||||
<string name="pin_mismatch">Los PIN no coinciden. Por favor, inténtalo de nuevo.</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Tämä korvaa olemassa olevan todennusavaimen uudella todennusavaimella. Ole hyvä ja ota huomioon, että vanha todennusavaimesi on korvattu eikä enää käytettävissä. Jos haluat luoda erillisen todennusavaimen sen sijaan, mene takaisin edelliseen ruutuun.</string>
|
||||
<string name="passkey_replacing">Korvataan todennusavainta...</string>
|
||||
<string name="passkey_checking_connection">Tarkistetaan yhteyttä</string>
|
||||
<string name="passkey_retrieving">Noudetaan todennusavainta...</string>
|
||||
<string name="passkey_verifying">Tarkistetaan…</string>
|
||||
<string name="passkey_authenticating">Todennetaan…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Yhteysvirhe</string>
|
||||
<string name="connection_error_message">Yhteyttä palvelimeen ei voida luoda. Tarkista internet-yhteytesi ja yritä luoda todennusavain uudelleen.</string>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<string name="autofill_service_description" translatable="true">Remplissage automatique AliasVault</string>
|
||||
<string name="aliasvault_icon">Icône AliasVault</string>
|
||||
<!-- Common strings -->
|
||||
<string name="common_close">Close</string>
|
||||
<string name="common_next">Next</string>
|
||||
<string name="common_cancel">Cancel</string>
|
||||
<string name="unknown_error">An unknown error occurred</string>
|
||||
<string name="common_close">Fermer</string>
|
||||
<string name="common_next">Suivant</string>
|
||||
<string name="common_cancel">Annuler</string>
|
||||
<string name="unknown_error">Une erreur inconnue s\'est produite</string>
|
||||
<!-- AutofillService strings -->
|
||||
<string name="autofill_failed_to_retrieve">Échec de la récupération, ouvrez l\'application</string>
|
||||
<string name="autofill_no_match_found">Aucune correspondance trouvée, créer un nouveau ?</string>
|
||||
@@ -19,60 +19,63 @@
|
||||
<string name="biometric_unlock_vault_title">Déverrouiller le coffre</string>
|
||||
<string name="biometric_unlock_vault_subtitle">Authentifiez-vous pour accéder à votre coffre</string>
|
||||
<!-- Passkey registration -->
|
||||
<string name="passkey_registration_title">Create Passkey</string>
|
||||
<string name="create_passkey_title">Create New Passkey</string>
|
||||
<string name="create_passkey_subtitle">Register a new passkey for this website. It will be securely stored in your vault and automatically synced across your devices with AliasVault.</string>
|
||||
<string name="replace_passkey_title">Replace Passkey</string>
|
||||
<string name="passkey_display_name_label">Passkey Name</string>
|
||||
<string name="passkey_display_name_hint">Enter a name for this passkey</string>
|
||||
<string name="passkey_website_label">Website</string>
|
||||
<string name="passkey_username_label">Username</string>
|
||||
<string name="passkey_create_button">Create Passkey</string>
|
||||
<string name="passkey_creating">Creating passkey…</string>
|
||||
<string name="passkey_saving">Saving to vault…</string>
|
||||
<string name="passkey_syncing">Syncing with server…</string>
|
||||
<string name="passkey_error_title">Error</string>
|
||||
<string name="passkey_error_empty_name">Please enter a name for the passkey</string>
|
||||
<string name="passkey_creation_failed">Failed to create passkey</string>
|
||||
<string name="passkey_retry_button">Retry</string>
|
||||
<string name="passkey_info_icon">Info icon</string>
|
||||
<string name="passkey_create_explanation">This creates a new passkey and stores it in your vault. It will be automatically synced across all your devices that use AliasVault.</string>
|
||||
<string name="passkey_create_new_button">Create New Passkey</string>
|
||||
<string name="passkey_select_to_replace">Or, replace an existing passkey:</string>
|
||||
<string name="passkey_replace_button">Replace Passkey</string>
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_registration_title">Créer une clé d\'accès</string>
|
||||
<string name="create_passkey_title">Créer une nouvelle clé d\'accès</string>
|
||||
<string name="create_passkey_subtitle">Enregistrez une nouvelle clé d\'accès pour ce site Web. Elle sera stockée de manière sécurisée dans votre coffre et automatiquement synchronisée entre vos appareils avec AliasVault.</string>
|
||||
<string name="replace_passkey_title">Remplacer la clé d\'accès</string>
|
||||
<string name="passkey_display_name_label">Nom de la clé d\'accès</string>
|
||||
<string name="passkey_display_name_hint">Entrez un nom pour cette clé d\'accès</string>
|
||||
<string name="passkey_website_label">Site Web</string>
|
||||
<string name="passkey_username_label">Nom d\'utilisateur</string>
|
||||
<string name="passkey_create_button">Créer clé d\'accès</string>
|
||||
<string name="passkey_creating">Création de la clé d\'accès…</string>
|
||||
<string name="passkey_saving">Sauvegarde dans le coffre…</string>
|
||||
<string name="passkey_syncing">Synchronisation avec le serveur…</string>
|
||||
<string name="passkey_error_title">Erreur</string>
|
||||
<string name="passkey_error_empty_name">Entrez un nom pour la clé d\'accès</string>
|
||||
<string name="passkey_creation_failed">Echec de la création de la clé d\'accès</string>
|
||||
<string name="passkey_retry_button">Recommencer</string>
|
||||
<string name="passkey_info_icon">Icône d\'info</string>
|
||||
<string name="passkey_create_explanation">Cela crée une nouvelle clé d\'accès et la stocke dans votre coffre. Elle sera automatiquement synchronisée sur tous vos appareils qui utilisent AliasVault.</string>
|
||||
<string name="passkey_create_new_button">Créer une nouvelle clé d\'accès</string>
|
||||
<string name="passkey_select_to_replace">Ou, remplacer une clé d\'accès existante :</string>
|
||||
<string name="passkey_replace_button">Remplacer la clé d\'accès</string>
|
||||
<string name="passkey_replace_explanation">Cela remplacera la clé d\'accès existante par une nouvelle. Veuillez noter que votre ancienne clé d\'accès sera écrasée et ne sera plus accessible. Si vous souhaitez créer une clé d\'accès séparée, revenez à l\'écran précédent.</string>
|
||||
<string name="passkey_replacing">Remplacement de la clé d\'accès…</string>
|
||||
<string name="passkey_checking_connection">Vérification de la connexion…</string>
|
||||
<string name="passkey_retrieving">Récupération de la clé d\'accès…</string>
|
||||
<string name="passkey_verifying">Vérification…</string>
|
||||
<string name="passkey_authenticating">Authentification…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
<string name="session_expired_title">Session Expired</string>
|
||||
<string name="session_expired_message">Your session has expired. Please sign in again.</string>
|
||||
<string name="password_changed_title">Password Changed</string>
|
||||
<string name="password_changed_message">Your password has been changed. Please sign in again.</string>
|
||||
<string name="version_not_supported_title">Update Required</string>
|
||||
<string name="version_not_supported_message">Your app version is no longer supported. Please update to the latest version.</string>
|
||||
<string name="server_unavailable_title">Server Unavailable</string>
|
||||
<string name="server_unavailable_message">The server is currently unavailable. Please try again later.</string>
|
||||
<string name="network_error_title">Network Error</string>
|
||||
<string name="network_error_message">A network error occurred. Please check your connection and try again.</string>
|
||||
<string name="server_version_not_supported_title">Server Update Required</string>
|
||||
<string name="server_version_not_supported_message">The server version is outdated. Please contact your administrator to update the server.</string>
|
||||
<string name="connection_error_title">Erreur de Connexion</string>
|
||||
<string name="connection_error_message">Aucune connexion au serveur ne peut être établie. Veuillez vérifier votre connexion internet et essayer de créer à nouveau la clé d\'accès.</string>
|
||||
<string name="session_expired_title">Session expirée</string>
|
||||
<string name="session_expired_message">Votre session a expiré. Veuillez vous reconnecter.</string>
|
||||
<string name="password_changed_title">Mot de passe modifié</string>
|
||||
<string name="password_changed_message">Votre mot de passe a été modifié. Veuillez vous reconnecter.</string>
|
||||
<string name="version_not_supported_title">Mise à jour requise</string>
|
||||
<string name="version_not_supported_message">La version de votre application n\'est plus prise en charge. Veuillez mettre à jour vers la dernière version.</string>
|
||||
<string name="server_unavailable_title">Serveur indisponible</string>
|
||||
<string name="server_unavailable_message">Le serveur est actuellement indisponible, veuillez réessayer plus tard.</string>
|
||||
<string name="network_error_title">Erreur réseau</string>
|
||||
<string name="network_error_message">Une erreur réseau s\'est produite. Veuillez vérifier votre connexion et réessayer.</string>
|
||||
<string name="server_version_not_supported_title">Mise à jour du serveur requise</string>
|
||||
<string name="server_version_not_supported_message">La version du serveur est obsolète. Veuillez contacter votre administrateur pour mettre à jour le serveur.</string>
|
||||
<!-- Passkey authentication and unlock error messages -->
|
||||
<string name="error_unlock_method_required">Please enable biometric or PIN authentication in the main AliasVault app in order to continue</string>
|
||||
<string name="error_unlock_vault_first">Please unlock vault in AliasVault app first</string>
|
||||
<string name="error_vault_decrypt_failed">Failed to decrypt vault</string>
|
||||
<string name="error_biometric_cancelled">Biometric authentication cancelled</string>
|
||||
<string name="error_encryption_key_failed">Failed to retrieve encryption key</string>
|
||||
<string name="error_unlock_method_required">Veuillez activer l\'authentification biométrique ou PIN dans l\'application principale AliasVault pour continuer</string>
|
||||
<string name="error_unlock_vault_first">Veuillez d\'abord déverrouiller le coffre dans l\'application AliasVault</string>
|
||||
<string name="error_vault_decrypt_failed">Échec du déchiffrement du coffre</string>
|
||||
<string name="error_biometric_cancelled">Authentification biométrique annulée</string>
|
||||
<string name="error_encryption_key_failed">Impossible de récupérer la clé de cryptage</string>
|
||||
<!-- PIN unlock -->
|
||||
<string name="pin_unlock_vault">Unlock Vault</string>
|
||||
<string name="pin_enter_to_unlock">Enter your PIN to unlock your vault</string>
|
||||
<string name="pin_locked_max_attempts">PIN locked after too many failed attempts</string>
|
||||
<string name="pin_incorrect_attempts_remaining">Incorrect PIN. %d attempts remaining</string>
|
||||
<string name="pin_unlock_vault">Déverrouiller le coffre</string>
|
||||
<string name="pin_enter_to_unlock">Entrez votre code PIN pour déverrouiller votre coffre</string>
|
||||
<string name="pin_locked_max_attempts">Code PIN verrouillé après trop de tentatives échouées</string>
|
||||
<string name="pin_incorrect_attempts_remaining">Code PIN incorrect, %d tentatives restantes</string>
|
||||
<!-- PIN setup -->
|
||||
<string name="pin_setup_title">Setup PIN</string>
|
||||
<string name="pin_setup_description">Choose a PIN to unlock your vault</string>
|
||||
<string name="pin_confirm_title">Confirm PIN</string>
|
||||
<string name="pin_confirm_description">Re-enter your PIN to confirm</string>
|
||||
<string name="pin_mismatch">PINs do not match. Please try again.</string>
|
||||
<string name="pin_setup_title">Configurer le code PIN</string>
|
||||
<string name="pin_setup_description">Choisissez un code PIN pour déverrouiller votre coffre</string>
|
||||
<string name="pin_confirm_title">Confirmer le code PIN</string>
|
||||
<string name="pin_confirm_description">Entrez à nouveau votre code PIN pour confirmer</string>
|
||||
<string name="pin_mismatch">Les codes PIN ne correspondent pas. Veuillez réessayer.</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">החיבור נבדק…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">שגיאת חיבור</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Questo sostituirà la passkey esistente con una nuova. Si prega di notare che la vecchia passkey sarà sovrascritta e non sarà più accessibile. Se si desidera invece creare una passkey separata, tornare alla schermata precedente.</string>
|
||||
<string name="passkey_replacing">Sostituzione passkey…</string>
|
||||
<string name="passkey_checking_connection">Controllo connessione…</string>
|
||||
<string name="passkey_retrieving">Recupero passkey…</string>
|
||||
<string name="passkey_verifying">Verifica…</string>
|
||||
<string name="passkey_authenticating">Autenticazione…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Errore Di Connessione</string>
|
||||
<string name="connection_error_message">Non è possibile effettuare alcuna connessione al server. Controlla la tua connessione internet e prova a creare nuovamente la passkey.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Dit zal de bestaande passkey vervangen door een nieuwe. Houd er rekening mee dat je oude passkey wordt overschreven en niet langer toegankelijk is. Als je in plaats hiervan een aparte passkey wilt maken, ga dan terug naar het vorige scherm.</string>
|
||||
<string name="passkey_replacing">Passkey vervangen…</string>
|
||||
<string name="passkey_checking_connection">Verbinding controleren…</string>
|
||||
<string name="passkey_retrieving">Passkey ophalen…</string>
|
||||
<string name="passkey_verifying">Verifiëren…</string>
|
||||
<string name="passkey_authenticating">Authenticeren…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Verbindingsfout</string>
|
||||
<string name="connection_error_message">Er kan geen verbinding met de server worden gemaakt. Controleer je internetverbinding en probeer het opnieuw.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Spowoduje to zastąpienie dotychczasowego klucza dostępu nowym. Należy pamiętać, że stare klucz zostanie nadpisany i nie będzie już dostępny. Jeśli chcesz utworzyć nowy klucz dostępu, wróć do poprzedniego ekranu.</string>
|
||||
<string name="passkey_replacing">Zastępowanie klucza dostępu…</string>
|
||||
<string name="passkey_checking_connection">Sprawdzanie połączenia…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Błąd połączenia</string>
|
||||
<string name="connection_error_message">Nie można nawiązać połączenia z serwerem. Sprawdź połączenie internetowe i spróbuj ponownie utworzyć klucz dostępu.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Isto irá substituir a passkey existente com uma nova. Por favor, saiba que sua passkey anterior será sobrescrita e não será mais acessível. Se você deseja criar uma passkey separadamente, volte à tela anterior.</string>
|
||||
<string name="passkey_replacing">Substituindo passkey…</string>
|
||||
<string name="passkey_checking_connection">Verificando conexão…</string>
|
||||
<string name="passkey_retrieving">Recuperando passkey…</string>
|
||||
<string name="passkey_verifying">Verificando…</string>
|
||||
<string name="passkey_authenticating">Autenticando…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Erro de Conexão</string>
|
||||
<string name="connection_error_message">A conexão com o servidor não foi feita. Por favor, confira sua conexão com a internet e tente criar a passkey novamente.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">Существующий ключ доступа будет заменен на новый. Обратите внимание, что старый ключ будет перезаписан и станет недоступен. Если вы хотите создать отдельный ключ доступа, вернитесь на предыдущий экран.</string>
|
||||
<string name="passkey_replacing">Замена ключа доступа…</string>
|
||||
<string name="passkey_checking_connection">Проверка соединения…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Ошибка подключения</string>
|
||||
<string name="connection_error_message">Не удалось подключиться к серверу. Проверьте интернет-соединение и попробуйте создать ключ доступа снова.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
|
||||
@@ -36,16 +36,19 @@
|
||||
<string name="passkey_creation_failed">创建通行密钥失败</string>
|
||||
<string name="passkey_retry_button">重试</string>
|
||||
<string name="passkey_info_icon">信息图标</string>
|
||||
<string name="passkey_create_explanation">This creates a new passkey and stores it in your vault. It will be automatically synced across all your devices that use AliasVault.</string>
|
||||
<string name="passkey_create_explanation">这将创建一个新的通行密钥并将其存储在您的密码库中。它将自动同步到您所有使用 AliasVault 的设备。</string>
|
||||
<string name="passkey_create_new_button">创建新通行密钥</string>
|
||||
<string name="passkey_select_to_replace">或者替换现有的通行密钥:</string>
|
||||
<string name="passkey_replace_button">替换通行密钥</string>
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replace_explanation">这将用新的通行密钥替换现有通行密钥。请注意,您的旧通行密钥将被覆盖且无法再访问。若您希望单独创建一个通行密钥,请返回到上一屏幕。</string>
|
||||
<string name="passkey_replacing">正在替换通行密钥…</string>
|
||||
<string name="passkey_checking_connection">检查连接中…</string>
|
||||
<string name="passkey_retrieving">正在检索通行密钥…</string>
|
||||
<string name="passkey_verifying">验证中…</string>
|
||||
<string name="passkey_authenticating">认证中…</string>
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">连接错误</string>
|
||||
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>
|
||||
<string name="connection_error_message">无法连接到服务器。请检查您的互联网连接,然后重试创建通行密钥。</string>
|
||||
<string name="session_expired_title">会话已过期</string>
|
||||
<string name="session_expired_message">您的会话已过期,请重新登录。</string>
|
||||
<string name="password_changed_title">密码已更改</string>
|
||||
@@ -55,12 +58,12 @@
|
||||
<string name="server_unavailable_title">服务器不可用</string>
|
||||
<string name="server_unavailable_message">服务器目前不可用,请稍后重试。</string>
|
||||
<string name="network_error_title">网络错误</string>
|
||||
<string name="network_error_message">A network error occurred. Please check your connection and try again.</string>
|
||||
<string name="network_error_message">网络发生错误,请检查您的连接后重试。</string>
|
||||
<string name="server_version_not_supported_title">服务器需要更新</string>
|
||||
<string name="server_version_not_supported_message">The server version is outdated. Please contact your administrator to update the server.</string>
|
||||
<string name="server_version_not_supported_message">服务器版本过旧,请联系您的管理员更新服务器。</string>
|
||||
<!-- Passkey authentication and unlock error messages -->
|
||||
<string name="error_unlock_method_required">Please enable biometric or PIN authentication in the main AliasVault app in order to continue</string>
|
||||
<string name="error_unlock_vault_first">Please unlock vault in AliasVault app first</string>
|
||||
<string name="error_unlock_method_required">请在 AliasVault 主应用中启用生物识别或 PIN 码认证以继续</string>
|
||||
<string name="error_unlock_vault_first">请先在 AliasVault 应用中解锁密码库</string>
|
||||
<string name="error_vault_decrypt_failed">解密密码库失败</string>
|
||||
<string name="error_biometric_cancelled">已取消生物识别认证</string>
|
||||
<string name="error_encryption_key_failed">检索加密密钥失败</string>
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
|
||||
<string name="passkey_replacing">Replacing passkey…</string>
|
||||
<string name="passkey_checking_connection">Checking connection…</string>
|
||||
<string name="passkey_retrieving">Retrieving passkey…</string>
|
||||
<string name="passkey_verifying">Verifying…</string>
|
||||
<string name="passkey_authenticating">Authenticating…</string>
|
||||
|
||||
<!-- Vault sync error messages -->
|
||||
<string name="connection_error_title">Connection Error</string>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="de" />
|
||||
<locale android:name="en" />
|
||||
<locale android:name="es" />
|
||||
<locale android:name="fi" />
|
||||
<locale android:name="fr" />
|
||||
<locale android:name="he" />
|
||||
<locale android:name="it" />
|
||||
<locale android:name="nl" />
|
||||
|
||||
1
apps/mobile-app/android/fdroid/.gitignore
vendored
1
apps/mobile-app/android/fdroid/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
fdroiddata
|
||||
fdroidserver
|
||||
net.aliasvault.app.yml
|
||||
|
||||
@@ -14,6 +14,8 @@ services:
|
||||
- ./net.aliasvault.app.yml:/net.aliasvault.app.yml
|
||||
# Add build script to the container
|
||||
- ./scripts/build.sh:/build.sh:Z
|
||||
# Bind the outputs directory to capture APK build output
|
||||
- ./outputs:/outputs:rw
|
||||
# Increase memory limits for Gradle builds
|
||||
shm_size: '2gb'
|
||||
mem_limit: 12g
|
||||
|
||||
@@ -13,9 +13,9 @@ RepoType: git
|
||||
Repo: https://github.com/aliasvault/aliasvault.git
|
||||
|
||||
Builds:
|
||||
- versionName: 0.1.0
|
||||
versionCode: 1
|
||||
commit: main
|
||||
- versionName: __VERSION_NAME__
|
||||
versionCode: __VERSION_CODE__
|
||||
commit: __COMMIT__
|
||||
subdir: apps/mobile-app/android/app/
|
||||
sudo:
|
||||
- sysctl fs.inotify.max_user_watches=524288 || true
|
||||
@@ -26,7 +26,7 @@ Builds:
|
||||
init:
|
||||
- cd ../..
|
||||
- sed -i -e '/signingConfig /d' android/app/build.gradle
|
||||
- npm install --build-from-source
|
||||
- npm install --production --build-from-source
|
||||
gradle:
|
||||
- yes
|
||||
scanignore:
|
||||
@@ -44,8 +44,6 @@ Builds:
|
||||
- apps/mobile-app/node_modules/react-native-context-menu-view/android/build.gradle
|
||||
- apps/mobile-app/node_modules/react-native-get-random-values/android/build.gradle
|
||||
- apps/mobile-app/node_modules/react-native-svg/android/build.gradle
|
||||
- apps/mobile-app/node_modules/expo-dev-launcher/android/build.gradle
|
||||
- apps/mobile-app/node_modules/expo-dev-menu/android/build.gradle
|
||||
scandelete:
|
||||
- apps/mobile-app/node_modules/
|
||||
|
||||
@@ -2,11 +2,64 @@
|
||||
set -e # Exit on any error, except where explicitly ignored
|
||||
trap 'echo "🛑 Interrupted. Exiting..."; exit 130' INT # Handle Ctrl+C cleanly
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BUILD_GRADLE="${SCRIPT_DIR}/../app/build.gradle"
|
||||
TEMPLATE_FILE="${SCRIPT_DIR}/net.aliasvault.app.yml.template"
|
||||
OUTPUT_FILE="${SCRIPT_DIR}/net.aliasvault.app.yml"
|
||||
|
||||
# Check if template exists
|
||||
if [ ! -f "$TEMPLATE_FILE" ]; then
|
||||
echo "❌ Error: Template file not found: $TEMPLATE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if build.gradle exists
|
||||
if [ ! -f "$BUILD_GRADLE" ]; then
|
||||
echo "❌ Error: build.gradle not found: $BUILD_GRADLE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version information from build.gradle
|
||||
echo "📱 Extracting version information from build.gradle..."
|
||||
VERSION_CODE=$(grep -E '^\s*versionCode\s+' "$BUILD_GRADLE" | sed -E 's/.*versionCode\s+([0-9]+).*/\1/')
|
||||
VERSION_NAME=$(grep -E '^\s*versionName\s+' "$BUILD_GRADLE" | sed -E 's/.*versionName\s+"([^"]+)".*/\1/')
|
||||
|
||||
if [ -z "$VERSION_CODE" ] || [ -z "$VERSION_NAME" ]; then
|
||||
echo "❌ Error: Could not extract version information from build.gradle"
|
||||
echo " versionCode: ${VERSION_CODE:-not found}"
|
||||
echo " versionName: ${VERSION_NAME:-not found}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current git branch
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
||||
|
||||
echo "✅ Version information extracted:"
|
||||
echo " versionCode: $VERSION_CODE"
|
||||
echo " versionName: $VERSION_NAME"
|
||||
echo " commit: $CURRENT_BRANCH"
|
||||
|
||||
# Generate the F-Droid metadata file from template
|
||||
echo "📝 Generating F-Droid metadata file..."
|
||||
sed -e "s/__VERSION_NAME__/$VERSION_NAME/g" \
|
||||
-e "s/__VERSION_CODE__/$VERSION_CODE/g" \
|
||||
-e "s/__COMMIT__/$CURRENT_BRANCH/g" \
|
||||
"$TEMPLATE_FILE" > "$OUTPUT_FILE"
|
||||
|
||||
echo "✅ Generated: $OUTPUT_FILE"
|
||||
|
||||
# Create outputs bind dir and set correct permissions
|
||||
mkdir -p outputs
|
||||
sudo chown -R 1000:1000 outputs
|
||||
|
||||
# Build and run the Docker environment
|
||||
echo "Building Docker images..."
|
||||
echo "🐳 Building Docker images..."
|
||||
if ! docker compose build; then
|
||||
echo "⚠️ Warning: Docker build failed, continuing..."
|
||||
fi
|
||||
|
||||
echo "Running fdroid-buildserver..."
|
||||
docker compose run --rm fdroid-buildserver
|
||||
echo "🚀 Running fdroid-buildserver..."
|
||||
docker compose run --rm fdroid-buildserver
|
||||
|
||||
echo "✅ F-Droid build completed!"
|
||||
|
||||
@@ -22,5 +22,9 @@ cd /home/vagrant/build
|
||||
fdroid fetchsrclibs net.aliasvault.app --verbose
|
||||
# Format build receipe
|
||||
fdroid rewritemeta net.aliasvault.app
|
||||
# Lint app
|
||||
fdroid lint --verbose net.aliasvault.app
|
||||
# Build app and scan for any binary files that are prohibited
|
||||
fdroid build --verbose --latest --scan-binary --on-server --no-tarball net.aliasvault.app
|
||||
fdroid build --verbose --test --latest --scan-binary --on-server --no-tarball net.aliasvault.app
|
||||
# Copy any outputs to the bind mount folder
|
||||
rsync -avh /home/vagrant/build/build/net.aliasvault.app/apps/mobile-app/android/app/build/outputs/ /outputs/
|
||||
|
||||
124
apps/mobile-app/android/fdroid/scripts/sign-apk.sh
Executable file
124
apps/mobile-app/android/fdroid/scripts/sign-apk.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ================================
|
||||
# This script is used to sign an unsigned F-Droid APK file with the local debug keystore (on MacOS) for testing purposes.
|
||||
# ================================
|
||||
# Flow:
|
||||
# 1. First do the run.sh / build.sh flow to build the F-Droid APK file on a (Linux) machine with enough memory and CPU power.
|
||||
# 2. Extract the unsigned APK file from the local (bind-mounted) outputs directory
|
||||
# 3. Then use this script to sign the APK file with the local debug keystore (on MacOS).
|
||||
#
|
||||
# ================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Colors ---
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[1;33m"
|
||||
CYAN="\033[0;36m"
|
||||
RESET="\033[0m"
|
||||
|
||||
info() { echo -e "${CYAN}[INFO]${RESET} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${RESET} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${RESET} $1"; }
|
||||
|
||||
echo -e "${YELLOW}=== APK Debug Signer (macOS) ===${RESET}"
|
||||
|
||||
# --- Ask for unsigned APK ---
|
||||
read -rp "Enter unsigned APK filename (example: app-release-unsigned.apk): " APK_IN
|
||||
|
||||
if [[ ! -f "$APK_IN" ]]; then
|
||||
error "File not found: $APK_IN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Input APK: $APK_IN"
|
||||
|
||||
# --- Detect SDK and build-tools ---
|
||||
SDK_ROOT="${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}"
|
||||
BT_DIR="$SDK_ROOT/build-tools"
|
||||
|
||||
if [[ ! -d "$BT_DIR" ]]; then
|
||||
error "build-tools not found in: $BT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Scanning build-tools..."
|
||||
|
||||
LATEST_BT="$(ls "$BT_DIR" | sort -V | tail -n 1)"
|
||||
|
||||
if [[ -z "$LATEST_BT" ]]; then
|
||||
error "No build-tools found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Using build-tools version: ${YELLOW}${LATEST_BT}${RESET}"
|
||||
|
||||
ZIPALIGN="$BT_DIR/$LATEST_BT/zipalign"
|
||||
APKSIGNER="$BT_DIR/$LATEST_BT/apksigner"
|
||||
|
||||
[[ -x "$ZIPALIGN" ]] || { error "zipalign missing: $ZIPALIGN"; exit 1; }
|
||||
[[ -x "$APKSIGNER" ]] || { error "apksigner missing: $APKSIGNER"; exit 1; }
|
||||
|
||||
# --- Filenames ---
|
||||
APK_ALIGNED="${APK_IN%.apk}-aligned-temp.apk"
|
||||
APK_SIGNED="${APK_IN%.apk}-signed.apk"
|
||||
|
||||
info "Temporary aligned APK: $APK_ALIGNED"
|
||||
info "Final signed APK: $APK_SIGNED"
|
||||
|
||||
# --- Debug keystore ---
|
||||
DEBUG_KEYSTORE="$HOME/.android/debug.keystore"
|
||||
DEBUG_ALIAS="androiddebugkey"
|
||||
DEBUG_PASS="android"
|
||||
|
||||
[[ -f "$DEBUG_KEYSTORE" ]] || {
|
||||
error "Debug keystore missing: $DEBUG_KEYSTORE"
|
||||
exit 1
|
||||
}
|
||||
|
||||
info "Using debug keystore: $DEBUG_KEYSTORE"
|
||||
|
||||
# --- Step 1: zipalign ---
|
||||
echo -e "${YELLOW}=== Step 1: zipalign ===${RESET}"
|
||||
echo -e "[CMD] \"$ZIPALIGN\" -p -f 4 \"$APK_IN\" \"$APK_ALIGNED\""
|
||||
|
||||
"$ZIPALIGN" -p -f 4 "$APK_IN" "$APK_ALIGNED"
|
||||
ok "zipalign complete"
|
||||
|
||||
# --- Step 2: sign ---
|
||||
echo -e "${YELLOW}=== Step 2: apksigner ===${RESET}"
|
||||
echo -e "[CMD] \"$APKSIGNER\" sign --ks \"$DEBUG_KEYSTORE\" --out \"$APK_SIGNED\" \"$APK_ALIGNED\""
|
||||
|
||||
"$APKSIGNER" sign \
|
||||
--ks "$DEBUG_KEYSTORE" \
|
||||
--ks-key-alias "$DEBUG_ALIAS" \
|
||||
--ks-pass "pass:$DEBUG_PASS" \
|
||||
--key-pass "pass:$DEBUG_PASS" \
|
||||
--out "$APK_SIGNED" \
|
||||
"$APK_ALIGNED"
|
||||
|
||||
ok "Signing complete"
|
||||
|
||||
# --- Step 3: verify ---
|
||||
echo -e "${YELLOW}=== Step 3: Verify ===${RESET}"
|
||||
|
||||
"$APKSIGNER" verify --verbose "$APK_SIGNED"
|
||||
ok "APK verified"
|
||||
|
||||
# --- Step 4: Cleanup ---
|
||||
echo -e "${YELLOW}=== Cleanup ===${RESET}"
|
||||
|
||||
if [[ -f "$APK_ALIGNED" ]]; then
|
||||
rm -f "$APK_ALIGNED"
|
||||
ok "Removed temporary file: $APK_ALIGNED"
|
||||
fi
|
||||
|
||||
ok "Cleanup complete"
|
||||
|
||||
echo -e "${GREEN}=== DONE ===${RESET}"
|
||||
echo -e "Signed APK created → ${YELLOW}$APK_SIGNED${RESET}"
|
||||
echo -e "Install with:"
|
||||
echo -e " ${CYAN}adb install -r \"$APK_SIGNED\"${RESET}"
|
||||
|
||||
@@ -52,6 +52,9 @@ expo.webp.animated=false
|
||||
# Enable network inspector
|
||||
EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
||||
|
||||
# Enable VisionCamera code scanner
|
||||
VisionCamera_enableCodeScanner=true
|
||||
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"expo": {
|
||||
"name": "AliasVault",
|
||||
"slug": "AliasVault",
|
||||
"version": "0.25.0",
|
||||
"version": "0.26.0-alpha",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "aliasvault",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { CameraView, useCameraPermissions } from 'expo-camera';
|
||||
import { Href, router, useLocalSearchParams } from 'expo-router';
|
||||
import { useEffect, useCallback, useRef } from 'react';
|
||||
import { View, Alert, StyleSheet } from 'react-native';
|
||||
import { View, StyleSheet, Platform, Alert } from 'react-native';
|
||||
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
@@ -10,6 +8,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import LoadingIndicator from '@/components/LoadingIndicator';
|
||||
import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
import { ThemedText } from '@/components/themed/ThemedText';
|
||||
import NativeVaultManager from '@/specs/NativeVaultManager';
|
||||
|
||||
// QR Code type prefixes
|
||||
const QR_CODE_PREFIXES = {
|
||||
@@ -54,71 +53,73 @@ function parseQRCode(data: string): ScannedQRCode {
|
||||
export default function QRScannerScreen() : React.ReactNode {
|
||||
const colors = useColors();
|
||||
const { t } = useTranslation();
|
||||
const [permission, requestPermission] = useCameraPermissions();
|
||||
const { url } = useLocalSearchParams<{ url?: string }>();
|
||||
const hasProcessedUrl = useRef(false);
|
||||
const processedUrls = useRef(new Set<string>());
|
||||
|
||||
// Request camera permission on mount
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Request camera permission.
|
||||
*/
|
||||
const requestCameraPermission = async () : Promise<void> => {
|
||||
if (!permission) {
|
||||
return; // Still loading permission status
|
||||
}
|
||||
|
||||
if (!permission.granted && permission.canAskAgain) {
|
||||
// Request permission
|
||||
await requestPermission();
|
||||
} else if (!permission.granted && !permission.canAskAgain) {
|
||||
// Permission was permanently denied
|
||||
Alert.alert(
|
||||
t('settings.qrScanner.cameraPermissionTitle'),
|
||||
t('settings.qrScanner.cameraPermissionMessage'),
|
||||
[{ text: t('common.ok'), /**
|
||||
* Go back to the settings tab.
|
||||
*/
|
||||
onPress: (): void => router.back() }]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
requestCameraPermission();
|
||||
}, [permission, requestPermission, t]);
|
||||
const hasLaunchedScanner = useRef(false);
|
||||
|
||||
/*
|
||||
* Handle barcode scanned - parse and navigate to appropriate page.
|
||||
* Only processes AliasVault QR codes, silently ignores others.
|
||||
* Native scanner already filters by prefix, so we only get AliasVault QR codes here.
|
||||
* Validation is handled by the destination page.
|
||||
*/
|
||||
const handleBarcodeScanned = useCallback(({ data }: { data: string }) : void => {
|
||||
const handleQRCodeScanned = useCallback((data: string) : void => {
|
||||
// Prevent processing the same URL multiple times
|
||||
if (processedUrls.current.has(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the QR code to determine its type
|
||||
const parsedData = parseQRCode(data);
|
||||
|
||||
// Silently ignore non-AliasVault QR codes
|
||||
if (!parsedData.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this URL as processed
|
||||
processedUrls.current.add(data);
|
||||
|
||||
// Parse the QR code to determine its type
|
||||
const parsedData = parseQRCode(data);
|
||||
|
||||
/*
|
||||
* Navigate to the appropriate page based on QR code type
|
||||
* Validation will be handled by the destination page
|
||||
* Use push instead of replace to navigate while scanner is still dismissing
|
||||
* This creates a smoother transition without returning to settings first
|
||||
*/
|
||||
if (parsedData.type === 'MOBILE_UNLOCK') {
|
||||
router.replace(`/(tabs)/settings/mobile-unlock/${parsedData.payload}` as Href);
|
||||
router.push(`/(tabs)/settings/mobile-unlock/${parsedData.payload}` as Href);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Launch the native QR scanner.
|
||||
*/
|
||||
const launchScanner = useCallback(async () => {
|
||||
if (hasLaunchedScanner.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasLaunchedScanner.current = true;
|
||||
|
||||
try {
|
||||
// Pass prefixes to native scanner for filtering and translated status text
|
||||
const prefixes = Object.values(QR_CODE_PREFIXES);
|
||||
const statusText = t('settings.qrScanner.scanningMessage');
|
||||
const scannedData = await NativeVaultManager.scanQRCode(prefixes, statusText);
|
||||
|
||||
if (scannedData) {
|
||||
handleQRCodeScanned(scannedData);
|
||||
} else {
|
||||
// User cancelled or scan failed, go back
|
||||
router.back();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('QR scan error:', error);
|
||||
Alert.alert(
|
||||
t('common.error'),
|
||||
'Failed to scan QR code',
|
||||
[{ text: t('common.ok'), /**
|
||||
* Navigate back.
|
||||
*/
|
||||
onPress: (): void => router.back() }]
|
||||
);
|
||||
}
|
||||
}, [handleQRCodeScanned, t]);
|
||||
|
||||
/**
|
||||
* Reset hasProcessedUrl when URL changes to allow processing new URLs.
|
||||
*/
|
||||
@@ -132,45 +133,24 @@ export default function QRScannerScreen() : React.ReactNode {
|
||||
useEffect(() => {
|
||||
if (url && typeof url === 'string' && !hasProcessedUrl.current) {
|
||||
hasProcessedUrl.current = true;
|
||||
handleBarcodeScanned({ data: url });
|
||||
handleQRCodeScanned(url);
|
||||
}
|
||||
}, [url, handleBarcodeScanned]);
|
||||
}, [url, handleQRCodeScanned]);
|
||||
|
||||
/**
|
||||
* Launch scanner when component mounts (Android/iOS only).
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'android' || Platform.OS === 'ios') {
|
||||
launchScanner();
|
||||
}
|
||||
}, [launchScanner]);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
camera: {
|
||||
flex: 1,
|
||||
},
|
||||
cameraContainer: {
|
||||
backgroundColor: colors.black,
|
||||
flex: 1,
|
||||
},
|
||||
cameraOverlay: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
bottom: 0,
|
||||
justifyContent: 'center',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
cameraOverlayText: {
|
||||
color: colors.white,
|
||||
fontSize: 16,
|
||||
marginTop: 20,
|
||||
paddingHorizontal: 40,
|
||||
textAlign: 'center',
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
top: 16,
|
||||
zIndex: 10,
|
||||
},
|
||||
loadingContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
@@ -179,35 +159,14 @@ export default function QRScannerScreen() : React.ReactNode {
|
||||
},
|
||||
});
|
||||
|
||||
// Show permission request screen
|
||||
if (!permission || !permission.granted) {
|
||||
return (
|
||||
<ThemedContainer>
|
||||
<View style={styles.loadingContainer}>
|
||||
<LoadingIndicator />
|
||||
</View>
|
||||
</ThemedContainer>
|
||||
);
|
||||
}
|
||||
|
||||
// Show loading while scanner is launching
|
||||
return (
|
||||
<ThemedContainer style={styles.container}>
|
||||
<View style={styles.cameraContainer}>
|
||||
<CameraView
|
||||
style={styles.camera}
|
||||
facing="back"
|
||||
barcodeScannerSettings={{
|
||||
barcodeTypes: ['qr'],
|
||||
}}
|
||||
onBarcodeScanned={handleBarcodeScanned}
|
||||
>
|
||||
<View style={styles.cameraOverlay}>
|
||||
<Ionicons name="qr-code-outline" size={100} color={colors.white} />
|
||||
<ThemedText style={styles.cameraOverlayText}>
|
||||
{t('settings.qrScanner.scanningMessage')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</CameraView>
|
||||
<View style={styles.loadingContainer}>
|
||||
<LoadingIndicator />
|
||||
<ThemedText style={{ marginTop: 20, color: colors.textMuted }}>
|
||||
{t('settings.qrScanner.scanningMessage')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</ThemedContainer>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,9 @@ import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import de from './locales/de.json';
|
||||
import en from './locales/en.json';
|
||||
import es from './locales/es.json';
|
||||
import fi from './locales/fi.json';
|
||||
import fr from './locales/fr.json';
|
||||
import he from './locales/he.json';
|
||||
import it from './locales/it.json';
|
||||
import nl from './locales/nl.json';
|
||||
@@ -17,7 +19,9 @@ import zh from './locales/zh.json';
|
||||
const resources = {
|
||||
de: { translation: de },
|
||||
en: { translation: en },
|
||||
es: { translation: es },
|
||||
fi: { translation: fi },
|
||||
fr: { translation: fr },
|
||||
he: { translation: he },
|
||||
nl: { translation: nl },
|
||||
it: { translation: it },
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,9 @@
|
||||
"notice": "Huomautus",
|
||||
"enabled": "Otettu käyttöön",
|
||||
"disabled": "Pois käytöstä",
|
||||
"twoFactorAuthentication": "Two-Factor Authentication",
|
||||
"deleteItemConfirmTitle": "Delete Item",
|
||||
"deleteItemConfirmDescription": "Are you sure you want to delete this item?",
|
||||
"twoFactorAuthentication": "Kaksivaiheinen tunnistautuminen",
|
||||
"deleteItemConfirmTitle": "Poista kohde",
|
||||
"deleteItemConfirmDescription": "Haluatko varmasti poistaa tämän kohteen?",
|
||||
"errors": {
|
||||
"unknownError": "Tapahtui tuntematon virhe. Yritä uudelleen.",
|
||||
"unknownErrorTryAgain": "Tapahtui tuntematon virhe. Yritä uudelleen.",
|
||||
@@ -207,13 +207,13 @@
|
||||
"passkeyWillBeDeleted": "Tämä todennusavain poistetaan, kun tallennat tämän käyttäjätiedon."
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Lisää 2FA TOTP -koodi",
|
||||
"nameOptional": "Nimi (valinnainen)",
|
||||
"secretKey": "Salainen avain",
|
||||
"instructions": "Syötä salainen avain, joka näkyy sivustossa, jossa haluat lisätä kaksivaiheisen tunnistautumisen",
|
||||
"saveToViewCode": "Tallenna nähdäksesi koodin",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Virheellinen salatun avaimen muoto."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -328,8 +328,8 @@
|
||||
"languageDescription": "Aseta kieli, jota käytetään luotaessa uusia henkilöllisyyksiä.",
|
||||
"genderSection": "Sukupuoli",
|
||||
"genderDescription": "Aseta oletussukupuoli uusien henkilöllisyyksien luomiseksi. ",
|
||||
"ageRangeSection": "Age Range",
|
||||
"ageRangeDescription": "Set the age range for generating new identities.",
|
||||
"ageRangeSection": "Ikähaarukka",
|
||||
"ageRangeDescription": "Aseta ikähaarukka uusia henkilöllisyyksien luomisessa",
|
||||
"genderOptions": {
|
||||
"random": "Satunnainen",
|
||||
"male": "Mies",
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
{
|
||||
"common": {
|
||||
"cancel": "Annuler",
|
||||
"close": "Close",
|
||||
"close": "Fermer",
|
||||
"delete": "Supprimer",
|
||||
"save": "Sauvegarder",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"ok": "OK",
|
||||
"continue": "Continue",
|
||||
"loading": "Loading",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"never": "Never",
|
||||
"copied": "Copied to clipboard",
|
||||
"continue": "Continuer",
|
||||
"loading": "Chargement",
|
||||
"error": "Erreur",
|
||||
"success": "Succès",
|
||||
"never": "Jamais",
|
||||
"copied": "Copier dans le presse-papiers",
|
||||
"loadMore": "Voir plus",
|
||||
"use": "Use",
|
||||
"confirm": "Confirm",
|
||||
"next": "Next",
|
||||
"notice": "Notice",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"twoFactorAuthentication": "Two-Factor Authentication",
|
||||
"deleteItemConfirmTitle": "Delete Item",
|
||||
"deleteItemConfirmDescription": "Are you sure you want to delete this item?",
|
||||
"use": "Utiliser",
|
||||
"confirm": "Confirmer",
|
||||
"next": "Suivant",
|
||||
"notice": "Notification",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"twoFactorAuthentication": "Authentification à deux facteurs",
|
||||
"deleteItemConfirmTitle": "Supprimer l'élement",
|
||||
"deleteItemConfirmDescription": "Êtes-vous certain de vouloir supprimer cet élément?",
|
||||
"errors": {
|
||||
"unknownError": "An unknown error occurred. Please try again.",
|
||||
"unknownErrorTryAgain": "An unknown error occurred. Please try again.",
|
||||
"serverVersionTooOld": "The AliasVault server needs to be updated to a newer version in order to use this feature. Please contact the server admin if you need help."
|
||||
"unknownError": "Une erreur inconnue s'est produite. Merci de réessayer.",
|
||||
"unknownErrorTryAgain": "Une erreur inconnue s'est produite. Merci de réessayer.",
|
||||
"serverVersionTooOld": "Le serveur AliasVault doit être mis à jour vers une version plus récente pour pouvoir utiliser cette fonctionnalité. Veuillez contacter l'administrateur du serveur si vous avez besoin d'aide."
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"login": "Log in",
|
||||
"logout": "Logout",
|
||||
"username": "Username or email",
|
||||
"password": "Password",
|
||||
"authCode": "Authentication Code",
|
||||
"unlock": "Unlock",
|
||||
"unlocking": "Unlocking...",
|
||||
"loggingIn": "Logging in",
|
||||
"validatingCredentials": "Validating credentials",
|
||||
"syncingVault": "Syncing vault",
|
||||
"verifyingAuthCode": "Verifying authentication code",
|
||||
"verify": "Verify",
|
||||
"unlockVault": "Unlock Vault",
|
||||
"unlockWithPin": "Unlock with PIN",
|
||||
"enterPassword": "Enter your password to unlock your vault",
|
||||
"enterPasswordPlaceholder": "Password",
|
||||
"enterAuthCode": "Enter 6-digit code",
|
||||
"login": "Se connecter",
|
||||
"logout": "Se déconnecter",
|
||||
"username": "Nom d'utilisateur ou email",
|
||||
"password": "Mot de passe",
|
||||
"authCode": "Code d'authentification",
|
||||
"unlock": "Déverrouiller",
|
||||
"unlocking": "Déverrouillage...",
|
||||
"loggingIn": "Connexion en cours",
|
||||
"validatingCredentials": "Validation des identifiants",
|
||||
"syncingVault": "Synchronisation du coffre",
|
||||
"verifyingAuthCode": "Vérification du code d'authentification",
|
||||
"verify": "Vérifier",
|
||||
"unlockVault": "Déverrouiller le coffre",
|
||||
"unlockWithPin": "Déverrouiller avec un code PIN",
|
||||
"enterPassword": "Entrez votre mot de passe principal pour déverrouiller votre coffre-fort",
|
||||
"enterPasswordPlaceholder": "Mot de passe",
|
||||
"enterAuthCode": "Saisissez le code à 6 chiffres",
|
||||
"usernamePlaceholder": "nom / nom@entreprise.com",
|
||||
"passwordPlaceholder": "Enter your password",
|
||||
"enableBiometric": "Enable {{biometric}}?",
|
||||
"biometricPrompt": "Would you like to use {{biometric}} to unlock your vault?",
|
||||
"tryBiometricAgain": "Try {{biometric}} Again",
|
||||
"tryPinAgain": "Try PIN Again",
|
||||
"authCodeNote": "Note: if you don't have access to your authenticator device, you can reset your 2FA with a recovery code by logging in via the website.",
|
||||
"passwordPlaceholder": "Saisissez votre mot de passe",
|
||||
"enableBiometric": "Activer {{biometric}}?",
|
||||
"biometricPrompt": "Voulez-vous utiliser {{biometric}} pour déverrouiller votre coffre ?",
|
||||
"tryBiometricAgain": "Réessayez {{biometric}}",
|
||||
"tryPinAgain": "Réessayez le PIN",
|
||||
"authCodeNote": "Remarque : si vous n'avez pas accès à votre appareil d'authentification, vous pouvez réinitialiser votre authentification 2FA avec un code de récupération en vous connectant via le site web.",
|
||||
"errors": {
|
||||
"credentialsRequired": "Username and password are required",
|
||||
"invalidAuthCode": "Please enter a valid 6-digit authentication code",
|
||||
"credentialsRequired": "Le nom d'utilisateur et le mot de passe sont requis",
|
||||
"invalidAuthCode": "Veuillez entrer un code d'authentification à 6 chiffres valide",
|
||||
"incorrectPassword": "Mot de passe incorrect. Veuillez réessayer.",
|
||||
"enterPassword": "Please enter your password",
|
||||
"serverError": "Could not reach AliasVault server. Please try again later or contact support if the problem persists.",
|
||||
"serverErrorSelfHosted": "Could not reach the API. For self-hosted instances, please verify the API endpoint is reachable by navigating to it in a browser: it should display 'OK'.",
|
||||
"networkError": "Network request failed. Please check your internet connection and try again.",
|
||||
"networkErrorSelfHosted": "Network request failed. Check your network connection and server availability. For self-hosted instances, please ensure you have a valid SSL certificate installed. Self-signed certificates are not supported on mobile devices for security reasons.",
|
||||
"sessionExpired": "Your session has expired. Please login again.",
|
||||
"httpError": "HTTP error: {{status}}"
|
||||
"enterPassword": "Veuillez saisir votre mot de passe",
|
||||
"serverError": "Impossible d'accéder au serveur AliasVault. Veuillez réessayer plus tard ou contacter le support si le problème persiste.",
|
||||
"serverErrorSelfHosted": "Impossible d'atteindre l'API. Pour les instances auto-hébergées, veuillez vérifier que le point de terminaison de l'API est accessible en naviguant vers celui-ci dans un navigateur : il devrait afficher 'OK'.",
|
||||
"networkError": "Erreur réseau. Vérifiez votre connexion et réessayez.",
|
||||
"networkErrorSelfHosted": "La requête réseau a échoué. Vérifiez votre connexion réseau et la disponibilité du serveur. Pour les instances auto-hébergées, veuillez vous assurer que vous avez un certificat SSL valide. Les certificats auto-signés ne sont pas pris en charge pour des raisons de sécurité.",
|
||||
"sessionExpired": "Votre session a expiré. Veuillez vous reconnecter.",
|
||||
"httpError": "Erreur HTTP : {{status}}"
|
||||
},
|
||||
"confirmLogout": "Are you sure you want to logout? You need to login again with your master password to access your vault.",
|
||||
"confirmLogout": "Êtes-vous sûr de vouloir vous déconnecter ? Vous devez vous reconnecter avec votre mot de passe maître pour accéder à votre coffre.",
|
||||
"noAccountYet": "Pas encore de compte ?",
|
||||
"createNewVault": "Créer un nouveau coffre-fort",
|
||||
"connectingTo": "Connexion à",
|
||||
"loggedInAs": "Connecté en tant que"
|
||||
},
|
||||
"vault": {
|
||||
"syncingVault": "Syncing vault",
|
||||
"uploadingVaultToServer": "Uploading vault to server",
|
||||
"savingChangesToVault": "Saving changes to vault",
|
||||
"checkingForVaultUpdates": "Checking for vault updates",
|
||||
"executingOperation": "Executing operation...",
|
||||
"checkingVaultUpdates": "Checking vault updates",
|
||||
"syncingUpdatedVault": "Syncing updated vault",
|
||||
"syncingVault": "Synchronisation du coffre",
|
||||
"uploadingVaultToServer": "Envoi du coffre sur le serveur",
|
||||
"savingChangesToVault": "Enregistrement des modifications dans le coffre",
|
||||
"checkingForVaultUpdates": "Vérification des mises à jour du coffre",
|
||||
"executingOperation": "Exécution de l'opération...",
|
||||
"checkingVaultUpdates": "Vérification des mises à jour du coffre",
|
||||
"syncingUpdatedVault": "Synchronisation du coffre mis à jour",
|
||||
"errors": {
|
||||
"failedToGetEncryptedDatabase": "Failed to get encrypted database",
|
||||
"usernameNotFound": "Username not found",
|
||||
"vaultOutdated": "Your vault is outdated. Please login on the AliasVault website and follow the steps.",
|
||||
"failedToSyncVault": "Failed to sync vault",
|
||||
"versionNotSupported": "This version of the AliasVault mobile app is not supported by the server anymore. Please update your app to the latest version.",
|
||||
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this mobile app. Please contact support if you need help.",
|
||||
"failedToGetEncryptedDatabase": "Impossible d'obtenir la base de données chiffrée",
|
||||
"usernameNotFound": "Identifiant non trouvé",
|
||||
"vaultOutdated": "Votre coffre est obsolète. Veuillez vous connecter sur le site AliasVault et suivre les étapes.",
|
||||
"failedToSyncVault": "Échec de la synchronisation du coffre",
|
||||
"versionNotSupported": "Cette version de l'application mobile AliasVault n'est plus prise en charge par le serveur. Veuillez mettre à jour votre application vers la dernière version.",
|
||||
"serverVersionNotSupported": "Le serveur AliasVault doit être mis à jour vers une version plus récente afin d'utiliser cette application mobile. Veuillez contacter le support si vous avez besoin d'aide.",
|
||||
"appOutdated": "Cette application est obsolète et ne peut pas être utilisée pour accéder à cette (nouvelle) version du coffre. Veuillez mettre à jour l'application AliasVault pour continuer.",
|
||||
"passwordChanged": "Your password has changed since the last time you logged in. Please login again for security reasons."
|
||||
"passwordChanged": "Votre mot de passe a changé depuis la dernière fois que vous vous êtes connecté. Veuillez vous reconnecter pour des raisons de sécurité."
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"title": "Credentials",
|
||||
"addCredential": "Add Credential",
|
||||
"editCredential": "Edit Credential",
|
||||
"deleteCredential": "Delete Credential",
|
||||
"title": "Identifiants",
|
||||
"addCredential": "Ajouter un identifiant",
|
||||
"editCredential": "Modifier l'identifiant",
|
||||
"deleteCredential": "Supprimer l'identifiant",
|
||||
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer ces identifiants ? Cette action est irréversible.",
|
||||
"service": "Service",
|
||||
"serviceName": "Nom du service",
|
||||
@@ -113,116 +113,116 @@
|
||||
"birthDate": "Date de naissance",
|
||||
"birthDatePlaceholder": "AAAA-MM-JJ",
|
||||
"notes": "Notes",
|
||||
"randomAlias": "Random Alias",
|
||||
"manual": "Manual",
|
||||
"generateRandomAlias": "Generate Random Alias",
|
||||
"clearAliasFields": "Clear Alias Fields",
|
||||
"enterFullEmail": "Enter full email address",
|
||||
"enterEmailPrefix": "Enter email prefix",
|
||||
"useDomainChooser": "Use domain chooser",
|
||||
"enterCustomDomain": "Enter custom domain",
|
||||
"selectEmailDomain": "Select Email Domain",
|
||||
"privateEmailTitle": "Private Email",
|
||||
"privateEmailAliasVaultServer": "AliasVault server",
|
||||
"privateEmailDescription": "E2E encrypted, fully private.",
|
||||
"publicEmailTitle": "Public Temp Email Providers",
|
||||
"publicEmailDescription": "Anonymous but limited privacy. Email content is readable by anyone that knows the address.",
|
||||
"searchPlaceholder": "Search vault...",
|
||||
"noMatchingCredentials": "No matching credentials found",
|
||||
"noCredentialsFound": "No credentials found. Create one to get started. Tip: you can also login to the AliasVault web app to import credentials from other password managers.",
|
||||
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
|
||||
"noAttachmentsFound": "No credentials with attachments found",
|
||||
"recentEmails": "Recent emails",
|
||||
"loadingEmails": "Loading emails...",
|
||||
"noEmailsYet": "No emails received yet.",
|
||||
"offlineEmailsMessage": "You are offline. Please connect to the internet to load your emails.",
|
||||
"emailLoadError": "An error occurred while loading emails. Please try again later.",
|
||||
"emailUnexpectedError": "An unexpected error occurred while loading emails. Please try again later.",
|
||||
"password": "Password",
|
||||
"passwordLength": "Password Length",
|
||||
"changePasswordComplexity": "Password Settings",
|
||||
"includeLowercase": "Lowercase (a-z)",
|
||||
"includeUppercase": "Uppercase (A-Z)",
|
||||
"includeNumbers": "Numbers (0-9)",
|
||||
"includeSpecialChars": "Special Characters (!@#)",
|
||||
"avoidAmbiguousChars": "Avoid Ambiguous Characters",
|
||||
"deletingCredential": "Deleting credential...",
|
||||
"errorLoadingCredentials": "Error loading credentials",
|
||||
"vaultSyncFailed": "Vault sync failed",
|
||||
"vaultSyncedSuccessfully": "Vault synced successfully",
|
||||
"vaultUpToDate": "Vault is up-to-date",
|
||||
"offlineMessage": "You are offline. Please connect to the internet to sync your vault.",
|
||||
"credentialCreated": "Credential Created!",
|
||||
"credentialCreatedMessage": "Your new credential has been added to your vault and is ready to use.",
|
||||
"credentialDetails": "Credential Details",
|
||||
"emailPreview": "Email Preview",
|
||||
"switchBackToBrowser": "Switch back to your browser to continue.",
|
||||
"randomAlias": "Alias aléatoire",
|
||||
"manual": "Manuel",
|
||||
"generateRandomAlias": "Générer un alias aléatoire",
|
||||
"clearAliasFields": "Effacer les champs d'alias",
|
||||
"enterFullEmail": "Entrez l'adresse email complète",
|
||||
"enterEmailPrefix": "Entrez le préfixe de l'email",
|
||||
"useDomainChooser": "Utiliser le sélecteur de domaine",
|
||||
"enterCustomDomain": "Entrez le domaine personnalisé",
|
||||
"selectEmailDomain": "Sélectionner un domaine de messagerie",
|
||||
"privateEmailTitle": "E-mail privé",
|
||||
"privateEmailAliasVaultServer": "Serveur AliasVault",
|
||||
"privateEmailDescription": "E2E chiffré, entièrement privé.",
|
||||
"publicEmailTitle": "Fournisseurs d'email public temporaires",
|
||||
"publicEmailDescription": "Anonyme mais confidentiel limitée. Le contenu des e-mails est lisible par toute personne qui connaît l'adresse.",
|
||||
"searchPlaceholder": "Rechercher dans le coffre...",
|
||||
"noMatchingCredentials": "Aucun identifiant correspondant trouvé",
|
||||
"noCredentialsFound": "Aucun identifiant trouvé. Créez en un pour commencer. Astuce : vous pouvez également vous connecter à l'application web AliasVault pour importer les identifiants depuis d'autres gestionnaires de mots de passe.",
|
||||
"noPasskeysFound": "Aucune clé d'accès n'a encore été créée. Les clés d'accès sont créés en visitant un site Web qui propose des clés d'accès comme méthode d'authentification.",
|
||||
"noAttachmentsFound": "Aucun identifiant avec des pièces jointes trouvé",
|
||||
"recentEmails": "E-mails récents",
|
||||
"loadingEmails": "Chargement des e-mails...",
|
||||
"noEmailsYet": "Pas encore d'e-mails reçus.",
|
||||
"offlineEmailsMessage": "Vous êtes déconnecté. Veuillez vous connecter à internet pour charger vos e-mails.",
|
||||
"emailLoadError": "Une erreur s'est produite lors du chargement des e-mails. Veuillez réessayer plus tard.",
|
||||
"emailUnexpectedError": "Une erreur inattendue s'est produite lors du chargement des e-mails. Veuillez réessayer plus tard.",
|
||||
"password": "Mot de passe",
|
||||
"passwordLength": "Longueur du mot de passe",
|
||||
"changePasswordComplexity": "Paramètres du mot de passe",
|
||||
"includeLowercase": "Minuscules (a-z)",
|
||||
"includeUppercase": "Majuscules (A-Z)",
|
||||
"includeNumbers": "Nombres (0-9)",
|
||||
"includeSpecialChars": "Caractères spéciaux (!@#)",
|
||||
"avoidAmbiguousChars": "Éviter les caractères ambigus",
|
||||
"deletingCredential": "Suppression de l'identifiant...",
|
||||
"errorLoadingCredentials": "Erreur lors du chargement des identifiants",
|
||||
"vaultSyncFailed": "Échec de la synchronisation du coffre",
|
||||
"vaultSyncedSuccessfully": "Le coffre a été synchronisé avec succès",
|
||||
"vaultUpToDate": "Le coffre est à jour",
|
||||
"offlineMessage": "Vous êtes déconnecté. Veuillez vous connecter à internet pour synchroniser votre coffre.",
|
||||
"credentialCreated": "Identifiant créé!",
|
||||
"credentialCreatedMessage": "Votre nouvel identifiant a été ajouté à votre coffre et est prêt à être utilisé.",
|
||||
"credentialDetails": "Détails de l'identifiant",
|
||||
"emailPreview": "Aperçu de l'e-mail",
|
||||
"switchBackToBrowser": "Passez à votre navigateur pour continuer.",
|
||||
"filters": {
|
||||
"all": "(All) Credentials",
|
||||
"passkeys": "Passkeys",
|
||||
"aliases": "Aliases",
|
||||
"userpass": "Passwords",
|
||||
"attachments": "Attachments"
|
||||
"all": "(Tous les) Identifiants de connexion",
|
||||
"passkeys": "Clés d'accès",
|
||||
"aliases": "Alias",
|
||||
"userpass": "Mots de passe",
|
||||
"attachments": "Pièces jointes"
|
||||
},
|
||||
"twoFactorAuth": "Two-factor authentication",
|
||||
"totpCode": "TOTP Code",
|
||||
"attachments": "Attachments",
|
||||
"deleteAttachment": "Delete",
|
||||
"fileSavedTo": "File saved to",
|
||||
"previewNotSupported": "Preview not supported",
|
||||
"downloadToView": "Download the file to view it",
|
||||
"twoFactorAuth": "Authentification à deux facteurs",
|
||||
"totpCode": "Code à usage unique",
|
||||
"attachments": "Pièces jointes",
|
||||
"deleteAttachment": "Supprimer",
|
||||
"fileSavedTo": "Fichier enregistré sous",
|
||||
"previewNotSupported": "Aperçu non supporté",
|
||||
"downloadToView": "Télécharger le fichier pour le voir",
|
||||
"unsavedChanges": {
|
||||
"title": "Discard Changes?",
|
||||
"message": "You have unsaved changes. Are you sure you want to discard them?",
|
||||
"discard": "Discard"
|
||||
"title": "Annuler les modifications?",
|
||||
"message": "Vos modifications n'ont pas été enregistrées. Voulez-vous vraiment les ignorer ?",
|
||||
"discard": "Ignorer"
|
||||
},
|
||||
"toasts": {
|
||||
"credentialUpdated": "Credential updated successfully",
|
||||
"credentialCreated": "Credential created successfully",
|
||||
"credentialDeleted": "Credential deleted successfully",
|
||||
"usernameCopied": "Username copied to clipboard",
|
||||
"emailCopied": "Email copied to clipboard",
|
||||
"passwordCopied": "Password copied to clipboard"
|
||||
"credentialUpdated": "Identifiant mis à jour avec succès",
|
||||
"credentialCreated": "Identifiant créé avec succès",
|
||||
"credentialDeleted": "Identifiant supprimé avec succès",
|
||||
"usernameCopied": "Nom d'utilisateur copié dans le presse-papiers",
|
||||
"emailCopied": "E-mail copié dans le presse-papiers",
|
||||
"passwordCopied": "Mot de passe copié dans le presse-papiers"
|
||||
},
|
||||
"createNewAliasFor": "Create new alias for",
|
||||
"createNewAliasFor": "Créer un nouvel alias pour",
|
||||
"errors": {
|
||||
"loadFailed": "Failed to load credential",
|
||||
"saveFailed": "Failed to save credential"
|
||||
"loadFailed": "Échec du chargement de l'identifiant",
|
||||
"saveFailed": "Échec de l'enregistrement de l'identifiant"
|
||||
},
|
||||
"contextMenu": {
|
||||
"title": "Credential Options",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"copyUsername": "Copy Username",
|
||||
"copyEmail": "Copy Email",
|
||||
"copyPassword": "Copy Password"
|
||||
"title": "Options de l'identifiant",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"copyUsername": "Copier le nom d'utilisateur",
|
||||
"copyEmail": "Copier l'e-mail",
|
||||
"copyPassword": "Copier le mot de passe"
|
||||
}
|
||||
},
|
||||
"passkeys": {
|
||||
"passkey": "Passkey",
|
||||
"passkey": "Clé d'identification",
|
||||
"site": "Site",
|
||||
"displayName": "Display Name",
|
||||
"helpText": "Passkeys are created on the website when prompted. They cannot be manually edited. To remove this passkey, you can delete it from this credential.",
|
||||
"passkeyMarkedForDeletion": "Passkey marked for deletion",
|
||||
"passkeyWillBeDeleted": "This passkey will be deleted when you save this credential."
|
||||
"displayName": "Nom affiché",
|
||||
"helpText": "Les clés d'accès sont créées sur le site Web lorsque vous y êtes invité. Elles ne peuvent pas être modifiées manuellement. Pour supprimer cette clé, vous pouvez la supprimer de cet identifiant.",
|
||||
"passkeyMarkedForDeletion": "Clé d'accès marquée pour suppression",
|
||||
"passkeyWillBeDeleted": "Cette clé d'accès sera supprimée lorsque vous enregistrerez cet identifiant."
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Ajouter un code 2FA",
|
||||
"nameOptional": "Nom (facultatif)",
|
||||
"secretKey": "Clé secrète",
|
||||
"instructions": "Entrez la clé secrète affichée par le site Web où vous souhaitez ajouter l'authentification à deux facteurs.",
|
||||
"saveToViewCode": "Enregistrer pour afficher le code",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Format de clé secrète invalide."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"autofill": "Autofill & Passkeys",
|
||||
"title": "Réglages",
|
||||
"autofill": "Remplissage automatique et clés d'accès",
|
||||
"iosAutofillSettings": {
|
||||
"headerText": "You can configure AliasVault to provide native password and passkey autofill functionality in iOS. Follow the instructions below to enable it.",
|
||||
"passkeyNotice": "Passkeys are created through iOS. To store them in AliasVault, ensure Autofill below is enabled.",
|
||||
"howToEnable": "How to enable Autofill & Passkeys:",
|
||||
"headerText": "Vous pouvez configurer AliasVault pour fournir des fonctionnalités natives de saisie automatique du mot de passe et de clés d'accès dans iOS. Suivez les instructions ci-dessous pour l'activer.",
|
||||
"passkeyNotice": "Les clés d'accès sont créées via iOS. Pour les stocker dans AliasVault, assurez-vous que le remplissage automatique ci-dessous est activé.",
|
||||
"howToEnable": "Comment activer le remplissage automatique et les clés d'accès :",
|
||||
"step1": "1. Ouvrir les paramètres d'iOS via le bouton ci-dessous",
|
||||
"step2": "2. Aller dans « Général »",
|
||||
"step3": "3. Appuyer sur « Remplissage automatique et mots de passe »",
|
||||
@@ -233,50 +233,50 @@
|
||||
"warningText": "Note : Vous devrez vous authentifier avec Face ID/Touch ID ou votre code d'accès lorsque vous utilisez le remplissage automatique."
|
||||
},
|
||||
"androidAutofillSettings": {
|
||||
"warningTitle": "⚠️ Experimental Feature",
|
||||
"warningDescription": "Autofill and passkey support for Android is currently in an experimental state.",
|
||||
"warningLink": "Read more about it here",
|
||||
"headerText": "You can configure AliasVault to provide native password and passkey autofill functionality in Android. Follow the instructions below to enable it.",
|
||||
"passkeyNotice": "Passkeys are created through Android Credential Manager (Android 14+). To store them in AliasVault, ensure Autofill below is enabled.",
|
||||
"howToEnable": "How to enable Autofill & Passkeys:",
|
||||
"step1": "1. Open Android Settings via the button below, and change the \"autofill preferred service\" to \"AliasVault\"",
|
||||
"openAutofillSettings": "Open Autofill Settings",
|
||||
"buttonTip": "If the button above doesn't work it might be blocked because of security settings. You can manually go to Android Settings → General Management → Passwords and autofill.",
|
||||
"step2": "2. Some apps, e.g. Google Chrome, may require manual configuration in their settings to allow third-party autofill apps. However, most apps should work with autofill by default.",
|
||||
"alreadyConfigured": "I already configured it",
|
||||
"advancedOptions": "Advanced Options",
|
||||
"showSearchText": "Show search text",
|
||||
"showSearchTextDescription": "Include the text AliasVault receives from Android that it uses to search for a matching credential"
|
||||
"warningTitle": "⚠️ Fonctionnalité expérimentale",
|
||||
"warningDescription": "Le remplissage automatique et la prise en charge de la clé d'accès pour Android sont actuellement dans un état expérimental.",
|
||||
"warningLink": "En savoir plus à ce sujet ici",
|
||||
"headerText": "Vous pouvez configurer AliasVault pour fournir le mot de passe natif et la fonctionnalité de saisie automatique du mot de passe dans Android. Suivez les instructions ci-dessous pour l'activer.",
|
||||
"passkeyNotice": "Les mots de passe sont créés via le gestionnaire d'identifiants Android (Android 14+). Pour les stocker dans AliasVault, assurez-vous que le remplissage automatique ci-dessous est activé.",
|
||||
"howToEnable": "Comment activer le remplissage automatique et les clés d'accès:",
|
||||
"step1": "1. Ouvrez les paramètres Android via le bouton ci-dessous, et changez le \"service préféré de saisie automatique\" par \"AliasVault\"",
|
||||
"openAutofillSettings": "Ouvrir les paramètres de remplissage automatique",
|
||||
"buttonTip": "Si le bouton ci-dessus ne fonctionne pas, il peut être bloqué en raison des paramètres de sécurité. Vous pouvez manuellement aller dans Réglages Android → Gestion Générale → Mots de passe et saisie automatique.",
|
||||
"step2": "2. Certaines applications, par exemple Google Chrome, peuvent nécessiter une configuration manuelle dans leurs paramètres pour permettre le remplissage automatique des applications tierces. Cependant, la plupart des applications devraient fonctionner avec le remplissage automatique par défaut.",
|
||||
"alreadyConfigured": "Je l'ai déjà configuré",
|
||||
"advancedOptions": "Options avancées",
|
||||
"showSearchText": "Afficher le texte de recherche",
|
||||
"showSearchTextDescription": "Inclure le texte que AliasVault reçoit d'Android qu'il utilise pour rechercher un identifiant correspondant"
|
||||
},
|
||||
"vaultUnlock": "Méthode de déverrouillage du coffre-fort",
|
||||
"autoLock": "Délai de verrouillage automatique",
|
||||
"clipboardClear": "Clear Clipboard",
|
||||
"clipboardClearDescription": "Automatically clear copied passwords and sensitive information from your clipboard after a specified time period.",
|
||||
"clipboardClearAndroidWarning": "Note: some Android devices have clipboard history enabled, which may keep track of previously copied items, even after AliasVault clears the clipboard. AliasVault can only overwrite the most recent item, but older entries may remain visible in history. For security reasons, we recommend disabling any clipboard history features in your device settings.",
|
||||
"clipboardClear": "Effacer le presse-papiers",
|
||||
"clipboardClearDescription": "Effacer automatiquement les mots de passe copiés et les informations sensibles du presse-papiers après une période donnée.",
|
||||
"clipboardClearAndroidWarning": "Remarque : certains appareils Android ont l'historique du presse-papiers activé, qui peut garder une trace des éléments précédemment copiés, même après que AliasVault a effacé le presse-papiers. AliasVault ne peut que remplacer l'élément le plus récent, mais les entrées plus anciennes peuvent rester visibles dans l'historique. Pour des raisons de sécurité, nous vous recommandons de désactiver toutes les fonctionnalités d'historique du presse-papiers dans les paramètres de votre appareil.",
|
||||
"clipboardClearOptions": {
|
||||
"never": "Never",
|
||||
"5seconds": "5 seconds",
|
||||
"10seconds": "10 seconds",
|
||||
"15seconds": "15 seconds",
|
||||
"30seconds": "30 seconds"
|
||||
"never": "Jamais",
|
||||
"5seconds": "5 secondes",
|
||||
"10seconds": "10 secondes",
|
||||
"15seconds": "15 secondes",
|
||||
"30seconds": "30 secondes"
|
||||
},
|
||||
"batteryOptimizationHelpTitle": "Enable Background Clipboard Clearing",
|
||||
"batteryOptimizationActive": "Battery optimization is blocking background tasks",
|
||||
"batteryOptimizationDisabled": "Background clipboard clearing enabled",
|
||||
"batteryOptimizationHelpDescription": "Android's battery optimization prevents reliable clipboard clearing when the app is in the background. Disabling battery optimization for AliasVault allows precise background clipboard clearing and automatically grants necessary alarm permissions.",
|
||||
"disableBatteryOptimization": "Disable battery optimization",
|
||||
"batteryOptimizationHelpTitle": "Activer le nettoyage du presse-papier en arrière-plan",
|
||||
"batteryOptimizationActive": "L'optimisation de la batterie bloque les tâches en arrière-plan",
|
||||
"batteryOptimizationDisabled": "Effacement du presse-papiers en arrière-plan activé",
|
||||
"batteryOptimizationHelpDescription": "L'optimisation de la batterie d'Android empêche le nettoyage fiable du presse-papiers lorsque l'application est en arrière-plan. La désactivation de l'optimisation de la batterie pour AliasVault permet un nettoyage précis du presse-papiers et accorde automatiquement les autorisations d'alarme nécessaires.",
|
||||
"disableBatteryOptimization": "Désactiver l'optimisation de la batterie",
|
||||
"identityGenerator": "Générateur d'identité",
|
||||
"passwordGenerator": "Password Generator",
|
||||
"importExport": "Import / Export",
|
||||
"importSectionTitle": "Import",
|
||||
"importSectionDescription": "Import your passwords from other password managers or from a previous AliasVault export.",
|
||||
"importWebNote": "To import credentials from existing password managers, please login to the web app. The import feature is currently only available on the web version.",
|
||||
"exportSectionTitle": "Export",
|
||||
"exportSectionDescription": "Export your vault data to a CSV file. This file can be used as a back-up and can also be imported into other password managers.",
|
||||
"exportCsvButton": "Export vault to CSV file",
|
||||
"exporting": "Exporting...",
|
||||
"exportConfirmTitle": "Export Vault",
|
||||
"exportWarning": "Warning: Exporting your vault to an unencrypted file will expose all of your passwords and sensitive information in plain text. Only do this on trusted devices and ensure you:\n\n• Store the exported file in a secure location\n• Delete the file when you no longer need it\n• Never share the exported file with others\n\nAre you sure you want to continue with the export?",
|
||||
"passwordGenerator": "Générateur de mot de passe",
|
||||
"importExport": "Importer / Exporter",
|
||||
"importSectionTitle": "Importer",
|
||||
"importSectionDescription": "Importez vos mots de passe depuis d'autres gestionnaires de mots de passe ou depuis un précédent export AliasVault.",
|
||||
"importWebNote": "Pour importer des informations d’identification à partir des gestionnaires de mots de passe existants, veuillez vous connecter à l’application Web. La fonction d’importation n’est actuellement disponible que sur la version web.",
|
||||
"exportSectionTitle": "Exporter",
|
||||
"exportSectionDescription": "Exporter les données de votre coffre vers un fichier CSV. Ce fichier peut être utilisé comme une sauvegarde et peut également être importé dans d'autres gestionnaires de mots de passe.",
|
||||
"exportCsvButton": "Exporter le coffre vers un fichier CSV",
|
||||
"exporting": "Exportation en cours...",
|
||||
"exportConfirmTitle": "Exporter le coffre",
|
||||
"exportWarning": "Attention : L'exportation de votre coffre-fort vers un fichier non chiffré exposera tous vos mots de passe et informations sensibles en texte clair. Effectuez cette opération uniquement sur un appareil de confiance et veillez à : \n• Stocker le fichier exporté dans un emplacement sécurisé \n• Supprimer le fichier dès que vous n'en avez plus besoin \n• Ne jamais partager le fichier exporté avec d'autres personnes \n\nÊtes-vous sûr de vouloir poursuivre l'exportation ?",
|
||||
"security": "Sécurité",
|
||||
"appVersion": "Version de l'application {{version}} ({{url}})",
|
||||
"autoLockOptions": {
|
||||
@@ -295,29 +295,29 @@
|
||||
"openSettings": "Ouvrir les paramètres",
|
||||
"vaultUnlockSettings": {
|
||||
"description": "Choisissez comment vous souhaitez déverrouiller votre coffre-fort.",
|
||||
"biometrics": "Biometrics",
|
||||
"faceId": "Face ID",
|
||||
"touchId": "Touch ID",
|
||||
"biometrics": "Biométrie",
|
||||
"faceId": "Identification faciale (Face ID)",
|
||||
"touchId": "Empreinte digitale",
|
||||
"faceIdTouchId": "Face ID / Touch ID",
|
||||
"biometricEnabled": "{{biometric}} est désormais activé avec succès",
|
||||
"biometricNotAvailable": "{{biometric}} non disponible",
|
||||
"biometricDisabledMessage": "{{biometric}} est désactivé pour AliasVault. Pour l'utiliser, veuillez d'abord l'activer dans les paramètres de votre appareil.",
|
||||
"biometricHelp": "Use biometrics to unlock your vault, which is secured by the {{keystore}}.",
|
||||
"biometricHelp": "Utilisez la biométrie pour déverrouiller votre coffre, qui est sécurisé par l’ {{keystore}}.",
|
||||
"biometricUnavailableHelp": "{{biometric}} n'est pas disponible. Appuyez pour ouvrir les paramètres et/ou allez dans les paramètres de votre appareil pour l'activer et le configurer.",
|
||||
"pin": "PIN Code",
|
||||
"pinDescription": "Use a custom PIN code to unlock your vault more quickly.",
|
||||
"pinEnabled": "PIN unlock enabled successfully",
|
||||
"pinDisabled": "PIN unlock has been disabled",
|
||||
"setupPin": "Setup PIN",
|
||||
"enterNewPin": "Enter New PIN",
|
||||
"enterNewPinDescription": "Choose a PIN to unlock your vault",
|
||||
"confirmPin": "Confirm PIN",
|
||||
"confirmPinDescription": "Re-enter your PIN to confirm",
|
||||
"pinMismatch": "PINs do not match. Please try again.",
|
||||
"pinLocked": "PIN locked after too many failed attempts. Please use your master password.",
|
||||
"pin": "Code PIN",
|
||||
"pinDescription": "Utilisez un code PIN personnalisé pour déverrouiller votre coffre plus rapidement.",
|
||||
"pinEnabled": "Déverrouillage par code PIN activé avec succès",
|
||||
"pinDisabled": "Déverrouillage par code PIN désactivé",
|
||||
"setupPin": "Configurer le code PIN",
|
||||
"enterNewPin": "Entrer le nouveau code PIN",
|
||||
"enterNewPinDescription": "Entrez votre code PIN pour déverrouiller votre coffre",
|
||||
"confirmPin": "Confirmer le code PIN",
|
||||
"confirmPinDescription": "Entrez à nouveau votre code PIN pour confirmer",
|
||||
"pinMismatch": "Les codes PIN ne correspondent pas. Veuillez réessayer.",
|
||||
"pinLocked": "Code PIN verrouillé après trop de tentatives infructueuses. Veuillez utiliser votre mot de passe principal.",
|
||||
"passwordHelp": "Saisissez à nouveau votre mot de passe maître complet pour déverrouiller votre coffre. Ceci est toujours activé comme option de repli.",
|
||||
"keystoreIOS": "Trousseau iOS",
|
||||
"keystoreAndroid": "Android Keystore"
|
||||
"keystoreAndroid": "KeyStore Android"
|
||||
},
|
||||
"autoLockSettings": {
|
||||
"description": "Choisissez combien de temps l'application peut rester en arrière-plan avant de nécessiter une ré-authentification. Vous devrez utiliser Face ID ou saisir votre mot de passe pour déverrouiller le coffre à nouveau."
|
||||
@@ -328,8 +328,8 @@
|
||||
"languageDescription": "Définissez la langue qui sera utilisée pour générer de nouvelles identités.",
|
||||
"genderSection": "Genre",
|
||||
"genderDescription": "Définissez la préférence de genre pour la génération de nouvelles identités.",
|
||||
"ageRangeSection": "Age Range",
|
||||
"ageRangeDescription": "Set the age range for generating new identities.",
|
||||
"ageRangeSection": "Tranches d'âges",
|
||||
"ageRangeDescription": "Définir la tranche d'âge pour la génération de nouvelles identités.",
|
||||
"genderOptions": {
|
||||
"random": "Aléatoire",
|
||||
"male": "Homme",
|
||||
@@ -337,7 +337,7 @@
|
||||
}
|
||||
},
|
||||
"passwordGeneratorSettings": {
|
||||
"description": "Configure the default settings used when generating new passwords. These settings will be used for all new passwords unless overridden for specific entries.",
|
||||
"description": "Configurez les paramètres par défaut utilisés lors de la génération de nouveaux mots de passe. Ces paramètres seront appliqués à tous les nouveaux mots de passe, sauf si vous les modifiez pour des entrées spécifiques.",
|
||||
"preview": "Aperçu"
|
||||
},
|
||||
"securitySettings": {
|
||||
@@ -383,23 +383,23 @@
|
||||
"time": "Heure",
|
||||
"ipAddress": "Adresse IP",
|
||||
"client": "Client",
|
||||
"failedToLoad": "Failed to load auth logs"
|
||||
"failedToLoad": "Impossible de charger les journaux d'authentification"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"headerText": "Deleting your account will immediately and permanently delete all of your data.",
|
||||
"warningText": "Warning: This action cannot be undone. All your data will be permanently deleted.",
|
||||
"finalWarning": "Final warning: Enter your password to permanently delete your account.",
|
||||
"warningVaults": "All encrypted vaults which includes all of your credentials will be permanently deleted",
|
||||
"warningAliases": "Your email aliases will be orphaned and cannot be claimed by other users",
|
||||
"warningRecovery": "Your account cannot be recovered after deletion",
|
||||
"irreversibleWarning": "Account deletion is irreversible and cannot be undone. Pressing the button below will delete your account immediately and permanently.",
|
||||
"enterUsername": "Enter your username to continue",
|
||||
"password": "Password",
|
||||
"enterPassword": "Enter password",
|
||||
"deleteAccount": "Delete Account",
|
||||
"confirmationMessage": "Are you absolutely sure you want to delete your account? This action cannot be undone.",
|
||||
"usernameDoesNotMatch": "Username does not match",
|
||||
"verifyingPassword": "Verifying password...",
|
||||
"headerText": "La suppression de votre compte supprimera immédiatement et définitivement toutes vos données.",
|
||||
"warningText": "Attention : cette action est irréversible. Toutes vos données seront définitivement supprimées.",
|
||||
"finalWarning": "Dernier avertissement : Entrez votre mot de passe pour supprimer définitivement votre compte.",
|
||||
"warningVaults": "Tous les coffres cryptés qui incluent tous vos identifiants seront définitivement supprimés",
|
||||
"warningAliases": "Vos alias d'email seront orphelins et ne pourront pas être réclamés par d'autres utilisateurs",
|
||||
"warningRecovery": "Votre compte ne peut pas être récupéré après suppression",
|
||||
"irreversibleWarning": "La suppression du compte est irréversible et ne peut pas être annulée. Appuyer sur le bouton ci-dessous supprimera votre compte immédiatement et définitivement.",
|
||||
"enterUsername": "Entrez votre nom d'utilisateur pour continuer",
|
||||
"password": "Mot de passe",
|
||||
"enterPassword": "Entrez le mot de passe",
|
||||
"deleteAccount": "Supprimer le compte",
|
||||
"confirmationMessage": "Voulez-vous vraiment supprimer votre compte ? Cette action est irréversible.",
|
||||
"usernameDoesNotMatch": "Le nom d'utilisateur ne correspond pas",
|
||||
"verifyingPassword": "Vérification du mot de passe...",
|
||||
"currentPasswordIncorrect": "Le mot de passe actuel est incorrect",
|
||||
"initiatingDeletion": "Initialisation de la suppression du compte",
|
||||
"verifyingWithServer": "Vérification par le serveur",
|
||||
@@ -410,20 +410,20 @@
|
||||
}
|
||||
},
|
||||
"qrScanner": {
|
||||
"title": "QR Code Scanner",
|
||||
"scanningMessage": "Scan AliasVault QR code",
|
||||
"invalidQrCode": "Invalid QR Code",
|
||||
"notAliasVaultQr": "This is not a valid AliasVault QR code. Please scan a QR code generated by AliasVault.",
|
||||
"cameraPermissionTitle": "Camera Permission Required",
|
||||
"cameraPermissionMessage": "Please allow camera access to scan QR codes.",
|
||||
"title": "Scanner de QR Code",
|
||||
"scanningMessage": "Scanner le QR code AliasVault",
|
||||
"invalidQrCode": "QR Code invalide",
|
||||
"notAliasVaultQr": "Ce n'est pas un code QR d'AliasVault valide. Veuillez scanner un code QR généré par AliasVault.",
|
||||
"cameraPermissionTitle": "Autorisation d'accès à l'appareil photo requise",
|
||||
"cameraPermissionMessage": "Veuillez autoriser l'accès à l'appareil photo pour scanner les QR codes.",
|
||||
"mobileLogin": {
|
||||
"confirmTitle": "Confirm Login Request",
|
||||
"confirmSubtitle": "Re-authenticate to approve login on another device.",
|
||||
"confirmMessage": "You are about to log in on a remote device with your account. This other device will have full access to your vault. Only proceed if you trust this device.",
|
||||
"successDescription": "The remote device has been successfully logged in.",
|
||||
"requestExpired": "This login request has expired. Please generate a new QR code.",
|
||||
"authenticationFailed": "Authentication failed. Please try again.",
|
||||
"noAuthMethodEnabled": "Biometric or PIN unlock needs to be enabled to unlock with mobile"
|
||||
"confirmTitle": "Confirmer la demande de connexion",
|
||||
"confirmSubtitle": "Se ré-authentifier pour approuver la connexion sur un autre appareil.",
|
||||
"confirmMessage": "Vous êtes sur le point de vous connecter sur un appareil distant avec votre compte. Cet autre appareil aura un accès complet à votre coffre. Ne procédez que si vous faites confiance à cet appareil.",
|
||||
"successDescription": "L'appareil distant a été connecté avec succès.",
|
||||
"requestExpired": "Cette demande de connexion a expiré. Veuillez générer un nouveau code QR.",
|
||||
"authenticationFailed": "Échec de l'authentification. Veuillez ré-essayer.",
|
||||
"noAuthMethodEnabled": "Le déverrouillage par biométrie ou par code PIN doit être activé pour déverrouiller avec le mobile"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -448,19 +448,19 @@
|
||||
"offlineMessage": "Vous êtes déconnecté. Veuillez vous connecter à internet pour charger vos e-mails.",
|
||||
"emptyMessage": "Vous n'avez pas encore reçu d'e-mails sur vos adresses e-mail privées. Quand vous recevez un nouvel e-mail, il apparaîtra ici.",
|
||||
"time": {
|
||||
"justNow": "just now",
|
||||
"minutesAgo_single": "{{count}} min ago",
|
||||
"minutesAgo_plural": "{{count}} mins ago",
|
||||
"hoursAgo_single": "{{count}} hr ago",
|
||||
"hoursAgo_plural": "{{count}} hrs ago",
|
||||
"yesterday": "yesterday"
|
||||
"justNow": "à l'instant",
|
||||
"minutesAgo_single": "Il y a {{count}} minute",
|
||||
"minutesAgo_plural": "Il y a {{count}} minutes",
|
||||
"hoursAgo_single": "Il y a {{count}} heure",
|
||||
"hoursAgo_plural": "Il y a {{count}} heures",
|
||||
"yesterday": "hier"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"required": "This field is required",
|
||||
"serviceNameRequired": "Service name is required",
|
||||
"invalidDateFormat": "Date must be in YYYY-MM-DD format",
|
||||
"invalidEmailFormat": "Invalid email format"
|
||||
"required": "Ce champ est requis",
|
||||
"serviceNameRequired": "Le nom du service est requis",
|
||||
"invalidDateFormat": "La date doit être au format AAAA-MM-JJ",
|
||||
"invalidEmailFormat": "Format d'email invalide"
|
||||
},
|
||||
"apiErrors": {
|
||||
"CLAIM_DOES_NOT_MATCH_USER": "L'adresse e-mail choisie est déjà utilisée. Veuillez modifier l'adresse e-mail en modifiant ces identifiants.",
|
||||
@@ -491,72 +491,72 @@
|
||||
},
|
||||
"app": {
|
||||
"status": {
|
||||
"unlockingVault": "Unlocking vault",
|
||||
"decryptingVault": "Decrypting vault",
|
||||
"openingVaultReadOnly": "Opening vault in read-only mode",
|
||||
"retryingConnection": "Retrying connection..."
|
||||
"unlockingVault": "Déverrouillage du coffre",
|
||||
"decryptingVault": "Décryptage du coffre",
|
||||
"openingVaultReadOnly": "Ouvrir le coffre en mode lecture seule",
|
||||
"retryingConnection": "Tentative de reconnexion..."
|
||||
},
|
||||
"offline": {
|
||||
"banner": "Offline mode (read-only)",
|
||||
"banner": "Mode hors ligne (lecture seule)",
|
||||
"backOnline": "Retour en ligne",
|
||||
"stillOffline": "Still offline"
|
||||
"stillOffline": "Toujours hors-ligne"
|
||||
},
|
||||
"alerts": {
|
||||
"syncIssue": "No Connection",
|
||||
"syncIssueMessage": "The AliasVault server could not be reached and your vault could not be synced. Would you like to open your local vault in read-only mode or retry the connection?",
|
||||
"openLocalVault": "Open Local Vault",
|
||||
"retrySync": "Retry Sync"
|
||||
"syncIssue": "Pas de connexion",
|
||||
"syncIssueMessage": "Le serveur AliasVault n'a pas pu être atteint et votre coffre n'a pas pu être synchronisé. Voulez-vous ouvrir votre coffre local en mode lecture seule ou réessayer la connexion ?",
|
||||
"openLocalVault": "Ouvrir le coffre local",
|
||||
"retrySync": "Réessayer la synchronisation"
|
||||
},
|
||||
"navigation": {
|
||||
"login": "Login",
|
||||
"loginSettings": "Login Settings"
|
||||
"login": "Se connecter",
|
||||
"loginSettings": "Paramètres de connexion"
|
||||
},
|
||||
"notFound": {
|
||||
"title": "Page not found",
|
||||
"message": "This page has been moved or deleted.",
|
||||
"goHome": "Go back to the start"
|
||||
"title": "Page non trouvée",
|
||||
"message": "Cette page a été déplacée ou supprimée.",
|
||||
"goHome": "Revenir au début"
|
||||
},
|
||||
"appName": "AliasVault",
|
||||
"reinitialize": {
|
||||
"vaultAutoLockedMessage": "Vault auto-locked after timeout.",
|
||||
"attemptingToUnlockMessage": "Attempting to unlock."
|
||||
"vaultAutoLockedMessage": "Coffre-fort verrouillé automatiquement après expiration du délai.",
|
||||
"attemptingToUnlockMessage": "Tentative de déverrouillage."
|
||||
},
|
||||
"loginSettings": {
|
||||
"title": "API Connection",
|
||||
"title": "Connexion API",
|
||||
"aliasvaultNet": "Aliasvault.net",
|
||||
"selfHosted": "Self-hosted",
|
||||
"customApiUrl": "Custom API URL",
|
||||
"selfHosted": "Auto-hébergé",
|
||||
"customApiUrl": "URL de l’API personnalisée",
|
||||
"customApiUrlPlaceholder": "https://my-aliasvault-instance.com/api",
|
||||
"version": "Version: {{version}}"
|
||||
"version": "Version : {{version}}"
|
||||
}
|
||||
},
|
||||
"upgrade": {
|
||||
"title": "Upgrade Vault",
|
||||
"subtitle": "AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.",
|
||||
"versionInformation": "Version Information",
|
||||
"yourVault": "Your vault version:",
|
||||
"newVersion": "New available version:",
|
||||
"upgrade": "Upgrade",
|
||||
"upgrading": "Upgrading...",
|
||||
"logout": "Logout",
|
||||
"whatsNew": "What's New",
|
||||
"whatsNewDescription": "An upgrade is required to support the following changes:",
|
||||
"noDescriptionAvailable": "No description available for this version.",
|
||||
"title": "Mettre à niveau le coffre",
|
||||
"subtitle": "AliasVault a été mis à jour et votre coffre doit être mis à niveau. Cela ne devrait prendre que quelques secondes.",
|
||||
"versionInformation": "Informations de version",
|
||||
"yourVault": "Version de votre coffre:",
|
||||
"newVersion": "Nouvelle version disponible :",
|
||||
"upgrade": "Mettre à jour",
|
||||
"upgrading": "Mise à jour...",
|
||||
"logout": "Déconnexion",
|
||||
"whatsNew": "Nouveautés",
|
||||
"whatsNewDescription": "Une mise à jour est nécessaire pour prendre en charge les modifications suivantes:",
|
||||
"noDescriptionAvailable": "Aucune description disponible pour cette version.",
|
||||
"status": {
|
||||
"preparingUpgrade": "Preparing upgrade...",
|
||||
"vaultAlreadyUpToDate": "Vault is already up to date",
|
||||
"startingDatabaseTransaction": "Starting database transaction...",
|
||||
"applyingDatabaseMigrations": "Applying database migrations...",
|
||||
"applyingMigration": "Applying migration {{current}} of {{total}}...",
|
||||
"committingChanges": "Committing changes..."
|
||||
"preparingUpgrade": "Préparation de la mise à jour...",
|
||||
"vaultAlreadyUpToDate": "Le coffre est déjà à jour",
|
||||
"startingDatabaseTransaction": "Démarrage de la transaction de base de données...",
|
||||
"applyingDatabaseMigrations": "Application des migrations de base de données...",
|
||||
"applyingMigration": "Application de la migration {{current}} sur {{total}}...",
|
||||
"committingChanges": "Application des modifications..."
|
||||
},
|
||||
"alerts": {
|
||||
"unableToGetVersionInfo": "Unable to get version information. Please try again.",
|
||||
"selfHostedServer": "Self-Hosted Server",
|
||||
"selfHostedWarning": "If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working.",
|
||||
"continueUpgrade": "Continue Upgrade",
|
||||
"upgradeFailed": "Upgrade Failed",
|
||||
"failedToApplyMigration": "Failed to apply migration ({{current}} of {{total}})"
|
||||
"unableToGetVersionInfo": "Impossible d'obtenir les informations de version. Veuillez réessayer.",
|
||||
"selfHostedServer": "Serveur auto-hébergé",
|
||||
"selfHostedWarning": "Si vous utilisez un serveur auto-hébergé, assurez-vous également de mettre à jour votre instance auto-hébergée, sinon la connexion au client web cessera de fonctionner.",
|
||||
"continueUpgrade": "Continuer la mise à jour",
|
||||
"upgradeFailed": "Échec de la mise à jour",
|
||||
"failedToApplyMigration": "Impossible d'appliquer la migration ({{current}} sur {{total}})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@
|
||||
"downloadToView": "יש להוריד את הקובץ כדי לצפות בו",
|
||||
"unsavedChanges": {
|
||||
"title": "להתעלם מהשינויים?",
|
||||
"message": "You have unsaved changes. Are you sure you want to discard them?",
|
||||
"discard": "Discard"
|
||||
"message": "יש שינויים שלא נשמרו. לסלק אותם?",
|
||||
"discard": "סילוק"
|
||||
},
|
||||
"toasts": {
|
||||
"credentialUpdated": "פרטי הגישה עודכנו בהצלחה",
|
||||
@@ -208,8 +208,8 @@
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"nameOptional": "שם (רשות)",
|
||||
"secretKey": "מפתח סודי",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"errors": {
|
||||
@@ -328,8 +328,8 @@
|
||||
"languageDescription": "נא להגדיר את השפה שתשמש ליצירת זהויות חדשות.",
|
||||
"genderSection": "מגדר",
|
||||
"genderDescription": "להגדיר את אפשרויות המגדר ליצירת זהויות חדשות.",
|
||||
"ageRangeSection": "Age Range",
|
||||
"ageRangeDescription": "Set the age range for generating new identities.",
|
||||
"ageRangeSection": "טווח גילים",
|
||||
"ageRangeDescription": "הגדרת טווח גילים ליצירת זהויות חדשות.",
|
||||
"genderOptions": {
|
||||
"random": "אקראי",
|
||||
"male": "זכר",
|
||||
@@ -412,10 +412,10 @@
|
||||
"qrScanner": {
|
||||
"title": "QR Code Scanner",
|
||||
"scanningMessage": "Scan AliasVault QR code",
|
||||
"invalidQrCode": "Invalid QR Code",
|
||||
"invalidQrCode": "קוד QR שגוי",
|
||||
"notAliasVaultQr": "This is not a valid AliasVault QR code. Please scan a QR code generated by AliasVault.",
|
||||
"cameraPermissionTitle": "Camera Permission Required",
|
||||
"cameraPermissionMessage": "Please allow camera access to scan QR codes.",
|
||||
"cameraPermissionTitle": "נדרשת הרשאת מצלמה",
|
||||
"cameraPermissionMessage": "נא לאפשר גישה למצלמה כדי לסרוק קודים מסוג QR.",
|
||||
"mobileLogin": {
|
||||
"confirmTitle": "Confirm Login Request",
|
||||
"confirmSubtitle": "Re-authenticate to approve login on another device.",
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"notice": "Uwaga",
|
||||
"enabled": "Włączone",
|
||||
"disabled": "Wyłączone",
|
||||
"twoFactorAuthentication": "Two-Factor Authentication",
|
||||
"deleteItemConfirmTitle": "Delete Item",
|
||||
"deleteItemConfirmDescription": "Are you sure you want to delete this item?",
|
||||
"twoFactorAuthentication": "Uwierzytelnianie dwuskładnikowe",
|
||||
"deleteItemConfirmTitle": "Usuń element",
|
||||
"deleteItemConfirmDescription": "Czy na pewno chcesz usunąć ten element?",
|
||||
"errors": {
|
||||
"unknownError": "Wystąpił nieznany błąd. Spróbuj ponownie.",
|
||||
"unknownErrorTryAgain": "Wystąpił nieznany błąd. Spróbuj ponownie.",
|
||||
@@ -207,13 +207,13 @@
|
||||
"passkeyWillBeDeleted": "Ten klucz dostępu zostanie usunięty po zapisaniu tych danych."
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Dodaj kod 2FA",
|
||||
"nameOptional": "Nazwa (opcjonalnie)",
|
||||
"secretKey": "Tajny klucz",
|
||||
"instructions": "Wprowadź tajny klucz wyświetlony na stronie internetowej, na której chcesz dodać uwierzytelnianie dwuskładnikowe.",
|
||||
"saveToViewCode": "Zapisz, aby wyświetlić kod",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Nieprawidłowy format tajnego klucza."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"notice": "Примечание",
|
||||
"enabled": "Включено",
|
||||
"disabled": "Отключено",
|
||||
"twoFactorAuthentication": "Two-Factor Authentication",
|
||||
"deleteItemConfirmTitle": "Delete Item",
|
||||
"deleteItemConfirmDescription": "Are you sure you want to delete this item?",
|
||||
"twoFactorAuthentication": "Двухфакторная аутентификация",
|
||||
"deleteItemConfirmTitle": "Удалить элемент",
|
||||
"deleteItemConfirmDescription": "Вы уверены, что хотите удалить этот элемент?",
|
||||
"errors": {
|
||||
"unknownError": "Произошла неизвестная ошибка. Пожалуйста, попробуйте снова.",
|
||||
"unknownErrorTryAgain": "Произошла неизвестная ошибка. Попробуйте снова.",
|
||||
@@ -207,13 +207,13 @@
|
||||
"passkeyWillBeDeleted": "Этот ключ доступа будет удален при сохранении этой учетной записи."
|
||||
},
|
||||
"totp": {
|
||||
"addCode": "Add 2FA Code",
|
||||
"nameOptional": "Name (optional)",
|
||||
"secretKey": "Secret Key",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"addCode": "Добавить код 2FA",
|
||||
"nameOptional": "Имя (необязательно)",
|
||||
"secretKey": "Секретный ключ",
|
||||
"instructions": "Введите секретный ключ, указанный на веб-сайте, где вы хотите добавить двухфакторную аутентификацию.",
|
||||
"saveToViewCode": "Сохранить для просмотра кода",
|
||||
"errors": {
|
||||
"invalidSecretKey": "Invalid secret key format."
|
||||
"invalidSecretKey": "Неверный формат секретного ключа."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"vault": {
|
||||
"syncingVault": "正在同步密码库",
|
||||
"uploadingVaultToServer": "正在向服务器上传密码库",
|
||||
"savingChangesToVault": "正在保存至密码库",
|
||||
"savingChangesToVault": "正在保存对密码库的更改",
|
||||
"checkingForVaultUpdates": "检查密码库更新中",
|
||||
"executingOperation": "执行操作中…",
|
||||
"checkingVaultUpdates": "检查密码库更新",
|
||||
@@ -210,7 +210,7 @@
|
||||
"addCode": "添加两步验证码",
|
||||
"nameOptional": "名称(可选)",
|
||||
"secretKey": "密钥",
|
||||
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
|
||||
"instructions": "输入要添加两步验证的网站显示的密钥。",
|
||||
"saveToViewCode": "保存以查看验证码",
|
||||
"errors": {
|
||||
"invalidSecretKey": "密钥格式无效。"
|
||||
@@ -418,12 +418,12 @@
|
||||
"cameraPermissionMessage": "请允许访问相机以扫描二维码。",
|
||||
"mobileLogin": {
|
||||
"confirmTitle": "确认登录请求",
|
||||
"confirmSubtitle": "Re-authenticate to approve login on another device.",
|
||||
"confirmMessage": "You are about to log in on a remote device with your account. This other device will have full access to your vault. Only proceed if you trust this device.",
|
||||
"successDescription": "The remote device has been successfully logged in.",
|
||||
"requestExpired": "This login request has expired. Please generate a new QR code.",
|
||||
"confirmSubtitle": "重新认证以批准在另一台设备上登录。",
|
||||
"confirmMessage": "您即将使用您的账户在远程设备上登录。该设备将拥有对您密码库的完全访问权限。请仅在您信任该设备的情况下继续。",
|
||||
"successDescription": "远程设备已成功登录。",
|
||||
"requestExpired": "此登录请求已过期,请生成新的二维码。",
|
||||
"authenticationFailed": "认证失败,请重试。",
|
||||
"noAuthMethodEnabled": "Biometric or PIN unlock needs to be enabled to unlock with mobile"
|
||||
"noAuthMethodEnabled": "需要启用生物识别或 PIN 码解锁才能使用移动设备解锁"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -787,7 +787,9 @@
|
||||
Base,
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
he,
|
||||
nl,
|
||||
it,
|
||||
@@ -1297,7 +1299,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@@ -1312,7 +1314,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1338,7 +1340,7 @@
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
INFOPLIST_FILE = AliasVault/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AliasVault;
|
||||
@@ -1348,7 +1350,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1499,7 +1501,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1535,7 +1537,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1569,7 +1571,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1626,7 +1628,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1679,7 +1681,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1732,7 +1734,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1781,7 +1783,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1816,7 +1818,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -1849,7 +1851,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1902,7 +1904,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1951,7 +1953,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2003,7 +2005,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2054,7 +2056,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = autofill/autofill.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -2070,7 +2072,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
@@ -2099,7 +2101,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = autofill/autofill.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2500901;
|
||||
CURRENT_PROJECT_VERSION = 2600100;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 8PHW4HN3F7;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -2115,7 +2117,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.aliasvault.app.autofill;
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
<array>
|
||||
<string>de</string>
|
||||
<string>en</string>
|
||||
<string>es</string>
|
||||
<string>fi</string>
|
||||
<string>fr</string>
|
||||
<string>he</string>
|
||||
<string>it</string>
|
||||
<string>nl</string>
|
||||
|
||||
Binary file not shown.
@@ -6,7 +6,9 @@
|
||||
<array>
|
||||
<string>de</string>
|
||||
<string>en</string>
|
||||
<string>es</string>
|
||||
<string>fi</string>
|
||||
<string>fr</string>
|
||||
<string>he</string>
|
||||
<string>it</string>
|
||||
<string>nl</string>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -291,4 +291,10 @@
|
||||
[vaultManager authenticateUser:title subtitle:subtitle resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
// MARK: - QR Code Scanner
|
||||
|
||||
- (void)scanQRCode:(NSArray<NSString *> *)prefixes statusText:(NSString *)statusText resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
||||
[vaultManager scanQRCode:prefixes statusText:statusText resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -5,6 +5,7 @@ import VaultStoreKit
|
||||
import VaultModels
|
||||
import SwiftUI
|
||||
import VaultUI
|
||||
import AVFoundation
|
||||
|
||||
/**
|
||||
* This class is used as a bridge to allow React Native to interact with the VaultStoreKit class.
|
||||
@@ -913,6 +914,42 @@ public class VaultManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func scanQRCode(_ prefixes: [String]?,
|
||||
statusText: String?,
|
||||
resolver resolve: @escaping RCTPromiseResolveBlock,
|
||||
rejecter reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
// Get the root view controller from React Native
|
||||
guard let rootVC = RCTPresentedViewController() else {
|
||||
reject("NO_VIEW_CONTROLLER", "No view controller available", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Create QR scanner view with optional prefix filtering and custom status text
|
||||
let scannerView = QRScannerView(
|
||||
prefixes: prefixes,
|
||||
statusText: statusText,
|
||||
onCodeScanned: { code in
|
||||
// Resolve immediately and dismiss without waiting (matches Android behavior)
|
||||
resolve(code)
|
||||
rootVC.dismiss(animated: true)
|
||||
},
|
||||
onCancel: {
|
||||
// Cancel resolves nil and dismisses
|
||||
resolve(nil)
|
||||
rootVC.dismiss(animated: true)
|
||||
}
|
||||
)
|
||||
|
||||
let hostingController = UIHostingController(rootView: scannerView)
|
||||
|
||||
// Present modally as full screen
|
||||
hostingController.modalPresentationStyle = .fullScreen
|
||||
rootVC.present(hostingController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func authenticateUser(_ title: String?,
|
||||
subtitle: String?,
|
||||
|
||||
@@ -272,10 +272,6 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoBlur (14.1.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoCamera (16.1.11):
|
||||
- ExpoModulesCore
|
||||
- ZXingObjC/OneD
|
||||
- ZXingObjC/PDF417
|
||||
- ExpoClipboard (7.1.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoDocumentPicker (13.1.6):
|
||||
@@ -337,9 +333,9 @@ PODS:
|
||||
- FBLazyVector (0.79.6)
|
||||
- fmt (11.0.2)
|
||||
- glog (0.3.5)
|
||||
- hermes-engine (0.79.5):
|
||||
- hermes-engine/Pre-built (= 0.79.5)
|
||||
- hermes-engine/Pre-built (0.79.5)
|
||||
- hermes-engine (0.79.6):
|
||||
- hermes-engine/Pre-built (= 0.79.6)
|
||||
- hermes-engine/Pre-built (0.79.6)
|
||||
- Macaw (0.9.10):
|
||||
- SWXMLHash
|
||||
- OpenSSL-Universal (3.3.3001)
|
||||
@@ -2446,14 +2442,9 @@ PODS:
|
||||
- SQLite.swift (0.14.1):
|
||||
- SQLite.swift/standard (= 0.14.1)
|
||||
- SQLite.swift/standard (0.14.1)
|
||||
- SwiftLint (0.59.1)
|
||||
- SwiftLint (0.62.2)
|
||||
- SWXMLHash (7.0.2)
|
||||
- Yoga (0.0.0)
|
||||
- ZXingObjC/Core (3.6.9)
|
||||
- ZXingObjC/OneD (3.6.9):
|
||||
- ZXingObjC/Core
|
||||
- ZXingObjC/PDF417 (3.6.9):
|
||||
- ZXingObjC/Core
|
||||
|
||||
DEPENDENCIES:
|
||||
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||
@@ -2468,7 +2459,6 @@ DEPENDENCIES:
|
||||
- expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
|
||||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||
- ExpoBlur (from `../node_modules/expo-blur/ios`)
|
||||
- ExpoCamera (from `../node_modules/expo-camera/ios`)
|
||||
- ExpoClipboard (from `../node_modules/expo-clipboard/ios`)
|
||||
- ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`)
|
||||
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||
@@ -2582,7 +2572,6 @@ SPEC REPOS:
|
||||
- SQLite.swift
|
||||
- SwiftLint
|
||||
- SWXMLHash
|
||||
- ZXingObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
@@ -2609,8 +2598,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-asset/ios"
|
||||
ExpoBlur:
|
||||
:path: "../node_modules/expo-blur/ios"
|
||||
ExpoCamera:
|
||||
:path: "../node_modules/expo-camera/ios"
|
||||
ExpoClipboard:
|
||||
:path: "../node_modules/expo-clipboard/ios"
|
||||
ExpoDocumentPicker:
|
||||
@@ -2808,8 +2795,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
|
||||
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
|
||||
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
|
||||
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
|
||||
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
|
||||
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
|
||||
EXManifests: 691a779b04e4f2c96da46fb9bef4f86174fefcb5
|
||||
@@ -2820,7 +2807,6 @@ SPEC CHECKSUMS:
|
||||
expo-dev-menu-interface: 609c35ae8b97479cdd4c9e23c8cf6adc44beea0e
|
||||
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
|
||||
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
|
||||
ExpoCamera: e1879906d41184e84b57d7643119f8509414e318
|
||||
ExpoClipboard: 436f6de6971f14eb75ae160e076d9cb3b19eb795
|
||||
ExpoDocumentPicker: b263a279685b6640b8c8bc70d71c83067aeaae55
|
||||
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
|
||||
@@ -2840,9 +2826,9 @@ SPEC CHECKSUMS:
|
||||
EXUpdatesInterface: 7ff005b7af94ee63fa452ea7bb95d7a8ff40277a
|
||||
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
|
||||
FBLazyVector: 07309209b7b914451b8f822544a18e2a0a85afff
|
||||
fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6
|
||||
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
|
||||
hermes-engine: f03b0e06d3882d71e67e45b073bb827da1a21aae
|
||||
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
|
||||
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
|
||||
hermes-engine: 44bb6fe76a6eb400d3a992e2d0b21946ae999fa9
|
||||
Macaw: 7af8ea57aa2cab35b4a52a45e6f85eea753ea9ae
|
||||
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
|
||||
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
|
||||
@@ -2922,10 +2908,9 @@ SPEC CHECKSUMS:
|
||||
SignalArgon2: 1c24183835ca861e6af06631c18b1671cdf35571
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
SQLite.swift: 2992550ebf3c5b268bf4352603e3df87d2a4ed72
|
||||
SwiftLint: 3d48e2fb2a3468fdaccf049e5e755df22fb40c2c
|
||||
SwiftLint: f84fc7d844e9cde0dc4f5013af608a269e317aba
|
||||
SWXMLHash: dd733a457e9c4fe93b1538654057aefae4acb382
|
||||
Yoga: dc7c21200195acacb62fa920c588e7c2106de45e
|
||||
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
|
||||
|
||||
PODFILE CHECKSUM: ac288e273086bafdd610cafff08ccca0d164f7c3
|
||||
|
||||
|
||||
240
apps/mobile-app/ios/VaultUI/QRScanner/QRScannerView.swift
Normal file
240
apps/mobile-app/ios/VaultUI/QRScanner/QRScannerView.swift
Normal file
@@ -0,0 +1,240 @@
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
private let locBundle = Bundle.vaultUI
|
||||
|
||||
/// SwiftUI view for scanning QR codes using AVFoundation
|
||||
public struct QRScannerView: View {
|
||||
let onCodeScanned: (String) -> Void
|
||||
let onCancel: () -> Void
|
||||
let prefixes: [String]?
|
||||
let statusText: String
|
||||
|
||||
@State private var hasScanned = false
|
||||
@State private var showFlash = false
|
||||
|
||||
public init(
|
||||
prefixes: [String]? = nil,
|
||||
statusText: String? = nil,
|
||||
onCodeScanned: @escaping (String) -> Void,
|
||||
onCancel: @escaping () -> Void
|
||||
) {
|
||||
self.prefixes = prefixes
|
||||
self.statusText = statusText?.isEmpty == false ? statusText! : "Scan QR code"
|
||||
self.onCodeScanned = onCodeScanned
|
||||
self.onCancel = onCancel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
// Camera preview
|
||||
QRScannerRepresentable(
|
||||
prefixes: prefixes,
|
||||
onCodeScanned: { code in
|
||||
if !hasScanned {
|
||||
hasScanned = true
|
||||
showFlash = true
|
||||
|
||||
// Flash animation then callback
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
|
||||
onCodeScanned(code)
|
||||
}
|
||||
}
|
||||
},
|
||||
onCodeRejected: {
|
||||
// Reset hasScanned to allow scanning again
|
||||
hasScanned = false
|
||||
}
|
||||
)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
// Overlay with viewfinder
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
// Viewfinder frame
|
||||
Rectangle()
|
||||
.stroke(Color.white, lineWidth: 3)
|
||||
.frame(width: 280, height: 280)
|
||||
.overlay(
|
||||
// Flash effect
|
||||
Rectangle()
|
||||
.fill(Color.white)
|
||||
.opacity(showFlash ? 0.7 : 0)
|
||||
.animation(.easeInOut(duration: 0.2), value: showFlash)
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Status text
|
||||
Text(statusText)
|
||||
.foregroundColor(.white)
|
||||
.padding()
|
||||
.background(Color.black.opacity(0.7))
|
||||
.cornerRadius(10)
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
|
||||
// Cancel button
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: onCancel) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundColor(.white)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.background(Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
/// UIViewControllerRepresentable wrapper for AVFoundation camera
|
||||
struct QRScannerRepresentable: UIViewControllerRepresentable {
|
||||
let prefixes: [String]?
|
||||
let onCodeScanned: (String) -> Void
|
||||
let onCodeRejected: () -> Void
|
||||
|
||||
func makeUIViewController(context: Context) -> QRScannerViewController {
|
||||
let controller = QRScannerViewController()
|
||||
controller.prefixes = prefixes
|
||||
controller.onCodeScanned = onCodeScanned
|
||||
controller.onCodeRejected = onCodeRejected
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: QRScannerViewController, context: Context) {
|
||||
// No updates needed
|
||||
}
|
||||
}
|
||||
|
||||
/// UIViewController that handles AVFoundation QR code scanning
|
||||
class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||
var captureSession: AVCaptureSession?
|
||||
var previewLayer: AVCaptureVideoPreviewLayer?
|
||||
var prefixes: [String]?
|
||||
var onCodeScanned: ((String) -> Void)?
|
||||
var onCodeRejected: (() -> Void)?
|
||||
private var rejectedQRCodes = Set<String>() // Track rejected QR codes to avoid repeated haptic feedback
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupCamera()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if let session = captureSession, !session.isRunning {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
session.startRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
if let session = captureSession, session.isRunning {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupCamera() {
|
||||
let session = AVCaptureSession()
|
||||
|
||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
|
||||
return
|
||||
}
|
||||
|
||||
let videoInput: AVCaptureDeviceInput
|
||||
|
||||
do {
|
||||
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if session.canAddInput(videoInput) {
|
||||
session.addInput(videoInput)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
|
||||
if session.canAddOutput(metadataOutput) {
|
||||
session.addOutput(metadataOutput)
|
||||
|
||||
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||
metadataOutput.metadataObjectTypes = [.qr]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
|
||||
previewLayer.frame = view.layer.bounds
|
||||
previewLayer.videoGravity = .resizeAspectFill
|
||||
view.layer.addSublayer(previewLayer)
|
||||
|
||||
self.captureSession = session
|
||||
self.previewLayer = previewLayer
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
session.startRunning()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
previewLayer?.frame = view.layer.bounds
|
||||
}
|
||||
|
||||
func metadataOutput(
|
||||
_ output: AVCaptureMetadataOutput,
|
||||
didOutput metadataObjects: [AVMetadataObject],
|
||||
from connection: AVCaptureConnection
|
||||
) {
|
||||
if let metadataObject = metadataObjects.first,
|
||||
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
|
||||
let stringValue = readableObject.stringValue {
|
||||
|
||||
// Check if prefixes filter is enabled
|
||||
if let prefixes = prefixes, !prefixes.isEmpty {
|
||||
// Check if the scanned code starts with any of the accepted prefixes
|
||||
let hasValidPrefix = prefixes.contains { prefix in
|
||||
stringValue.hasPrefix(prefix)
|
||||
}
|
||||
|
||||
if !hasValidPrefix {
|
||||
// Invalid QR code - only give haptic feedback once per unique code
|
||||
if !rejectedQRCodes.contains(stringValue) {
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.warning)
|
||||
rejectedQRCodes.insert(stringValue)
|
||||
}
|
||||
|
||||
// Notify that code was rejected (to reset UI state if needed)
|
||||
onCodeRejected?()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Valid QR code - stop scanning
|
||||
captureSession?.stopRunning()
|
||||
|
||||
// Success haptic feedback
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.success)
|
||||
|
||||
// Callback with scanned code
|
||||
onCodeScanned?(stringValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,73 @@
|
||||
/* English localization strings for VaultUI */
|
||||
"error" = "Error";
|
||||
"cancel" = "Cancel";
|
||||
"back" = "Back";
|
||||
"next" = "Next";
|
||||
"edit" = "Edit";
|
||||
"website" = "Website";
|
||||
"username" = "Username";
|
||||
"title" = "Title";
|
||||
"unknown_error" = "An unknown error occurred";
|
||||
"cancel" = "Cancelar";
|
||||
"back" = "Atrás";
|
||||
"next" = "Siguiente";
|
||||
"edit" = "Editar";
|
||||
"website" = "Sitio Web";
|
||||
"username" = "Usuario";
|
||||
"title" = "Título";
|
||||
"unknown_error" = "Se produjo un error desconocido";
|
||||
|
||||
"loading_credentials" = "Loading credentials...";
|
||||
"no_credentials_found" = "No credentials found";
|
||||
"no_credentials_match" = "No existing credentials match your search";
|
||||
"create_new_credential" = "Create New Credential";
|
||||
"select_credential" = "Select Credential";
|
||||
"select_text_to_insert" = "Select Text to Insert";
|
||||
"choose_username" = "Choose Username";
|
||||
"select_text_to_insert_message" = "Select the text to insert into the focused input field";
|
||||
"choose_username_message" = "This website may require either your username or your email address to log in";
|
||||
"username_prefix" = "Username: ";
|
||||
"loading_credentials" = "Cargando credenciales...";
|
||||
"no_credentials_found" = "No se encontraron credenciales";
|
||||
"no_credentials_match" = "No hay credenciales existentes que coincidan con su búsqueda";
|
||||
"create_new_credential" = "Crear nueva credencial";
|
||||
"select_credential" = "Seleccionar credencial";
|
||||
"select_text_to_insert" = "Seleccionar texto a insertar";
|
||||
"choose_username" = "Escoge un nombre de usuario";
|
||||
"select_text_to_insert_message" = "Seleccione el texto a insertar en el campo de entrada resaltado";
|
||||
"choose_username_message" = "Este sitio web puede requerir su nombre de usuario o correo electrónico para iniciar sesión";
|
||||
"username_prefix" = "Usuario: ";
|
||||
"email_prefix" = "Email: ";
|
||||
"password" = "Password";
|
||||
"credentials_load_error" = "Failed to load credentials. Please open the AliasVault app to check for updates.";
|
||||
"no_credential_selected" = "No credential selected.";
|
||||
"retrieving_credential" = "Retrieving credential";
|
||||
"retrieving_passkey" = "Retrieving passkey";
|
||||
"password" = "Contraseña";
|
||||
"credentials_load_error" = "Error al cargar las credenciales. Por favor, abre la aplicación AliasVault para buscar actualizaciones.";
|
||||
"no_credential_selected" = "Ninguna credencial seleccionada.";
|
||||
"retrieving_credential" = "Recuperar credencial";
|
||||
"retrieving_passkey" = "Recuperando llave";
|
||||
|
||||
/* Context menu strings */
|
||||
"copy_username" = "Copy Username";
|
||||
"copy_password" = "Copy Password";
|
||||
"copy_email" = "Copy Email";
|
||||
"view_details" = "View Details";
|
||||
"username_copied" = "Username copied";
|
||||
"password_copied" = "Password copied";
|
||||
"email_copied" = "Email copied";
|
||||
"copy_username" = "Copiar nombre de usuario";
|
||||
"copy_password" = "Copiar Contraseña";
|
||||
"copy_email" = "Copiar email";
|
||||
"view_details" = "Ver detalles";
|
||||
"username_copied" = "Usuario copiado";
|
||||
"password_copied" = "Contraseña copiada";
|
||||
"email_copied" = "Email copiado";
|
||||
|
||||
/* Search bar */
|
||||
"search_credentials" = "Search credentials...";
|
||||
"search_credentials" = "Buscar credenciales...";
|
||||
|
||||
/* Passkey registration */
|
||||
"create_passkey_title" = "Create New Passkey";
|
||||
"create_passkey_subtitle" = "Register a new passkey for this website. It will be securely stored in your vault and automatically synced across your devices with AliasVault.";
|
||||
"create_passkey_button_confirm" = "Create Passkey";
|
||||
"create_passkey_title" = "Crear Nueva llave";
|
||||
"create_passkey_subtitle" = "Registre una nueva llave de acceso para este sitio web. Se almacenará de forma segura en su bóveda y se sincronizará automáticamente en todos sus dispositivos con AliasVault.";
|
||||
"create_passkey_button_confirm" = "Crear llave de acceso";
|
||||
|
||||
/* Passkey provider */
|
||||
"passkey" = "Passkey";
|
||||
"loading_passkeys" = "Loading passkeys...";
|
||||
"no_passkeys_found" = "No passkeys found";
|
||||
"no_passkeys_match" = "No existing passkeys match your search";
|
||||
"select_passkey" = "Select Passkey";
|
||||
"passkeys_load_error" = "Failed to load passkeys. Please open the AliasVault app to check for updates.";
|
||||
"passkey" = "Llave de acceso";
|
||||
"loading_passkeys" = "Cargando llaves de acceso...";
|
||||
"no_passkeys_found" = "No se encontraron claves de acceso";
|
||||
"no_passkeys_match" = "Ninguna llave de acceso existente coincide con la búsqueda";
|
||||
"select_passkey" = "Seleccionar la clave de acceso";
|
||||
"passkeys_load_error" = "Error al cargar las credenciales. Por favor, abre la aplicación AliasVault para buscar actualizaciones.";
|
||||
|
||||
/* Passkey replacement */
|
||||
"create_new_passkey" = "Create New Passkey";
|
||||
"select_passkey_to_replace" = "Or, replace an existing passkey";
|
||||
"confirm_replace" = "Replace Passkey";
|
||||
"replace_passkey_title" = "Replace Passkey";
|
||||
"replace_passkey_explanation" = "This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.";
|
||||
"create_passkey_explanation" = "This creates a new passkey and stores it in your vault. It will be automatically synced across all your devices that use AliasVault.";
|
||||
"create_new_passkey" = "Crear nueva llave de acceso";
|
||||
"select_passkey_to_replace" = "O, reemplazar una llave de acceso existente";
|
||||
"confirm_replace" = "Reemplazar llave de acceso";
|
||||
"replace_passkey_title" = "Reemplazar llave de acceso";
|
||||
"replace_passkey_explanation" = "Esto reemplazará la llave de acceso existente con una nueva. Tenga en cuenta que su llave de acceso antigua será sobrescrita y ya no será accesible. Si desea crear una llave de acceso separada en su lugar, vuelva a la pantalla anterior.";
|
||||
"create_passkey_explanation" = "Esto crea una nueva llave de acceso y la almacena en tu bóveda. Se sincronizará automáticamente en todos tus dispositivos que utilicen AliasVault.";
|
||||
|
||||
/* PIN Unlock */
|
||||
"unlock_vault" = "Unlock Vault";
|
||||
"enter_pin_to_unlock_vault" = "Enter your PIN to unlock your vault";
|
||||
"pin_locked_max_attempts" = "PIN locked after too many failed attempts";
|
||||
"pin_incorrect_attempts_remaining" = "Incorrect PIN. %d attempts remaining";
|
||||
"unlock_vault" = "Desbloquear bóveda";
|
||||
"enter_pin_to_unlock_vault" = "Introduzca su PIN para desbloquear su bóveda";
|
||||
"pin_locked_max_attempts" = "PIN bloqueado tras demasiados intentos fallidos";
|
||||
"pin_incorrect_attempts_remaining" = "PIN incorrecto, %d intentos restantes";
|
||||
|
||||
/* PIN Setup */
|
||||
"pin_setup_title" = "Setup PIN";
|
||||
"pin_setup_subtitle" = "Choose a PIN to unlock your vault";
|
||||
"pin_confirm_title" = "Confirm PIN";
|
||||
"pin_confirm_subtitle" = "Re-enter your PIN to confirm";
|
||||
"pin_mismatch" = "PINs do not match. Please try again.";
|
||||
"pin_setup_title" = "Configurar PIN";
|
||||
"pin_setup_subtitle" = "Elija un PIN para desbloquear su bóveda";
|
||||
"pin_confirm_title" = "Confirmar PIN";
|
||||
"pin_confirm_subtitle" = "Vuelva a introducir su PIN para confirmar";
|
||||
"pin_mismatch" = "Los PIN no coinciden. Por favor, inténtalo de nuevo.";
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
/* English localization strings for VaultUI */
|
||||
"error" = "Error";
|
||||
"cancel" = "Cancel";
|
||||
"back" = "Back";
|
||||
"next" = "Next";
|
||||
"edit" = "Edit";
|
||||
"website" = "Website";
|
||||
"username" = "Username";
|
||||
"title" = "Title";
|
||||
"unknown_error" = "An unknown error occurred";
|
||||
"error" = "Erreur";
|
||||
"cancel" = "Annuler";
|
||||
"back" = "Précédent";
|
||||
"next" = "Suivant";
|
||||
"edit" = "Modifier";
|
||||
"website" = "Site Web";
|
||||
"username" = "Nom d'utilisateur";
|
||||
"title" = "Titre";
|
||||
"unknown_error" = "Une erreur inconnue s'est produite";
|
||||
|
||||
"loading_credentials" = "Loading credentials...";
|
||||
"no_credentials_found" = "No credentials found";
|
||||
"no_credentials_match" = "No existing credentials match your search";
|
||||
"create_new_credential" = "Create New Credential";
|
||||
"select_credential" = "Select Credential";
|
||||
"select_text_to_insert" = "Select Text to Insert";
|
||||
"choose_username" = "Choose Username";
|
||||
"select_text_to_insert_message" = "Select the text to insert into the focused input field";
|
||||
"choose_username_message" = "This website may require either your username or your email address to log in";
|
||||
"username_prefix" = "Username: ";
|
||||
"email_prefix" = "Email: ";
|
||||
"password" = "Password";
|
||||
"credentials_load_error" = "Failed to load credentials. Please open the AliasVault app to check for updates.";
|
||||
"no_credential_selected" = "No credential selected.";
|
||||
"retrieving_credential" = "Retrieving credential";
|
||||
"retrieving_passkey" = "Retrieving passkey";
|
||||
"loading_credentials" = "Chargement des identifiants...";
|
||||
"no_credentials_found" = "Aucun identifiant trouvé";
|
||||
"no_credentials_match" = "Aucun identifiant existant ne correspond à votre recherche";
|
||||
"create_new_credential" = "Créer un nouvel identifiant";
|
||||
"select_credential" = "Sélectionner l’identifiant";
|
||||
"select_text_to_insert" = "Sélectionner le texte à insérer";
|
||||
"choose_username" = "Choisissez un nom d'utilisateur";
|
||||
"select_text_to_insert_message" = "Sélectionnez le texte à insérer dans le champ de saisie ciblé";
|
||||
"choose_username_message" = "Ce site Web peut nécessiter votre nom d'utilisateur ou votre adresse e-mail pour vous connecter";
|
||||
"username_prefix" = "Nom d'utilisateur: ";
|
||||
"email_prefix" = "E-mail: ";
|
||||
"password" = "Mot de passe";
|
||||
"credentials_load_error" = "Échec du chargement des identifiants. Veuillez ouvrir l'application AliasVault pour vérifier les mises à jour.";
|
||||
"no_credential_selected" = "Aucun identifiant sélectionné.";
|
||||
"retrieving_credential" = "Récupération de l'identifiant";
|
||||
"retrieving_passkey" = "Récupération de la clé d'accès";
|
||||
|
||||
/* Context menu strings */
|
||||
"copy_username" = "Copy Username";
|
||||
"copy_password" = "Copy Password";
|
||||
"copy_email" = "Copy Email";
|
||||
"view_details" = "View Details";
|
||||
"username_copied" = "Username copied";
|
||||
"password_copied" = "Password copied";
|
||||
"email_copied" = "Email copied";
|
||||
"copy_username" = "Copier le nom d'utilisateur";
|
||||
"copy_password" = "Copier le mot de passe";
|
||||
"copy_email" = "Copier l'e-mail";
|
||||
"view_details" = "Voir les détails";
|
||||
"username_copied" = "Nom d'utilisateur copié";
|
||||
"password_copied" = "Mot de passe copié";
|
||||
"email_copied" = "E-mail copié";
|
||||
|
||||
/* Search bar */
|
||||
"search_credentials" = "Search credentials...";
|
||||
"search_credentials" = "Rechercher des identifiants...";
|
||||
|
||||
/* Passkey registration */
|
||||
"create_passkey_title" = "Create New Passkey";
|
||||
"create_passkey_subtitle" = "Register a new passkey for this website. It will be securely stored in your vault and automatically synced across your devices with AliasVault.";
|
||||
"create_passkey_button_confirm" = "Create Passkey";
|
||||
"create_passkey_title" = "Créer une nouvelle clé d'accès";
|
||||
"create_passkey_subtitle" = "Enregistrez une nouvelle clé d'accès pour ce site Web. Elle sera stockée de manière sécurisée dans votre coffre et automatiquement synchronisée entre vos appareils avec AliasVault.";
|
||||
"create_passkey_button_confirm" = "Créer clé d'accès";
|
||||
|
||||
/* Passkey provider */
|
||||
"passkey" = "Passkey";
|
||||
"loading_passkeys" = "Loading passkeys...";
|
||||
"no_passkeys_found" = "No passkeys found";
|
||||
"no_passkeys_match" = "No existing passkeys match your search";
|
||||
"select_passkey" = "Select Passkey";
|
||||
"passkeys_load_error" = "Failed to load passkeys. Please open the AliasVault app to check for updates.";
|
||||
"passkey" = "Clé d'accès";
|
||||
"loading_passkeys" = "Chargement des clés d'accès...";
|
||||
"no_passkeys_found" = "Aucune clé d'accès trouvée";
|
||||
"no_passkeys_match" = "Aucune clé d'accès existante ne correspond à votre recherche";
|
||||
"select_passkey" = "Sélectionner une clé d'accès";
|
||||
"passkeys_load_error" = "Impossible de charger les clés d'accès. Veuillez ouvrir l'application AliasVault pour vérifier les mises à jour.";
|
||||
|
||||
/* Passkey replacement */
|
||||
"create_new_passkey" = "Create New Passkey";
|
||||
"select_passkey_to_replace" = "Or, replace an existing passkey";
|
||||
"confirm_replace" = "Replace Passkey";
|
||||
"replace_passkey_title" = "Replace Passkey";
|
||||
"replace_passkey_explanation" = "This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.";
|
||||
"create_passkey_explanation" = "This creates a new passkey and stores it in your vault. It will be automatically synced across all your devices that use AliasVault.";
|
||||
"create_new_passkey" = "Créer une nouvelle clé d'accès";
|
||||
"select_passkey_to_replace" = "Ou, remplacer une clé d'accès existante";
|
||||
"confirm_replace" = "Remplacer la clé d'accès";
|
||||
"replace_passkey_title" = "Remplacer la clé d'accès";
|
||||
"replace_passkey_explanation" = "Cela remplacera la clé d'accès existante par une nouvelle. Veuillez noter que votre ancienne clé d'accès sera écrasée et ne sera plus accessible. Si vous souhaitez créer une clé d'accès séparée, revenez à l'écran précédent.";
|
||||
"create_passkey_explanation" = "Cela crée une nouvelle clé d'accès et la stocke dans votre coffre. Elle sera automatiquement synchronisée sur tous vos appareils qui utilisent AliasVault.";
|
||||
|
||||
/* PIN Unlock */
|
||||
"unlock_vault" = "Unlock Vault";
|
||||
"enter_pin_to_unlock_vault" = "Enter your PIN to unlock your vault";
|
||||
"pin_locked_max_attempts" = "PIN locked after too many failed attempts";
|
||||
"pin_incorrect_attempts_remaining" = "Incorrect PIN. %d attempts remaining";
|
||||
"unlock_vault" = "Déverrouiller le coffre";
|
||||
"enter_pin_to_unlock_vault" = "Entrez votre code PIN pour déverrouiller votre coffre";
|
||||
"pin_locked_max_attempts" = "Le code PIN est verrouillé après trop de tentatives échouées";
|
||||
"pin_incorrect_attempts_remaining" = "Code PIN incorrect, %d tentatives restantes";
|
||||
|
||||
/* PIN Setup */
|
||||
"pin_setup_title" = "Setup PIN";
|
||||
"pin_setup_subtitle" = "Choose a PIN to unlock your vault";
|
||||
"pin_confirm_title" = "Confirm PIN";
|
||||
"pin_confirm_subtitle" = "Re-enter your PIN to confirm";
|
||||
"pin_mismatch" = "PINs do not match. Please try again.";
|
||||
"pin_setup_title" = "Configurer le code PIN";
|
||||
"pin_setup_subtitle" = "Choisissez un code PIN pour déverrouiller votre coffre";
|
||||
"pin_confirm_title" = "Confirmer le code PIN";
|
||||
"pin_confirm_subtitle" = "Entrez à nouveau votre code PIN pour confirmer";
|
||||
"pin_mismatch" = "Les codes PIN ne correspondent pas. Veuillez réessayer.";
|
||||
|
||||
294
apps/mobile-app/package-lock.json
generated
294
apps/mobile-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,10 +38,8 @@
|
||||
"@types/jsrsasign": "^10.5.15",
|
||||
"expo": "^53.0.22",
|
||||
"expo-blur": "~14.1.5",
|
||||
"expo-camera": "^16.1.11",
|
||||
"expo-clipboard": "~7.1.5",
|
||||
"expo-constants": "~17.1.7",
|
||||
"expo-dev-client": "~5.1.8",
|
||||
"expo-document-picker": "~13.1.6",
|
||||
"expo-file-system": "~18.1.11",
|
||||
"expo-font": "~13.3.2",
|
||||
@@ -58,8 +56,7 @@
|
||||
"expo-web-browser": "~14.2.0",
|
||||
"fbemitter": "^3.0.0",
|
||||
"i18next": "^25.3.2",
|
||||
"jest": "~29.7.0",
|
||||
"jest-expo": "~53.0.10",
|
||||
"lodash": "^4.17.21",
|
||||
"otpauth": "^9.4.0",
|
||||
"react": "19.0.0",
|
||||
"react-hook-form": "^7.56.1",
|
||||
@@ -76,6 +73,7 @@
|
||||
"react-native-safe-area-context": "5.6.1",
|
||||
"react-native-screens": "~4.15.4",
|
||||
"react-native-svg": "15.11.2",
|
||||
"react-native-svg-transformer": "^1.5.0",
|
||||
"react-native-toast-message": "^2.2.1",
|
||||
"react-native-webview": "13.13.5",
|
||||
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
|
||||
@@ -98,10 +96,10 @@
|
||||
"eslint-config-expo": "~9.2.0",
|
||||
"eslint-plugin-jsdoc": "^55.2.0",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"expo-dev-client": "~5.1.8",
|
||||
"globals": "^16.3.0",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~53.0.0",
|
||||
"react-native-svg-transformer": "^1.5.0",
|
||||
"typescript": "~5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -105,6 +105,13 @@ export interface Spec extends TurboModule {
|
||||
// Re-authentication methods
|
||||
// Authenticate user with biometric or PIN. If title/subtitle are null/empty, defaults to "Unlock Vault" context.
|
||||
authenticateUser(title: string | null, subtitle: string | null): Promise<boolean>;
|
||||
|
||||
// QR code scanner
|
||||
// Scan a QR code and return the scanned data. Returns null if cancelled or failed.
|
||||
// If prefixes is provided, only QR codes starting with one of these prefixes will be accepted.
|
||||
// Scanner will keep scanning until a matching code is found or user cancels.
|
||||
// statusText is the message to display on the scanner screen (defaults to "Scan QR code" if null/empty).
|
||||
scanQRCode(prefixes: string[] | null, statusText: string | null): Promise<string | null>;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('NativeVaultManager');
|
||||
|
||||
@@ -8,7 +8,7 @@ export class AppInfo {
|
||||
/**
|
||||
* The current mobile app version. This should be updated with each release of the mobile app.
|
||||
*/
|
||||
public static readonly VERSION = '0.25.0';
|
||||
public static readonly VERSION = '0.26.0-alpha';
|
||||
|
||||
/**
|
||||
* The API version to send to the server (base semver without stage suffixes).
|
||||
|
||||
@@ -36,6 +36,20 @@ declare class PasswordGenerator {
|
||||
private readonly uppercaseChars;
|
||||
private readonly numberChars;
|
||||
private readonly specialChars;
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
private readonly ambiguousChars;
|
||||
private length;
|
||||
private useLowercase;
|
||||
|
||||
@@ -39,7 +39,21 @@ var PasswordGenerator = class {
|
||||
this.uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
this.numberChars = "0123456789";
|
||||
this.specialChars = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
this.ambiguousChars = "Il1O0o";
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
this.ambiguousChars = "Il1O0oZzSsBbGg2568|[]{}()<>;:,.`'\"_-";
|
||||
this.length = 18;
|
||||
this.useLowercase = true;
|
||||
this.useUppercase = true;
|
||||
|
||||
@@ -13,7 +13,21 @@ var PasswordGenerator = class {
|
||||
this.uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
this.numberChars = "0123456789";
|
||||
this.specialChars = "!@#$%^&*()_+-=[]{}|;:,.<>?";
|
||||
this.ambiguousChars = "Il1O0o";
|
||||
/**
|
||||
* Ambiguous characters that look similar and are easy to confuse when typing:
|
||||
* - I, l, 1, | (pipe) - all look like vertical lines
|
||||
* - O, 0, o - all look like circles
|
||||
* - Z, 2 - similar appearance
|
||||
* - S, 5 - similar appearance
|
||||
* - B, 8 - similar appearance
|
||||
* - G, 6 - similar appearance
|
||||
* - Brackets, braces, parentheses: [], {}, ()
|
||||
* - Quotes: ', ", `
|
||||
* - Punctuation pairs: ;:, .,
|
||||
* - Dashes: -, _
|
||||
* - Angle brackets: <>
|
||||
*/
|
||||
this.ambiguousChars = "Il1O0oZzSsBbGg2568|[]{}()<>;:,.`'\"_-";
|
||||
this.length = 18;
|
||||
this.useLowercase = true;
|
||||
this.useUppercase = true;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RecentUsageAccountDeletions.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Admin.Main.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Model representing usernames with most account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
public class RecentUsageAccountDeletions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of account deletions for this username in the last 30 days.
|
||||
/// </summary>
|
||||
public int DeletionCount30d { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date when the most recent account with this username was registered.
|
||||
/// </summary>
|
||||
public DateTime? LastRegistrationDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date when the most recent account with this username was deleted.
|
||||
/// </summary>
|
||||
public DateTime? LastDeletionDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="RecentUsageDeletionsByIp.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Admin.Main.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Model representing IP addresses with most account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
public class RecentUsageDeletionsByIp
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the original IP address (for linking purposes).
|
||||
/// </summary>
|
||||
public string OriginalIpAddress { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the anonymized IP address.
|
||||
/// </summary>
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of account deletions from this IP in the last 30 days.
|
||||
/// </summary>
|
||||
public int DeletionCount30d { get; set; }
|
||||
}
|
||||
@@ -31,4 +31,14 @@ public class RecentUsageStatistics
|
||||
/// Gets or sets the list of IP addresses with most mobile login requests in the last 72 hours.
|
||||
/// </summary>
|
||||
public List<RecentUsageMobileLogins> TopIpsByMobileLogins72h { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of IP addresses with most account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
public List<RecentUsageDeletionsByIp> TopIpsByDeletions30d { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of usernames with most account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
public List<RecentUsageAccountDeletions> TopUsernamesByDeletions30d { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
@using AliasVault.Admin.Main.Models
|
||||
@using AliasVault.RazorComponents.Tables
|
||||
|
||||
<div class="p-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Top Usernames by Account Deletions (Last 30d)</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Usernames with the most account deletion events in the last 30 days</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Data != null && Data.Any())
|
||||
{
|
||||
<div class="mb-3">
|
||||
<Paginator CurrentPage="@CurrentPage" PageSize="@PageSize" TotalRecords="@Data.Count" OnPageChanged="@HandlePageChanged" />
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<SortableTable Columns="@_tableColumns">
|
||||
@foreach (var deletion in PagedData)
|
||||
{
|
||||
<SortableTableRow>
|
||||
<SortableTableColumn IsPrimary="true">
|
||||
<a href="logging/auth?search=@Uri.EscapeDataString(deletion.Username)" class="text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer">
|
||||
@deletion.Username
|
||||
</a>
|
||||
</SortableTableColumn>
|
||||
<SortableTableColumn>@deletion.DeletionCount30d.ToString("N0")</SortableTableColumn>
|
||||
<SortableTableColumn>
|
||||
@if (deletion.LastDeletionDate.HasValue)
|
||||
{
|
||||
<span>@deletion.LastDeletionDate.Value.ToString("yyyy-MM-dd HH:mm:ss") UTC</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-gray-400 dark:text-gray-500">-</span>
|
||||
}
|
||||
</SortableTableColumn>
|
||||
</SortableTableRow>
|
||||
}
|
||||
</SortableTable>
|
||||
</div>
|
||||
}
|
||||
else if (Data != null)
|
||||
{
|
||||
<div class="text-center text-gray-500 dark:text-gray-400">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No Recent Account Deletions</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">No account deletions occurred in the last 30 days.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="px-6 py-8 flex justify-center">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<RecentUsageAccountDeletions>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageAccountDeletions> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageAccountDeletions>();
|
||||
|
||||
private readonly List<TableColumn> _tableColumns = new()
|
||||
{
|
||||
new() { Title = "Username", PropertyName = "Username", Sortable = false },
|
||||
new() { Title = "Deletions (30d)", PropertyName = "DeletionCount30d", Sortable = false },
|
||||
new() { Title = "Last Deletion", PropertyName = "LastDeletionDate", Sortable = false }
|
||||
};
|
||||
|
||||
private void HandlePageChanged(int page)
|
||||
{
|
||||
CurrentPage = page;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@
|
||||
public List<RecentUsageAliases>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 20;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageAliases> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageAliases>();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
@using AliasVault.Admin.Main.Models
|
||||
@using AliasVault.RazorComponents.Tables
|
||||
|
||||
<div class="p-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Top IP Addresses by Account Deletions (Last 30d)</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">IP addresses with the most account deletions in the last 30 days (last octet anonymized)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Data != null && Data.Any())
|
||||
{
|
||||
<div class="mb-3">
|
||||
<Paginator CurrentPage="@CurrentPage" PageSize="@PageSize" TotalRecords="@Data.Count" OnPageChanged="@HandlePageChanged" />
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<SortableTable Columns="@_tableColumns">
|
||||
@foreach (var ip in PagedData)
|
||||
{
|
||||
<SortableTableRow>
|
||||
<SortableTableColumn IsPrimary="true">
|
||||
<a href="logging/auth?search=@Uri.EscapeDataString(ip.OriginalIpAddress)" class="font-mono text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer">
|
||||
@ip.IpAddress
|
||||
</a>
|
||||
</SortableTableColumn>
|
||||
<SortableTableColumn>@ip.DeletionCount30d.ToString("N0")</SortableTableColumn>
|
||||
</SortableTableRow>
|
||||
}
|
||||
</SortableTable>
|
||||
</div>
|
||||
}
|
||||
else if (Data != null)
|
||||
{
|
||||
<div class="text-center text-gray-500 dark:text-gray-400">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No Recent Account Deletions</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">No account deletions occurred in the last 30 days.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="px-6 py-8 flex justify-center">
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<RecentUsageDeletionsByIp>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageDeletionsByIp> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageDeletionsByIp>();
|
||||
|
||||
private readonly List<TableColumn> _tableColumns = new()
|
||||
{
|
||||
new() { Title = "IP Range", PropertyName = "IpAddress", Sortable = false },
|
||||
new() { Title = "Deletions (30d)", PropertyName = "DeletionCount30d", Sortable = false }
|
||||
};
|
||||
|
||||
private void HandlePageChanged(int page)
|
||||
{
|
||||
CurrentPage = page;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user