mirror of
https://github.com/aliasvault/aliasvault.git
synced 2025-12-23 22:28:22 -05:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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é."
|
||||
@@ -186,8 +186,8 @@
|
||||
"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",
|
||||
"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",
|
||||
@@ -244,7 +244,7 @@
|
||||
"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)",
|
||||
"nameOptional": "Nom (facultatif)",
|
||||
"secretKey": "Secret Key",
|
||||
"saveToViewCode": "Save to view code",
|
||||
"errors": {
|
||||
@@ -365,7 +365,7 @@
|
||||
"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",
|
||||
"pin": "Code PIN",
|
||||
"pinDescription": "Unlock vault with PIN code",
|
||||
"setupPin": "Setup PIN Code",
|
||||
"enterNewPinDescription": "Enter a PIN code consisting of minimum 6 digits",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,78 +1,78 @@
|
||||
<?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>
|
||||
<!-- 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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7,22 +7,22 @@
|
||||
"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.",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 60;
|
||||
objectVersion = 70;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -212,7 +212,7 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = {
|
||||
CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@@ -222,84 +222,13 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
CE59C7602E4F47FD0024A246 /* VaultUITests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE77825E2EA1822400A75E6F /* VaultUtils */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultUtils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultStoreKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultStoreKitTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = VaultModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CEE909812DA548C7008D568F /* Autofill */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */,
|
||||
);
|
||||
explicitFileTypes = {
|
||||
};
|
||||
explicitFolders = (
|
||||
);
|
||||
path = Autofill;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE59C7602E4F47FD0024A246 /* VaultUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUITests; sourceTree = "<group>"; };
|
||||
CE77825E2EA1822400A75E6F /* VaultUtils */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUtils; sourceTree = "<group>"; };
|
||||
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = "<group>"; };
|
||||
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = "<group>"; };
|
||||
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = "<group>"; };
|
||||
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = "<group>"; };
|
||||
CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -787,7 +716,9 @@
|
||||
Base,
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
he,
|
||||
nl,
|
||||
it,
|
||||
@@ -1297,7 +1228,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 +1243,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1338,7 +1269,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 +1279,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.25.0;
|
||||
MARKETING_VERSION = 0.26.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1418,10 +1349,7 @@
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
@@ -1475,10 +1403,7 @@
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
" ",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
@@ -1499,7 +1424,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 +1460,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 +1494,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 +1551,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 +1604,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 +1657,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 +1706,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 +1741,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 +1774,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 +1827,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 +1876,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 +1928,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 +1979,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 +1995,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 +2024,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 +2040,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.
@@ -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):
|
||||
@@ -2449,11 +2445,6 @@ PODS:
|
||||
- SwiftLint (0.59.1)
|
||||
- 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:
|
||||
@@ -2820,7 +2807,6 @@ SPEC CHECKSUMS:
|
||||
expo-dev-menu-interface: 609c35ae8b97479cdd4c9e23c8cf6adc44beea0e
|
||||
ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6
|
||||
ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9
|
||||
ExpoCamera: e1879906d41184e84b57d7643119f8509414e318
|
||||
ExpoClipboard: 436f6de6971f14eb75ae160e076d9cb3b19eb795
|
||||
ExpoDocumentPicker: b263a279685b6640b8c8bc70d71c83067aeaae55
|
||||
ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63
|
||||
@@ -2925,7 +2911,6 @@ SPEC CHECKSUMS:
|
||||
SwiftLint: 3d48e2fb2a3468fdaccf049e5e755df22fb40c2c
|
||||
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.";
|
||||
|
||||
188
apps/mobile-app/package-lock.json
generated
188
apps/mobile-app/package-lock.json
generated
@@ -17,10 +17,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",
|
||||
@@ -37,8 +35,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",
|
||||
@@ -55,6 +52,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",
|
||||
@@ -77,10 +75,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": {
|
||||
@@ -129,6 +127,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -4099,6 +4098,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz",
|
||||
"integrity": "sha512-uEcYWi1NV+2Qe1oELfp9b5hTYekqWATv2cuwcOAg5EvsIsUPtzFrKIasgUXLBRGb9P7yR5ifoJ+ug4u6jdqSTQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^7.12.4",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
@@ -4223,7 +4223,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
||||
"integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4240,7 +4239,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
|
||||
"integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4257,7 +4255,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
|
||||
"integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4274,7 +4271,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
|
||||
"integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4291,7 +4287,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
|
||||
"integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4308,7 +4303,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
|
||||
"integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4325,7 +4319,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
|
||||
"integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -4342,7 +4335,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
|
||||
"integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -4359,7 +4351,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
|
||||
"integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
|
||||
@@ -4386,8 +4377,8 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
|
||||
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "8.1.0",
|
||||
@@ -4407,7 +4398,6 @@
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -4420,7 +4410,6 @@
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.3.0",
|
||||
@@ -4447,7 +4436,6 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
|
||||
"integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.3",
|
||||
@@ -4465,7 +4453,6 @@
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
@@ -4478,7 +4465,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
|
||||
"integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
@@ -4501,7 +4487,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz",
|
||||
"integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cosmiconfig": "^8.1.3",
|
||||
@@ -4523,7 +4508,6 @@
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.3.0",
|
||||
@@ -4560,7 +4544,6 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
@@ -4631,7 +4614,6 @@
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
@@ -4643,7 +4625,6 @@
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
@@ -4774,6 +4755,7 @@
|
||||
"integrity": "sha512-ixLZ7zG7j1fM0DijL9hDArwhwcCb4vqmePgwtV0GfnkHRSCUEv4LvzarcTdhoqgyMznUx/EhoTUv31CKZzkQlw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4891,6 +4873,7 @@
|
||||
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.43.0",
|
||||
"@typescript-eslint/types": "8.43.0",
|
||||
@@ -5421,7 +5404,6 @@
|
||||
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||
@@ -5432,24 +5414,21 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.13.2",
|
||||
@@ -5457,7 +5436,6 @@
|
||||
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
@@ -5469,8 +5447,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.14.1",
|
||||
@@ -5478,7 +5455,6 @@
|
||||
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -5492,7 +5468,6 @@
|
||||
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
@@ -5503,7 +5478,6 @@
|
||||
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
@@ -5513,8 +5487,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.14.1",
|
||||
@@ -5522,7 +5495,6 @@
|
||||
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -5540,7 +5512,6 @@
|
||||
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
@@ -5555,7 +5526,6 @@
|
||||
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -5569,7 +5539,6 @@
|
||||
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
@@ -5585,7 +5554,6 @@
|
||||
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@xtuc/long": "4.2.2"
|
||||
@@ -5605,16 +5573,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
@@ -5663,6 +5629,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5687,7 +5654,6 @@
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
@@ -5767,7 +5733,6 @@
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
@@ -5786,7 +5751,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -5803,8 +5767,7 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anser": {
|
||||
"version": "1.4.10",
|
||||
@@ -6562,6 +6525,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001737",
|
||||
"electron-to-chromium": "^1.5.211",
|
||||
@@ -6707,7 +6671,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -6801,7 +6764,6 @@
|
||||
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
@@ -7294,7 +7256,6 @@
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
|
||||
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "~2.2.0"
|
||||
@@ -7308,7 +7269,6 @@
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.28",
|
||||
@@ -7323,7 +7283,6 @@
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/cssom": {
|
||||
@@ -7734,7 +7693,6 @@
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"no-case": "^3.0.4",
|
||||
@@ -7840,7 +7798,6 @@
|
||||
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -8046,8 +8003,7 @@
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
@@ -8163,6 +8119,7 @@
|
||||
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8347,6 +8304,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -8703,6 +8661,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo/-/expo-53.0.22.tgz",
|
||||
"integrity": "sha512-sJ2I4W/e5iiM4u/wYCe3qmW4D7WPCRqByPDD0hJcdYNdjc9HFFFdO4OAudZVyC/MmtoWZEIH5kTJP1cw9FjzYA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@expo/cli": "0.24.21",
|
||||
@@ -8772,26 +8731,6 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-camera": {
|
||||
"version": "16.1.11",
|
||||
"resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.11.tgz",
|
||||
"integrity": "sha512-etA5ZKoC6nPBnWWqiTmlX//zoFZ6cWQCCIdmpUHTGHAKd4qZNCkhPvBWbi8o32pDe57lix1V4+TPFgEcvPwsaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-web": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-native-web": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/expo-clipboard": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-7.1.5.tgz",
|
||||
@@ -8808,6 +8747,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.7.tgz",
|
||||
"integrity": "sha512-byBjGsJ6T6FrLlhOBxw4EaiMXrZEn/MlUYIj/JAd+FS7ll5X/S4qVRbIimSJtdW47hXMq0zxPfJX6njtA56hHA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@expo/config": "~11.0.12",
|
||||
"@expo/env": "~1.0.7"
|
||||
@@ -8821,6 +8761,7 @@
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.1.8.tgz",
|
||||
"integrity": "sha512-IopYPgBi3JflksO5ieTphbKsbYHy9iIVdT/d69It++y0iBMSm0oBIoDmUijrHKjE3fV6jnrwrm8luU13/mzIQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"expo-dev-launcher": "5.1.11",
|
||||
@@ -8837,6 +8778,7 @@
|
||||
"version": "5.1.11",
|
||||
"resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.11.tgz",
|
||||
"integrity": "sha512-bN0+nv5H038s8Gzf8i16hwCyD3sWDmHp7vb+QbL1i6B3XNnICCKS/H/3VH6H3PRMvCmoLGPlg+ODDqGlf0nu3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "8.11.0",
|
||||
@@ -8852,6 +8794,7 @@
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
@@ -8868,12 +8811,14 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expo-dev-menu": {
|
||||
"version": "6.1.10",
|
||||
"resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-6.1.10.tgz",
|
||||
"integrity": "sha512-LaI0Bw5zzw5XefjYSX6YaMydzk0YBysjqQoxzj6ufDyKgwAfPmFwOLkZ03DOSerc9naezGLNAGgTEN6QTgMmgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"expo-dev-menu-interface": "1.10.0"
|
||||
@@ -8886,6 +8831,7 @@
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-1.10.0.tgz",
|
||||
"integrity": "sha512-NxtM/qot5Rh2cY333iOE87dDg1S8CibW+Wu4WdLua3UMjy81pXYzAGCZGNOeY7k9GpNFqDPNDXWyBSlk9r2pBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
@@ -8915,6 +8861,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.3.2.tgz",
|
||||
"integrity": "sha512-wUlMdpqURmQ/CNKK/+BIHkDA5nGjMqNlYmW0pJFXY/KE/OG80Qcavdu2sHsL4efAIiNGvYdBS10WztuQYU4X0A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fontfaceobserver": "^2.1.0"
|
||||
},
|
||||
@@ -8936,6 +8883,7 @@
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz",
|
||||
"integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expo-keep-awake": {
|
||||
@@ -8964,6 +8912,7 @@
|
||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
|
||||
"integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"expo-constants": "~17.1.7",
|
||||
"invariant": "^2.2.4"
|
||||
@@ -9002,6 +8951,7 @@
|
||||
"version": "0.16.6",
|
||||
"resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.16.6.tgz",
|
||||
"integrity": "sha512-1A+do6/mLUWF9xd3uCrlXr9QFDbjbfqAYmUy8UDLOjof1lMrOhyeC4Yi6WexA/A8dhZEpIxSMCKfn7G4aHAh4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@expo/config": "~11.0.12",
|
||||
@@ -9153,6 +9103,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.1.0.tgz",
|
||||
"integrity": "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
@@ -9244,8 +9195,7 @@
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.5.3",
|
||||
@@ -9769,8 +9719,7 @@
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
@@ -10106,6 +10055,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -10179,7 +10129,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
@@ -10196,7 +10145,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -10951,6 +10899,7 @@
|
||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
@@ -11270,9 +11219,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-expo/node_modules/react-server-dom-webpack": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.0.0.tgz",
|
||||
"integrity": "sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw==",
|
||||
"version": "19.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.0.3.tgz",
|
||||
"integrity": "sha512-Ri1trzmT2G2ZTrummbbJca306MKyXpI8l4B7j1F8jGCjykUHCaojwtXBr/luEiRSFzQRkYBkH+wabfppP3x/IQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11284,8 +11233,8 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.0.3",
|
||||
"react-dom": "^19.0.3",
|
||||
"webpack": "^5.59.0"
|
||||
}
|
||||
},
|
||||
@@ -11294,8 +11243,7 @@
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "29.6.3",
|
||||
@@ -11968,7 +11916,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
@@ -12350,7 +12297,6 @@
|
||||
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
}
|
||||
@@ -12374,7 +12320,6 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
@@ -12616,7 +12561,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.3"
|
||||
@@ -13397,7 +13341,6 @@
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lower-case": "^2.0.2",
|
||||
@@ -13457,9 +13400,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
@@ -13931,7 +13874,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0"
|
||||
@@ -13954,7 +13896,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
@@ -14014,7 +13955,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
|
||||
"integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
@@ -14076,7 +14016,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -14491,7 +14430,6 @@
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -14550,6 +14488,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -14608,6 +14547,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -14656,6 +14596,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.6.tgz",
|
||||
"integrity": "sha512-kvIWSmf4QPfY41HC25TR285N7Fv0Pyn3DAEK8qRL9dA35usSaxsJkHfw+VqnonqJjXOaoKCEanwudRAJ60TBGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.7.0",
|
||||
"@react-native/assets-registry": "0.79.6",
|
||||
@@ -14875,6 +14816,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz",
|
||||
"integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
|
||||
"@babel/plugin-transform-class-properties": "^7.0.0-0",
|
||||
@@ -14910,6 +14852,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz",
|
||||
"integrity": "sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
@@ -14920,6 +14863,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.15.4.tgz",
|
||||
"integrity": "sha512-aKHPDScUbpQiZEG9eZssHdG5jEQs4yiJ8eMx6g81Ex/xU7DZkv3911enzdCb+v4eJE79X8waizY0ZhauZJQmrw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.0",
|
||||
"react-native-is-edge-to-edge": "^1.2.1",
|
||||
@@ -14935,6 +14879,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz",
|
||||
"integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^1.1.3",
|
||||
@@ -14949,7 +14894,6 @@
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-1.5.1.tgz",
|
||||
"integrity": "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgr/core": "^8.1.0",
|
||||
@@ -14977,6 +14921,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.5.tgz",
|
||||
"integrity": "sha512-MfC2B+woL4Hlj2WCzcb1USySKk+SteXnUKmKktOk/H/AQy5+LuVdkPKm8SknJ0/RxaxhZ48WBoTRGaqgR137hw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"invariant": "2.2.4"
|
||||
@@ -15544,7 +15489,6 @@
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
@@ -15583,7 +15527,6 @@
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
@@ -15596,8 +15539,7 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/secure-remote-password": {
|
||||
"version": "0.3.0",
|
||||
@@ -15707,7 +15649,6 @@
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -16119,7 +16060,6 @@
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dot-case": "^3.0.4",
|
||||
@@ -16690,14 +16630,12 @@
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/svgo": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
|
||||
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@trysound/sax": "0.2.0",
|
||||
@@ -16723,7 +16661,6 @@
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
@@ -16733,7 +16670,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.30",
|
||||
@@ -16747,7 +16683,6 @@
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
@@ -16763,7 +16698,6 @@
|
||||
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
@@ -16862,7 +16796,6 @@
|
||||
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
@@ -16898,7 +16831,6 @@
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -16914,7 +16846,6 @@
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
@@ -17147,7 +17078,6 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
@@ -17282,6 +17212,7 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -17490,6 +17421,7 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
@@ -17644,7 +17576,6 @@
|
||||
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.1.2"
|
||||
@@ -17678,7 +17609,6 @@
|
||||
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -17738,7 +17668,6 @@
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
@@ -17753,7 +17682,6 @@
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
public List<RecentUsageEmails>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 20;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageEmails> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageEmails>();
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
public List<RecentUsageMobileLogins>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 20;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageMobileLogins> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageMobileLogins>();
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
public List<RecentUsageRegistrations>? Data { get; set; }
|
||||
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
private int PageSize { get; set; } = 20;
|
||||
private int PageSize { get; set; } = 10;
|
||||
|
||||
private IEnumerable<RecentUsageRegistrations> PagedData =>
|
||||
Data?.Skip((CurrentPage - 1) * PageSize).Take(PageSize) ?? Enumerable.Empty<RecentUsageRegistrations>();
|
||||
|
||||
@@ -30,6 +30,12 @@
|
||||
|
||||
<!-- Top IP Addresses by Mobile Login Requests ---->
|
||||
<RecentUsageMobileLoginsTable Data="@_recentUsageStats?.TopIpsByMobileLogins72h" />
|
||||
|
||||
<!-- Top IP Addresses by Account Deletions ---->
|
||||
<RecentUsageDeletionsByIpTable Data="@_recentUsageStats?.TopIpsByDeletions30d" />
|
||||
|
||||
<!-- Top Usernames by Account Deletions ---->
|
||||
<RecentUsageAccountDeletionsTable Data="@_recentUsageStats?.TopUsernamesByDeletions30d" />
|
||||
</div>
|
||||
|
||||
@if (_loadingError)
|
||||
|
||||
@@ -218,11 +218,7 @@ else
|
||||
v.RevisionNumber,
|
||||
CredentialCount = v.CredentialsCount,
|
||||
}),
|
||||
EmailClaims = u.EmailClaims.Select(ec => new
|
||||
{
|
||||
ec.CreatedAt,
|
||||
ec.Address
|
||||
}),
|
||||
EmailClaimCount = u.EmailClaims.Count(),
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
@@ -247,7 +243,7 @@ else
|
||||
IsInactive = isInactive,
|
||||
VaultCount = user.Vaults.Count(),
|
||||
CredentialCount = user.Vaults.OrderByDescending(x => x.RevisionNumber).First().CredentialCount,
|
||||
EmailClaimCount = user.EmailClaims.Count(),
|
||||
EmailClaimCount = user.EmailClaimCount,
|
||||
VaultStorageInKb = user.Vaults.Sum(x => x.FileSize),
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
@@ -15,6 +15,8 @@ else if (RelatedUsers.Any())
|
||||
</SortableTableColumn>
|
||||
<SortableTableColumn>@relatedUser.SharedIpAddresses.ToString("N0")</SortableTableColumn>
|
||||
<SortableTableColumn>@relatedUser.MostRecentSharedIp</SortableTableColumn>
|
||||
<SortableTableColumn>@relatedUser.CredentialsCount.ToString("N0")</SortableTableColumn>
|
||||
<SortableTableColumn>@relatedUser.EmailClaimsCount.ToString("N0")</SortableTableColumn>
|
||||
<SortableTableColumn>@relatedUser.RegistrationDate.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
|
||||
<SortableTableColumn><StatusPill Enabled="!relatedUser.IsBlocked" TextTrue="Active" TextFalse="Blocked" /></SortableTableColumn>
|
||||
</SortableTableRow>
|
||||
@@ -65,6 +67,8 @@ else
|
||||
new TableColumn { Title = "Username", PropertyName = "Username" },
|
||||
new TableColumn { Title = "Shared IPs", PropertyName = "SharedIpAddresses" },
|
||||
new TableColumn { Title = "Most Recent IP", PropertyName = "MostRecentSharedIp" },
|
||||
new TableColumn { Title = "Credentials", PropertyName = "CredentialsCount" },
|
||||
new TableColumn { Title = "Email Claims", PropertyName = "EmailClaimsCount" },
|
||||
new TableColumn { Title = "Registered", PropertyName = "RegistrationDate" },
|
||||
new TableColumn { Title = "Status", PropertyName = "IsBlocked" },
|
||||
];
|
||||
@@ -152,15 +156,32 @@ else
|
||||
|
||||
TotalRelatedUsers = users.Count;
|
||||
|
||||
// Get vault statistics for all related users (from their latest vault)
|
||||
var userIds = users.Select(u => u.Id).ToList();
|
||||
var vaultStats = await dbContext.Vaults
|
||||
.Where(v => userIds.Contains(v.UserId))
|
||||
.GroupBy(v => v.UserId)
|
||||
.Select(g => new
|
||||
{
|
||||
UserId = g.Key,
|
||||
CredentialsCount = g.OrderByDescending(v => v.RevisionNumber).First().CredentialsCount,
|
||||
EmailClaimsCount = g.OrderByDescending(v => v.RevisionNumber).First().EmailClaimsCount
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// Combine the data
|
||||
RelatedUsers = (from user in users
|
||||
join data in relatedUserData on user.UserName equals data.Username
|
||||
join vaultStat in vaultStats on user.Id equals vaultStat.UserId into vaultGroup
|
||||
from vault in vaultGroup.DefaultIfEmpty()
|
||||
select new RelatedUserInfo
|
||||
{
|
||||
UserId = user.Id,
|
||||
Username = user.UserName ?? "Unknown",
|
||||
SharedIpAddresses = data.SharedIps,
|
||||
MostRecentSharedIp = data.MostRecentSharedIp ?? "-",
|
||||
CredentialsCount = vault?.CredentialsCount ?? 0,
|
||||
EmailClaimsCount = vault?.EmailClaimsCount ?? 0,
|
||||
RegistrationDate = user.CreatedAt,
|
||||
IsBlocked = user.Blocked
|
||||
})
|
||||
@@ -186,6 +207,8 @@ else
|
||||
"Username" => SortableTable.SortListByProperty(relatedUsers, r => r.Username, sortDirection),
|
||||
"SharedIpAddresses" => SortableTable.SortListByProperty(relatedUsers, r => r.SharedIpAddresses, sortDirection),
|
||||
"MostRecentSharedIp" => SortableTable.SortListByProperty(relatedUsers, r => r.MostRecentSharedIp, sortDirection),
|
||||
"CredentialsCount" => SortableTable.SortListByProperty(relatedUsers, r => r.CredentialsCount, sortDirection),
|
||||
"EmailClaimsCount" => SortableTable.SortListByProperty(relatedUsers, r => r.EmailClaimsCount, sortDirection),
|
||||
"RegistrationDate" => SortableTable.SortListByProperty(relatedUsers, r => r.RegistrationDate, sortDirection),
|
||||
"IsBlocked" => SortableTable.SortListByProperty(relatedUsers, r => r.IsBlocked, sortDirection),
|
||||
_ => relatedUsers
|
||||
@@ -206,6 +229,8 @@ else
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public int SharedIpAddresses { get; set; }
|
||||
public string MostRecentSharedIp { get; set; } = string.Empty;
|
||||
public int CredentialsCount { get; set; }
|
||||
public int EmailClaimsCount { get; set; }
|
||||
public DateTime RegistrationDate { get; set; }
|
||||
public bool IsBlocked { get; set; }
|
||||
}
|
||||
|
||||
@@ -332,6 +332,18 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await base.OnParametersSetAsync();
|
||||
|
||||
// Refresh data when navigating to a different user (e.g., clicking related user links)
|
||||
if (!IsLoading && User?.Id != Id)
|
||||
{
|
||||
await RefreshData();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshData()
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
@@ -116,6 +116,8 @@ public class StatisticsService
|
||||
GetTopUsersByEmails72hAsync().ContinueWith(t => stats.TopUsersByEmails72h = t.Result),
|
||||
GetTopIpsByRegistrations72hAsync().ContinueWith(t => stats.TopIpsByRegistrations72h = t.Result),
|
||||
GetTopIpsByMobileLogins72hAsync().ContinueWith(t => stats.TopIpsByMobileLogins72h = t.Result),
|
||||
GetTopIpsByDeletions30dAsync().ContinueWith(t => stats.TopIpsByDeletions30d = t.Result),
|
||||
GetTopUsernamesByDeletions30dAsync().ContinueWith(t => stats.TopUsernamesByDeletions30d = t.Result),
|
||||
};
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
@@ -475,7 +477,7 @@ public class StatisticsService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 20 users by number of aliases created in the last 72 hours.
|
||||
/// Gets the top 100 users by number of aliases created in the last 72 hours.
|
||||
/// </summary>
|
||||
/// <returns>List of top users by recent aliases.</returns>
|
||||
private async Task<List<RecentUsageAliases>> GetTopUsersByAliases72hAsync()
|
||||
@@ -495,7 +497,7 @@ public class StatisticsService
|
||||
AliasCount72h = g.Count(),
|
||||
})
|
||||
.OrderByDescending(u => u.AliasCount72h)
|
||||
.Take(20)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topUsers.Select(u => new RecentUsageAliases
|
||||
@@ -509,7 +511,7 @@ public class StatisticsService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 20 users by number of emails received in the last 72 hours.
|
||||
/// Gets the top 100 users by number of emails received in the last 72 hours.
|
||||
/// </summary>
|
||||
/// <returns>List of top users by recent emails.</returns>
|
||||
private async Task<List<RecentUsageEmails>> GetTopUsersByEmails72hAsync()
|
||||
@@ -529,7 +531,7 @@ public class StatisticsService
|
||||
EmailCount72h = g.Count(),
|
||||
})
|
||||
.OrderByDescending(u => u.EmailCount72h)
|
||||
.Take(20)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topUsers.Select(u => new RecentUsageEmails
|
||||
@@ -543,7 +545,7 @@ public class StatisticsService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 20 IP addresses by number of registrations in the last 72 hours.
|
||||
/// Gets the top 100 IP addresses by number of registrations in the last 72 hours.
|
||||
/// </summary>
|
||||
/// <returns>List of top IP addresses by recent registrations.</returns>
|
||||
private async Task<List<RecentUsageRegistrations>> GetTopIpsByRegistrations72hAsync()
|
||||
@@ -565,7 +567,7 @@ public class StatisticsService
|
||||
RegistrationCount72h = g.Count(),
|
||||
})
|
||||
.OrderByDescending(ip => ip.RegistrationCount72h)
|
||||
.Take(20)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topIps.Select(ip => new RecentUsageRegistrations
|
||||
@@ -577,7 +579,7 @@ public class StatisticsService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 20 IP addresses by number of mobile login requests in the last 72 hours.
|
||||
/// Gets the top 100 IP addresses by number of mobile login requests in the last 72 hours.
|
||||
/// </summary>
|
||||
/// <returns>List of top IP addresses by mobile login requests.</returns>
|
||||
private async Task<List<RecentUsageMobileLogins>> GetTopIpsByMobileLogins72hAsync()
|
||||
@@ -597,7 +599,7 @@ public class StatisticsService
|
||||
MobileLoginCount72h = g.Count(),
|
||||
})
|
||||
.OrderByDescending(ip => ip.MobileLoginCount72h)
|
||||
.Take(20)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topIps.Select(ip => new RecentUsageMobileLogins
|
||||
@@ -607,4 +609,72 @@ public class StatisticsService
|
||||
MobileLoginCount72h = ip.MobileLoginCount72h,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 100 IP addresses by number of account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
/// <returns>List of top IP addresses by recent account deletions.</returns>
|
||||
private async Task<List<RecentUsageDeletionsByIp>> GetTopIpsByDeletions30dAsync()
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync();
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-30);
|
||||
|
||||
// Get account deletions by IP from auth logs (using AccountDeletion event type)
|
||||
var topIps = await context.AuthLogs
|
||||
.Where(al => al.Timestamp >= cutoffDate &&
|
||||
al.IpAddress != null &&
|
||||
al.IpAddress != "xxx.xxx.xxx.xxx" &&
|
||||
al.IsSuccess &&
|
||||
al.EventType == AuthEventType.AccountDeletion)
|
||||
.GroupBy(al => al.IpAddress)
|
||||
.Select(g => new
|
||||
{
|
||||
IpAddress = g.Key,
|
||||
DeletionCount30d = g.Count(),
|
||||
})
|
||||
.OrderByDescending(ip => ip.DeletionCount30d)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topIps.Select(ip => new RecentUsageDeletionsByIp
|
||||
{
|
||||
OriginalIpAddress = ip.IpAddress!,
|
||||
IpAddress = AnonymizeIpAddress(ip.IpAddress!),
|
||||
DeletionCount30d = ip.DeletionCount30d,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top 100 usernames by number of account deletions in the last 30 days.
|
||||
/// </summary>
|
||||
/// <returns>List of top usernames by recent account deletions.</returns>
|
||||
private async Task<List<RecentUsageAccountDeletions>> GetTopUsernamesByDeletions30dAsync()
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync();
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-30);
|
||||
|
||||
// Get account deletions by username from auth logs (using AccountDeletion event type)
|
||||
var topUsernames = await context.AuthLogs
|
||||
.Where(al => al.Timestamp >= cutoffDate &&
|
||||
al.Username != null &&
|
||||
al.IsSuccess &&
|
||||
al.EventType == AuthEventType.AccountDeletion)
|
||||
.GroupBy(al => al.Username)
|
||||
.Select(g => new
|
||||
{
|
||||
Username = g.Key,
|
||||
DeletionCount30d = g.Count(),
|
||||
LastDeletionDate = g.Max(al => al.Timestamp),
|
||||
})
|
||||
.OrderByDescending(u => u.DeletionCount30d)
|
||||
.Take(100)
|
||||
.ToListAsync();
|
||||
|
||||
return topUsernames.Select(u => new RecentUsageAccountDeletions
|
||||
{
|
||||
Username = u.Username!,
|
||||
DeletionCount30d = u.DeletionCount30d,
|
||||
LastDeletionDate = u.LastDeletionDate,
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,18 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
return BadRequest(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.REFRESH_TOKEN_REQUIRED, 400));
|
||||
}
|
||||
|
||||
var principal = GetPrincipalFromToken(tokenModel.Token);
|
||||
ClaimsPrincipal principal;
|
||||
try
|
||||
{
|
||||
principal = GetPrincipalFromToken(tokenModel.Token);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If token validation fails (expired, malformed, or invalid signature),
|
||||
// return unauthorized as we cannot identify the user from the access token.
|
||||
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.INVALID_REFRESH_TOKEN, 401));
|
||||
}
|
||||
|
||||
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value == null)
|
||||
{
|
||||
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USER_NOT_FOUND, 401));
|
||||
@@ -354,31 +365,24 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
||||
{
|
||||
await using var context = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// If the token is not provided, return bad request.
|
||||
// If the refresh token is not provided, return bad request.
|
||||
if (string.IsNullOrWhiteSpace(model.RefreshToken))
|
||||
{
|
||||
return BadRequest(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.REFRESH_TOKEN_REQUIRED, 400));
|
||||
}
|
||||
|
||||
var principal = GetPrincipalFromToken(model.Token);
|
||||
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value == null)
|
||||
// Look up the refresh token directly - we don't need to validate the access token
|
||||
// since the refresh token itself contains the user information we need.
|
||||
var refreshTokenEntry = await context.AliasVaultUserRefreshTokens.Include(t => t.User).FirstOrDefaultAsync(t => t.Value == model.RefreshToken);
|
||||
|
||||
if (refreshTokenEntry == null)
|
||||
{
|
||||
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USER_NOT_FOUND, 401));
|
||||
// Token doesn't exist - could already be revoked or never existed.
|
||||
// Return success to avoid leaking information about token validity.
|
||||
return Ok();
|
||||
}
|
||||
|
||||
var user = await userManager.FindByIdAsync(principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty);
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.USER_NOT_FOUND, 401));
|
||||
}
|
||||
|
||||
// Check if the refresh token is valid.
|
||||
var providedTokenExists = await context.AliasVaultUserRefreshTokens.AnyAsync(t => t.UserId == user.Id && t.Value == model.RefreshToken);
|
||||
if (!providedTokenExists)
|
||||
{
|
||||
await authLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.Logout, AuthFailureReason.InvalidRefreshToken);
|
||||
return Unauthorized(ApiErrorCodeHelper.CreateErrorResponse(ApiErrorCode.INVALID_REFRESH_TOKEN, 401));
|
||||
}
|
||||
var user = refreshTokenEntry.User;
|
||||
|
||||
// Remove the provided refresh token and any other existing refresh tokens that are issued to the current device ID.
|
||||
// This to make sure all tokens are revoked for this device that user is "logging out" from.
|
||||
|
||||
@@ -481,9 +481,6 @@ else
|
||||
|
||||
private async Task GenerateRandomAlias()
|
||||
{
|
||||
GlobalLoadingSpinner.Show();
|
||||
StateHasChanged();
|
||||
|
||||
// Store current values BEFORE generating, as the service might modify them
|
||||
string currentUsername = Obj.Username ?? string.Empty;
|
||||
string currentPassword = Obj.Password.Value ?? string.Empty;
|
||||
@@ -528,7 +525,6 @@ else
|
||||
LastGeneratedEmail = generatedObj.Alias.Email;
|
||||
}
|
||||
|
||||
GlobalLoadingSpinner.Hide();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -117,75 +117,75 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="REFRESH_TOKEN_REQUIRED" xml:space="preserve">
|
||||
<value>Refresh token is required.</value>
|
||||
<value>Se requiere actualizar el token.</value>
|
||||
</data>
|
||||
<data name="ACCOUNT_BLOCKED" xml:space="preserve">
|
||||
<value>Your account has been disabled. If you believe this is a mistake, please contact support.</value>
|
||||
<value>Tu cuenta ha sido desactivada. Si crees que esto es un error, ponte en contacto con soporte.</value>
|
||||
</data>
|
||||
<data name="INVALID_REFRESH_TOKEN" xml:space="preserve">
|
||||
<value>Invalid refresh token</value>
|
||||
<value>Token de actualización inválido</value>
|
||||
</data>
|
||||
<data name="PUBLIC_REGISTRATION_DISABLED" xml:space="preserve">
|
||||
<value>New account registration is currently disabled on this server. Please contact the administrator.</value>
|
||||
<value>El registro de nueva cuenta está actualmente deshabilitado en este servidor. Por favor, contacte con el administrador.</value>
|
||||
</data>
|
||||
<data name="USER_NOT_FOUND" xml:space="preserve">
|
||||
<value>Invalid username or password. Please try again.</value>
|
||||
<value>Nombre de usuario o contraseña no válidos. Por favor, inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="USERNAME_REQUIRED" xml:space="preserve">
|
||||
<value>Username is required.</value>
|
||||
<value>Se requiere un nombre de usuario.</value>
|
||||
</data>
|
||||
<data name="USERNAME_ALREADY_IN_USE" xml:space="preserve">
|
||||
<value>Username is already in use.</value>
|
||||
<value>El nombre de usuario ya está en uso.</value>
|
||||
</data>
|
||||
<data name="USERNAME_AVAILABLE" xml:space="preserve">
|
||||
<value>Username is available.</value>
|
||||
<value>El nombre de usuario está disponible.</value>
|
||||
</data>
|
||||
<data name="USERNAME_MISMATCH" xml:space="preserve">
|
||||
<value>Username does not match the current user.</value>
|
||||
<value>El nombre de usuario no coincide con el usuario actual.</value>
|
||||
</data>
|
||||
<data name="PASSWORD_MISMATCH" xml:space="preserve">
|
||||
<value>The provided password does not match your current password.</value>
|
||||
<value>La contraseña proporcionada no coincide con su contraseña actual.</value>
|
||||
</data>
|
||||
<data name="ACCOUNT_SUCCESSFULLY_DELETED" xml:space="preserve">
|
||||
<value>Account successfully deleted.</value>
|
||||
<value>Cuenta eliminada con éxito.</value>
|
||||
</data>
|
||||
<data name="USERNAME_EMPTY_OR_WHITESPACE" xml:space="preserve">
|
||||
<value>Username cannot be empty or whitespace.</value>
|
||||
<value>El nombre de usuario no puede estar vacío o en blanco.</value>
|
||||
</data>
|
||||
<data name="USERNAME_TOO_SHORT" xml:space="preserve">
|
||||
<value>Username too short: must be at least 3 characters long.</value>
|
||||
<value>Nombre de usuario demasiado corto: debe tener al menos 3 caracteres.</value>
|
||||
</data>
|
||||
<data name="USERNAME_TOO_LONG" xml:space="preserve">
|
||||
<value>Username too long: cannot be longer than 40 characters.</value>
|
||||
<value>Nombre de usuario demasiado largo: no puede tener más de 40 caracteres.</value>
|
||||
</data>
|
||||
<data name="USERNAME_INVALID_EMAIL" xml:space="preserve">
|
||||
<value>Invalid email address.</value>
|
||||
<value>Dirección de correo electrónico inválida.</value>
|
||||
</data>
|
||||
<data name="USERNAME_INVALID_CHARACTERS" xml:space="preserve">
|
||||
<value>Username is invalid, can only contain letters or digits.</value>
|
||||
<value>El nombre de usuario inválido, solo puede contener letras o dígitos.</value>
|
||||
</data>
|
||||
<data name="PENDING_MIGRATIONS" xml:space="preserve">
|
||||
<value>There are pending migrations. Please run 'dotnet ef database update' to apply them.</value>
|
||||
<value>Hay migraciones pendientes. Ejecute 'dotnet ef database update' para aplicarlas.</value>
|
||||
</data>
|
||||
<data name="INTERNAL_SERVER_ERROR" xml:space="preserve">
|
||||
<value>Internal server error</value>
|
||||
<value>Error interno del servidor</value>
|
||||
</data>
|
||||
<data name="VAULT_ERROR" xml:space="preserve">
|
||||
<value>The local vault is not up-to-date. Please synchronize your vault by refreshing the page and try again.</value>
|
||||
<value>La bóveda local no está actualizada. Por favor, sincronice su bóveda actualizando la página e inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="UNKNOWN_ERROR" xml:space="preserve">
|
||||
<value>An unknown error occurred. Please try again.</value>
|
||||
<value>Se ha producido un error desconocido. Por favor, inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="INVALID_AUTHENTICATOR_CODE" xml:space="preserve">
|
||||
<value>Invalid authenticator code. Please try again.</value>
|
||||
<value>Código de autenticación inválido. Por favor, inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="INVALID_RECOVERY_CODE" xml:space="preserve">
|
||||
<value>Invalid recovery code. Please try again.</value>
|
||||
<value>Código de recuperación inválido. Por favor, inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="VAULT_NOT_UP_TO_DATE" xml:space="preserve">
|
||||
<value>Your vault is not up-to-date. Please synchronize your vault and try again.</value>
|
||||
<value>Su bóveda no está actualizada. Por favor, sincronice su bóveda e inténtelo de nuevo.</value>
|
||||
</data>
|
||||
<data name="ACCOUNT_LOCKED" xml:space="preserve">
|
||||
<value>You have entered an incorrect password too many times and your account has now been locked out. You can try again in 30 minutes.</value>
|
||||
<value>Ha introducido una contraseña incorrecta demasiadas veces y su cuenta ha sido bloqueada. Puede intentarlo de nuevo en 30 minutos.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -60,114 +60,114 @@
|
||||
</resheader>
|
||||
<!-- Page titles -->
|
||||
<data name="PageTitle" xml:space="preserve">
|
||||
<value>Log in to AliasVault</value>
|
||||
<value>Iniciar sesión en AliasVault</value>
|
||||
<comment>Main login page title</comment>
|
||||
</data>
|
||||
<data name="TwoFactorAuthenticationTitle" xml:space="preserve">
|
||||
<value>Two-factor authentication</value>
|
||||
<value>Autenticación de doble factor</value>
|
||||
<comment>Title for 2FA step</comment>
|
||||
</data>
|
||||
<data name="RecoveryCodeVerificationTitle" xml:space="preserve">
|
||||
<value>Recovery code verification</value>
|
||||
<value>Verificación de código de recuperación</value>
|
||||
<comment>Title for recovery code step</comment>
|
||||
</data>
|
||||
<!-- Form labels -->
|
||||
<data name="UsernameOrEmailLabel" xml:space="preserve">
|
||||
<value>Your username or email</value>
|
||||
<value>Tu nombre de usuario o email</value>
|
||||
<comment>Label for username/email input field</comment>
|
||||
</data>
|
||||
<data name="PasswordLabel" xml:space="preserve">
|
||||
<value>Your password</value>
|
||||
<value>Tu contraseña</value>
|
||||
<comment>Label for password input field</comment>
|
||||
</data>
|
||||
<data name="AuthenticatorCodeLabel" xml:space="preserve">
|
||||
<value>Authenticator code</value>
|
||||
<value>Código de autentificación</value>
|
||||
<comment>Label for 2FA code input field</comment>
|
||||
</data>
|
||||
<data name="RecoveryCodeLabel" xml:space="preserve">
|
||||
<value>Recovery Code</value>
|
||||
<value>Código de recuperación</value>
|
||||
<comment>Label for recovery code input field</comment>
|
||||
</data>
|
||||
<data name="RememberMeLabel" xml:space="preserve">
|
||||
<value>Remember me</value>
|
||||
<value>Recordar mis datos</value>
|
||||
<comment>Label for remember me checkbox</comment>
|
||||
</data>
|
||||
<data name="RememberMachineLabel" xml:space="preserve">
|
||||
<value>Remember this machine</value>
|
||||
<value>Recordar este equipo</value>
|
||||
<comment>Label for remember machine checkbox</comment>
|
||||
</data>
|
||||
<!-- Buttons -->
|
||||
<data name="LoginButton" xml:space="preserve">
|
||||
<value>Log in</value>
|
||||
<value>Iniciar sesión</value>
|
||||
<comment>Login button text</comment>
|
||||
</data>
|
||||
<!-- Links -->
|
||||
<data name="LostPasswordLink" xml:space="preserve">
|
||||
<value>Lost Password?</value>
|
||||
<value>¿Perdiste contraseña?</value>
|
||||
<comment>Link text for password recovery</comment>
|
||||
</data>
|
||||
<data name="CreateNewVaultLink" xml:space="preserve">
|
||||
<value>Create new vault</value>
|
||||
<value>Crear nueva bóveda</value>
|
||||
<comment>Link text for creating a new vault</comment>
|
||||
</data>
|
||||
<data name="LoginWithRecoveryCodeLink" xml:space="preserve">
|
||||
<value>Log in with a recovery code instead.</value>
|
||||
<value>Iniciar sesión con un código de recuperación en su lugar.</value>
|
||||
<comment>Link text for logging in with recovery code</comment>
|
||||
</data>
|
||||
<data name="LoginWithAuthenticatorLink" xml:space="preserve">
|
||||
<value>Log in with an authenticator code instead.</value>
|
||||
<value>Iniciar sesión con un código de autenticación en su lugar.</value>
|
||||
<comment>Link text for logging in with authenticator</comment>
|
||||
</data>
|
||||
<data name="MobileDeviceLink" xml:space="preserve">
|
||||
<value>Log in using Mobile App</value>
|
||||
<value>Iniciar sesión con la aplicación móvil</value>
|
||||
<comment>Link text for mobile device login</comment>
|
||||
</data>
|
||||
<!-- Descriptions and help text -->
|
||||
<data name="TwoFactorAuthenticationDescription" xml:space="preserve">
|
||||
<value>Your login is protected with an authenticator app. Enter your authenticator code below.</value>
|
||||
<value>Su inicio de sesión está protegido con una aplicación de autenticación. Introduzca su código de autenticación abajo.</value>
|
||||
<comment>Description for 2FA step</comment>
|
||||
</data>
|
||||
<data name="RecoveryCodeDescription" xml:space="preserve">
|
||||
<value>You have requested to log in with a recovery code. A recovery code is a one-time code that can be used to log in to your account. Note that if you don't manually disable 2FA after login, you will be asked for an authenticator code again at the next login.</value>
|
||||
<value>Ha solicitado iniciar sesión con un código de recuperación. Un código de recuperación es un código de una sola vez que se puede utilizar para iniciar sesión en su cuenta. Tenga en cuenta que si no desactiva manualmente 2FA después de iniciar sesión, se le pedirá un código de autenticación de nuevo en el próximo inicio de sesión.</value>
|
||||
<comment>Description for recovery code step</comment>
|
||||
</data>
|
||||
<data name="NoAccountYetText" xml:space="preserve">
|
||||
<value>No account yet?</value>
|
||||
<value>¿Sin cuenta todavía?</value>
|
||||
<comment>Text before registration link</comment>
|
||||
</data>
|
||||
<data name="DontHaveAuthenticatorText" xml:space="preserve">
|
||||
<value>Don't have access to your authenticator device?</value>
|
||||
<value>¿No tienes acceso a tu dispositivo de autenticación?</value>
|
||||
<comment>Question text about not having access to authenticator</comment>
|
||||
</data>
|
||||
<data name="RegainedAccessText" xml:space="preserve">
|
||||
<value>Regained access to your authenticator device?</value>
|
||||
<value>¿Acceso recuperado al dispositivo de autenticación?</value>
|
||||
<comment>Question text about regaining access to authenticator</comment>
|
||||
</data>
|
||||
<!-- Loading and status messages -->
|
||||
<data name="LoggingInMessage" xml:space="preserve">
|
||||
<value>Logging in...</value>
|
||||
<value>Iniciando sesión...</value>
|
||||
<comment>Loading message during login process</comment>
|
||||
</data>
|
||||
<data name="VerifyingTwoFactorCodeMessage" xml:space="preserve">
|
||||
<value>Verifying 2FA code...</value>
|
||||
<value>Verificando código 2FA...</value>
|
||||
<comment>Loading message during 2FA verification</comment>
|
||||
</data>
|
||||
<data name="VerifyingRecoveryCodeMessage" xml:space="preserve">
|
||||
<value>Verifying recovery code...</value>
|
||||
<value>Verificando código de recuperación...</value>
|
||||
<comment>Loading message during recovery code verification</comment>
|
||||
</data>
|
||||
<!-- Error messages -->
|
||||
<data name="LoginErrorMessage" xml:space="preserve">
|
||||
<value>An error occurred while processing the login request. Try again (later).</value>
|
||||
<value>Se ha producido un error al procesar la solicitud de acceso. Inténtalo de nuevo (más tarde).</value>
|
||||
<comment>Generic login error message</comment>
|
||||
</data>
|
||||
<data name="LoginRequestErrorMessage" xml:space="preserve">
|
||||
<value>An error occurred while processing the login request.</value>
|
||||
<value>Se ha producido un error al procesar la solicitud de acceso.</value>
|
||||
<comment>Login request processing error message</comment>
|
||||
</data>
|
||||
<!-- Placeholders -->
|
||||
<data name="UsernamePlaceholder" xml:space="preserve">
|
||||
<value>name / name@company.com</value>
|
||||
<value>nombre / nombre@empresa.com</value>
|
||||
<comment>Placeholder text for username input</comment>
|
||||
</data>
|
||||
<data name="PasswordPlaceholder" xml:space="preserve">
|
||||
|
||||
@@ -60,57 +60,57 @@
|
||||
</resheader>
|
||||
<!-- Page title -->
|
||||
<data name="PageTitle" xml:space="preserve">
|
||||
<value>Create a new AliasVault account</value>
|
||||
<value>Crear una nueva cuenta de AliasVault</value>
|
||||
<comment>Main registration page title</comment>
|
||||
</data>
|
||||
<!-- Form labels -->
|
||||
<data name="UsernameOrEmailLabel" xml:space="preserve">
|
||||
<value>Your username or email</value>
|
||||
<value>Tu nombre de usuario o email</value>
|
||||
<comment>Label for username/email input field</comment>
|
||||
</data>
|
||||
<data name="PasswordLabel" xml:space="preserve">
|
||||
<value>Your password</value>
|
||||
<value>Tu contraseña</value>
|
||||
<comment>Label for password input field</comment>
|
||||
</data>
|
||||
<data name="ConfirmPasswordLabel" xml:space="preserve">
|
||||
<value>Confirm password</value>
|
||||
<value>Confirmar contraseña</value>
|
||||
<comment>Label for password confirmation input field</comment>
|
||||
</data>
|
||||
<!-- Terms and conditions -->
|
||||
<data name="AcceptTermsLabel" xml:space="preserve">
|
||||
<value>I accept the</value>
|
||||
<value>Acepto los</value>
|
||||
<comment>Text before terms and conditions link</comment>
|
||||
</data>
|
||||
<data name="TermsAndConditionsLink" xml:space="preserve">
|
||||
<value>Terms and Conditions</value>
|
||||
<value>Términos y Condiciones</value>
|
||||
<comment>Link text for terms and conditions</comment>
|
||||
</data>
|
||||
<!-- Buttons -->
|
||||
<data name="CreateAccountButton" xml:space="preserve">
|
||||
<value>Create account</value>
|
||||
<value>Crear cuenta</value>
|
||||
<comment>Create account button text</comment>
|
||||
</data>
|
||||
<!-- Links -->
|
||||
<data name="AlreadyRegisteredText" xml:space="preserve">
|
||||
<value>Already registered?</value>
|
||||
<value>¿Ya estás registrado?</value>
|
||||
<comment>Text before login link</comment>
|
||||
</data>
|
||||
<data name="LoginHereLink" xml:space="preserve">
|
||||
<value>Login here</value>
|
||||
<value>Iniciar sesión aquí</value>
|
||||
<comment>Link text for login page</comment>
|
||||
</data>
|
||||
<!-- Loading and status messages -->
|
||||
<data name="CreatingAccountMessage" xml:space="preserve">
|
||||
<value>Creating account...</value>
|
||||
<value>Creando cuenta...</value>
|
||||
<comment>Loading message during account creation</comment>
|
||||
</data>
|
||||
<data name="RegistrationErrorMessage" xml:space="preserve">
|
||||
<value>An error occurred during registration.</value>
|
||||
<value>Se ha producido un error durante el registro.</value>
|
||||
<comment>Generic registration error message</comment>
|
||||
</data>
|
||||
<!-- Placeholders -->
|
||||
<data name="UsernamePlaceholder" xml:space="preserve">
|
||||
<value>name / name@company.com</value>
|
||||
<value>nombre / nombre@empresa.com</value>
|
||||
<comment>Placeholder text for username input</comment>
|
||||
</data>
|
||||
<data name="PasswordPlaceholder" xml:space="preserve">
|
||||
|
||||
@@ -20,59 +20,59 @@
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<data name="WelcomeMessage">
|
||||
<value>Great! Now, let's set up your master password for AliasVault.</value>
|
||||
<value>¡Genial! Ahora, vamos a configurar su contraseña maestra para AliasVault.</value>
|
||||
<comment>Assistant welcome message for password setup step</comment>
|
||||
</data>
|
||||
<data name="ImportantNote">
|
||||
<value>Important: This master password will be used to encrypt your vault. It should be a long, complex string that you can remember. If you forget this password, your data will be permanently inaccessible.</value>
|
||||
<value>Importante: Esta contraseña maestra se utilizará para cifrar tu bóveda. Debería ser una cadena larga y compleja que puedas recordar. Si olvida esta contraseña, sus datos serán inaccesibles permanentemente.</value>
|
||||
<comment>Important warning about master password</comment>
|
||||
</data>
|
||||
<data name="SecurityPoint1">
|
||||
<value>Your master password never leaves your device</value>
|
||||
<value>Tu contraseña maestra nunca sale de tu dispositivo</value>
|
||||
<comment>First security point about password privacy</comment>
|
||||
</data>
|
||||
<data name="SecurityPoint2">
|
||||
<value>The server has no access to your unencrypted data</value>
|
||||
<value>El servidor no tiene acceso a tus datos no cifrados</value>
|
||||
<comment>Second security point about server access</comment>
|
||||
</data>
|
||||
<data name="SecurityPoint3">
|
||||
<value>Even the server admin cannot restore your access if you forget this password</value>
|
||||
<value>Incluso el administrador del servidor no puede restaurar su acceso si olvida esta contraseña</value>
|
||||
<comment>Third security point about password recovery</comment>
|
||||
</data>
|
||||
<data name="MasterPasswordLabel">
|
||||
<value>Master Password</value>
|
||||
<value>Contraseña Maestra</value>
|
||||
<comment>Label for master password field</comment>
|
||||
</data>
|
||||
<data name="MasterPasswordPlaceholder">
|
||||
<value>Enter your master password</value>
|
||||
<value>Ingrese su contraseña maestra</value>
|
||||
<comment>Placeholder for master password field</comment>
|
||||
</data>
|
||||
<data name="ConfirmMasterPasswordLabel">
|
||||
<value>Confirm Master Password</value>
|
||||
<value>Confirma tu Contraseña Maestra</value>
|
||||
<comment>Label for confirm password field</comment>
|
||||
</data>
|
||||
<data name="ConfirmMasterPasswordPlaceholder">
|
||||
<value>Confirm your master password</value>
|
||||
<value>Confirma tu contraseña maestra</value>
|
||||
<comment>Placeholder for confirm password field</comment>
|
||||
</data>
|
||||
<data name="ValidatingPasswordMessage">
|
||||
<value>Validating password...</value>
|
||||
<value>Validando contraseña...</value>
|
||||
<comment>Message shown while validating password</comment>
|
||||
</data>
|
||||
<data name="PasswordValidAndStrongMessage">
|
||||
<value>Password is valid and strong!</value>
|
||||
<value>¡Contraseña válida y fuerte!</value>
|
||||
<comment>Success message for valid password</comment>
|
||||
</data>
|
||||
<data name="PasswordTooShortError">
|
||||
<value>Master password must be at least 10 characters long.</value>
|
||||
<value>La contraseña maestra debe tener al menos 10 caracteres.</value>
|
||||
<comment>Error message for password too short</comment>
|
||||
</data>
|
||||
<data name="ConfirmPasswordPrompt">
|
||||
<value>Confirm your password by entering it again.</value>
|
||||
<value>Confirme su contraseña introduciéndola de nuevo.</value>
|
||||
<comment>Prompt to confirm password</comment>
|
||||
</data>
|
||||
<data name="PasswordsMismatchError">
|
||||
<value>Passwords do not match.</value>
|
||||
<value>Las contraseñas no coinciden.</value>
|
||||
<comment>Error message when passwords don't match</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -21,32 +21,32 @@
|
||||
</xsd:schema>
|
||||
<!-- Instructions -->
|
||||
<data name="PleaseReadAndAgree">
|
||||
<value>Please read and agree to the following terms and conditions before proceeding.</value>
|
||||
<value>Por favor, lea y acepte los siguientes términos y condiciones antes de continuar.</value>
|
||||
<comment>Instructions to read and agree to terms</comment>
|
||||
</data>
|
||||
<!-- Terms and Conditions title -->
|
||||
<data name="TermsAndConditionsTitle">
|
||||
<value>Terms and Conditions</value>
|
||||
<value>Términos y Condiciones</value>
|
||||
<comment>Title for the terms and conditions section</comment>
|
||||
</data>
|
||||
<!-- Terms content -->
|
||||
<data name="TermsContent">
|
||||
<value>AliasVault is designed to enhance your online security and protect your privacy. With AliasVault, you can create unique identities and email aliases for your various online accounts, helping you maintain control over your personal information and reduce the risk of identity theft.
|
||||
<value>AliasVault está diseñado para mejorar su seguridad en línea y proteger su privacidad. Con AliasVault, puede crear identidades únicas y alias de correo electrónico para sus diversas cuentas en línea, lo que le ayuda a mantener el control sobre su información personal y reducir el riesgo de robo de identidad.
|
||||
|
||||
By using AliasVault, you agree to the following terms:
|
||||
Al utilizar AliasVault, usted acepta los siguientes términos:
|
||||
|
||||
1. You will not use AliasVault for any illegal purposes, including but not limited to fraud, identity theft, or impersonating real individuals.
|
||||
1. No utilizará AliasVault para ningún fin ilegal, incluyendo, entre otros, el fraude, el robo de identidad o la suplantación de personas reales.
|
||||
|
||||
2. You are responsible for maintaining the confidentiality of your account and any aliases created through AliasVault.
|
||||
2. Usted es responsable de mantener la confidencialidad de su cuenta y de cualquier alias creado a través de AliasVault.
|
||||
|
||||
3. AliasVault reserves the right to terminate your account if we suspect any misuse or violation of these terms.
|
||||
3. AliasVault se reserva el derecho de cancelar su cuenta si sospechamos de cualquier uso indebido o violación de estos términos.
|
||||
|
||||
4. You understand that while AliasVault enhances your privacy, no system is completely foolproof, and you use the service at your own risk.</value>
|
||||
4. Usted entiende que, aunque AliasVault mejora su privacidad, ningún sistema es completamente infalible y utiliza el servicio bajo su propia responsabilidad.</value>
|
||||
<comment>Full terms and conditions content</comment>
|
||||
</data>
|
||||
<!-- Agreement checkbox -->
|
||||
<data name="AgreementCheckboxLabel">
|
||||
<value>I have read and agree to the Terms and Conditions</value>
|
||||
<value>He leído y acepto los Términos y Condiciones</value>
|
||||
<comment>Label for the agreement checkbox</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -21,46 +21,46 @@
|
||||
</xsd:schema>
|
||||
<!-- Assistant messages -->
|
||||
<data name="GreatNowLetsSetupUsername">
|
||||
<value>Great! Now, let's set up your username for AliasVault.</value>
|
||||
<value>¡Genial! Ahora, vamos a configurar tu nombre de usuario para AliasVault.</value>
|
||||
<comment>Assistant message about setting up username</comment>
|
||||
</data>
|
||||
<data name="EnterUsernameInstructions">
|
||||
<value>Please enter a username you'd like to use. This can be your email address or any unique name you prefer.</value>
|
||||
<value>Por favor, introduce un nombre de usuario que te gustaría usar. Esta puede ser tu dirección email o cualquier nombre único que prefieras.</value>
|
||||
<comment>Instructions for entering username</comment>
|
||||
</data>
|
||||
<data name="RememberUsernameNote">
|
||||
<value>Remember: This is what you'll use to log in later, so make sure it's something you'll remember!</value>
|
||||
<value>Recuerde: ¡Esto es lo que usarás para iniciar sesión más tarde, así que asegúrate de que es algo que recordarás!</value>
|
||||
<comment>Important note about remembering username</comment>
|
||||
</data>
|
||||
<!-- Form labels -->
|
||||
<data name="UsernameLabel">
|
||||
<value>Username</value>
|
||||
<value>Usuario</value>
|
||||
<comment>Label for username input field</comment>
|
||||
</data>
|
||||
<data name="UsernamePlaceholder">
|
||||
<value>Enter your desired username or email</value>
|
||||
<value>Introduce tu nombre de usuario o email</value>
|
||||
<comment>Placeholder text for username input</comment>
|
||||
</data>
|
||||
<!-- Validation messages -->
|
||||
<data name="ValidatingUsernameMessage">
|
||||
<value>Validating username...</value>
|
||||
<value>Validando nombre de usuario...</value>
|
||||
<comment>Message shown while validating username</comment>
|
||||
</data>
|
||||
<data name="UsernameAvailableMessage">
|
||||
<value>Username is available!</value>
|
||||
<value>¡El nombre de usuario está disponible!</value>
|
||||
<comment>Message shown when username is available</comment>
|
||||
</data>
|
||||
<data name="UsernameRequiredError">
|
||||
<value>Username is required.</value>
|
||||
<value>Se requiere un nombre de usuario.</value>
|
||||
<comment>Error message when username is empty</comment>
|
||||
</data>
|
||||
<data name="ServerCommunicationError">
|
||||
<value>An error occurred during communication with the AliasVault server.</value>
|
||||
<value>Se ha producido un error durante la comunicación con el servidor AliasVault.</value>
|
||||
<comment>Error message for server communication issues</comment>
|
||||
</data>
|
||||
<!-- Alt text -->
|
||||
<data name="AssistantAvatarAlt">
|
||||
<value>AliasVault Assistant</value>
|
||||
<value>Asistente de AliasVault</value>
|
||||
<comment>Alt text for assistant avatar image</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,7 +59,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ClearClipboardButton" xml:space="preserve">
|
||||
<value>Clear Clipboard</value>
|
||||
<value>Limpiar portapapeles</value>
|
||||
<comment>Button text to manually clear clipboard immediately</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -59,55 +59,55 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="TwoFactorAuthenticationTitle" xml:space="preserve">
|
||||
<value>Two-factor authentication</value>
|
||||
<value>Autenticación de doble factor</value>
|
||||
<comment>Section title for 2FA codes</comment>
|
||||
</data>
|
||||
<data name="AddTotpCodeButton" xml:space="preserve">
|
||||
<value>Add TOTP Code</value>
|
||||
<value>Añadir código TOTP</value>
|
||||
<comment>Button text to add new TOTP code</comment>
|
||||
</data>
|
||||
<data name="AddTotpCodeDescription" xml:space="preserve">
|
||||
<value>Add a two-factor authenticator code</value>
|
||||
<value>Añadir un código de autenticación de doble factor</value>
|
||||
<comment>Description for adding TOTP codes</comment>
|
||||
</data>
|
||||
<data name="AddTotpCodeModalTitle" xml:space="preserve">
|
||||
<value>Add 2FA TOTP Code</value>
|
||||
<value>Añadir código TOTP 2FA</value>
|
||||
<comment>Modal title for adding TOTP code</comment>
|
||||
</data>
|
||||
<data name="CloseFormButton" xml:space="preserve">
|
||||
<value>Close form</value>
|
||||
<value>Cerrar formulario</value>
|
||||
<comment>Button to close the add TOTP form</comment>
|
||||
</data>
|
||||
<data name="TotpInstructions" xml:space="preserve">
|
||||
<value>If the website offers or requires 2FA for your account, copy the secret key or QR code URI and paste it below.</value>
|
||||
<value>Si el sitio web ofrece o requiere 2FA para su cuenta, copie la clave secreta o el código QR URI y péguelo a continuación.</value>
|
||||
<comment>Instructions for adding TOTP codes</comment>
|
||||
</data>
|
||||
<data name="NameOptionalLabel" xml:space="preserve">
|
||||
<value>Name (optional)</value>
|
||||
<value>Nombre (Opcional)</value>
|
||||
<comment>Label for optional name field</comment>
|
||||
</data>
|
||||
<data name="SecretKeyLabel" xml:space="preserve">
|
||||
<value>Secret Key</value>
|
||||
<value>Clave secreta</value>
|
||||
<comment>Label for secret key field</comment>
|
||||
</data>
|
||||
<data name="SecretKeyPlaceholder" xml:space="preserve">
|
||||
<value>Enter secret key (manual entry)</value>
|
||||
<value>Introducir clave secreta (entrada manual)</value>
|
||||
<comment>Placeholder text for secret key input</comment>
|
||||
</data>
|
||||
<data name="SaveButton" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
<value>Guardar</value>
|
||||
<comment>Save button text</comment>
|
||||
</data>
|
||||
<data name="SaveToViewCodeMessage" xml:space="preserve">
|
||||
<value>Save to view code</value>
|
||||
<value>Guardar para ver el código</value>
|
||||
<comment>Message shown for unsaved TOTP codes</comment>
|
||||
</data>
|
||||
<data name="DeleteTotpCodeConfirmation" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this TOTP code?</value>
|
||||
<value>¿Está seguro que desea eliminar este código TOTP?</value>
|
||||
<comment>Confirmation message for deleting TOTP code</comment>
|
||||
</data>
|
||||
<data name="DeleteTotpCodeTitle" xml:space="preserve">
|
||||
<value>Delete TOTP code</value>
|
||||
<value>Eliminar código TOTP</value>
|
||||
<comment>Title for delete TOTP code action</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,15 +59,15 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="TwoFactorAuthenticationTitle" xml:space="preserve">
|
||||
<value>Two-factor authentication</value>
|
||||
<value>Autenticación de doble factor</value>
|
||||
<comment>Section title for 2FA codes</comment>
|
||||
</data>
|
||||
<data name="NoTotpCodesMessage" xml:space="preserve">
|
||||
<value>No two-factor authenticator codes available</value>
|
||||
<value>No hay códigos de autenticación de doble factor disponibles</value>
|
||||
<comment>Message shown when no TOTP codes are configured</comment>
|
||||
</data>
|
||||
<data name="CopiedMessage" xml:space="preserve">
|
||||
<value>Copied!</value>
|
||||
<value>¡Copiado!</value>
|
||||
<comment>Feedback message when TOTP code is copied to clipboard</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,63 +59,63 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="FromLabel" xml:space="preserve">
|
||||
<value>From:</value>
|
||||
<value>De:</value>
|
||||
<comment>Email sender field label</comment>
|
||||
</data>
|
||||
<data name="ToLabel" xml:space="preserve">
|
||||
<value>To:</value>
|
||||
<value>Para:</value>
|
||||
<comment>Email recipient field label</comment>
|
||||
</data>
|
||||
<data name="DateLabel" xml:space="preserve">
|
||||
<value>Date:</value>
|
||||
<value>Fecha:</value>
|
||||
<comment>Email date field label</comment>
|
||||
</data>
|
||||
<data name="ActionsLabel" xml:space="preserve">
|
||||
<value>Actions:</value>
|
||||
<value>Acciones:</value>
|
||||
<comment>Email actions section label</comment>
|
||||
</data>
|
||||
<data name="DeleteButton" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
<value>Eliminar</value>
|
||||
<comment>Delete email button text</comment>
|
||||
</data>
|
||||
<data name="AttachmentsLabel" xml:space="preserve">
|
||||
<value>Attachments:</value>
|
||||
<value>Archivos adjuntos:</value>
|
||||
<comment>Email attachments section header</comment>
|
||||
</data>
|
||||
<data name="CloseButton" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
<value>Cerrar</value>
|
||||
<comment>Close modal button text</comment>
|
||||
</data>
|
||||
<data name="DeleteEmailTitle" xml:space="preserve">
|
||||
<value>Delete Email</value>
|
||||
<value>Eliminar Email</value>
|
||||
<comment>Delete email confirmation dialog title</comment>
|
||||
</data>
|
||||
<data name="DeleteEmailConfirmation" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this email? This action cannot be undone.</value>
|
||||
<value>¿Está seguro que desea eliminar este correo electrónico? Esta acción no se puede deshacer.</value>
|
||||
<comment>Delete email confirmation message</comment>
|
||||
</data>
|
||||
<data name="EmailDeletedSuccess" xml:space="preserve">
|
||||
<value>Email deleted successfully</value>
|
||||
<value>Correo eliminado exitosamente</value>
|
||||
<comment>Success message when email is deleted</comment>
|
||||
</data>
|
||||
<data name="EmailDeleteFailed" xml:space="preserve">
|
||||
<value>Failed to delete email</value>
|
||||
<value>Error al eliminar el correo</value>
|
||||
<comment>Error message when email deletion fails</comment>
|
||||
</data>
|
||||
<data name="GenericError" xml:space="preserve">
|
||||
<value>An error occurred</value>
|
||||
<value>Ha ocurrido un error</value>
|
||||
<comment>Generic error message</comment>
|
||||
</data>
|
||||
<data name="NoEmailBody" xml:space="preserve">
|
||||
<value>[This email has no body.]</value>
|
||||
<value>[Este email no tiene cuerpo.]</value>
|
||||
<comment>Message shown when email has no content</comment>
|
||||
</data>
|
||||
<data name="AttachmentDownloadFailed" xml:space="preserve">
|
||||
<value>Failed to download attachment</value>
|
||||
<value>Error al descargar el archivo adjunto</value>
|
||||
<comment>Error message when attachment download fails</comment>
|
||||
</data>
|
||||
<data name="AttachmentDownloadError" xml:space="preserve">
|
||||
<value>Error downloading attachment</value>
|
||||
<value>Error al descargar el archivo adjunto</value>
|
||||
<comment>Error message for attachment download error</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,63 +59,63 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="FromLabel" xml:space="preserve">
|
||||
<value>From:</value>
|
||||
<value>De:</value>
|
||||
<comment>Email sender field label</comment>
|
||||
</data>
|
||||
<data name="ToLabel" xml:space="preserve">
|
||||
<value>To:</value>
|
||||
<value>Para:</value>
|
||||
<comment>Email recipient field label</comment>
|
||||
</data>
|
||||
<data name="DateLabel" xml:space="preserve">
|
||||
<value>Date:</value>
|
||||
<value>Fecha:</value>
|
||||
<comment>Email date field label</comment>
|
||||
</data>
|
||||
<data name="CredentialLabel" xml:space="preserve">
|
||||
<value>Credential:</value>
|
||||
<value>Credencial:</value>
|
||||
<comment>Email credential field label</comment>
|
||||
</data>
|
||||
<data name="NoneValue" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<value>Nada</value>
|
||||
<comment>No credential assigned value</comment>
|
||||
</data>
|
||||
<data name="AttachmentsLabel" xml:space="preserve">
|
||||
<value>Attachments:</value>
|
||||
<value>Archivos adjuntos:</value>
|
||||
<comment>Email attachments section header</comment>
|
||||
</data>
|
||||
<data name="SelectEmailMessage" xml:space="preserve">
|
||||
<value>Select an email to view its contents</value>
|
||||
<value>Seleccione un email para ver su contenido</value>
|
||||
<comment>Empty state message when no email is selected</comment>
|
||||
</data>
|
||||
<data name="DeleteEmailTitle" xml:space="preserve">
|
||||
<value>Delete Email</value>
|
||||
<value>Eliminar Email</value>
|
||||
<comment>Delete email confirmation dialog title</comment>
|
||||
</data>
|
||||
<data name="DeleteEmailConfirmation" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this email? This action cannot be undone.</value>
|
||||
<value>¿Está seguro que desea eliminar este correo? Esta acción no se puede deshacer.</value>
|
||||
<comment>Delete email confirmation message</comment>
|
||||
</data>
|
||||
<data name="EmailDeletedSuccess" xml:space="preserve">
|
||||
<value>Email deleted successfully</value>
|
||||
<value>Correo eliminado correctamente</value>
|
||||
<comment>Success message when email is deleted</comment>
|
||||
</data>
|
||||
<data name="EmailDeleteFailed" xml:space="preserve">
|
||||
<value>Failed to delete email</value>
|
||||
<value>Error al eliminar el correo</value>
|
||||
<comment>Error message when email deletion fails</comment>
|
||||
</data>
|
||||
<data name="GenericError" xml:space="preserve">
|
||||
<value>An error occurred</value>
|
||||
<value>Ha ocurrido un error</value>
|
||||
<comment>Generic error message</comment>
|
||||
</data>
|
||||
<data name="NoEmailBody" xml:space="preserve">
|
||||
<value>[This email has no body.]</value>
|
||||
<value>[Este email no tiene cuerpo.]</value>
|
||||
<comment>Message shown when email has no content</comment>
|
||||
</data>
|
||||
<data name="AttachmentDownloadFailed" xml:space="preserve">
|
||||
<value>Failed to download attachment</value>
|
||||
<value>Error al descargar el archivo adjunto</value>
|
||||
<comment>Error message when attachment download fails</comment>
|
||||
</data>
|
||||
<data name="AttachmentDownloadError" xml:space="preserve">
|
||||
<value>Error downloading attachment</value>
|
||||
<value>Error descargando archivo adjunto</value>
|
||||
<comment>Error message for attachment download error</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,7 +59,7 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="NewEmailTooltip" xml:space="preserve">
|
||||
<value>New email</value>
|
||||
<value>Nuevo correo</value>
|
||||
<comment>Tooltip text for new email indicator</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,35 +59,35 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="EmailSectionTitle" xml:space="preserve">
|
||||
<value>Email</value>
|
||||
<value>Correo</value>
|
||||
<comment>Section title for email panel</comment>
|
||||
</data>
|
||||
<data name="AutoRefreshEnabledTooltip" xml:space="preserve">
|
||||
<value>Auto-refresh enabled</value>
|
||||
<value>Auto-refrescar activado</value>
|
||||
<comment>Tooltip for auto-refresh indicator</comment>
|
||||
</data>
|
||||
<data name="SubjectColumn" xml:space="preserve">
|
||||
<value>Subject</value>
|
||||
<value>Asunto</value>
|
||||
<comment>Table column header for email subject</comment>
|
||||
</data>
|
||||
<data name="DateColumn" xml:space="preserve">
|
||||
<value>Date</value>
|
||||
<value>Fecha</value>
|
||||
<comment>Table column header for email date</comment>
|
||||
</data>
|
||||
<data name="NoEmailsReceivedMessage" xml:space="preserve">
|
||||
<value>No emails received (yet).</value>
|
||||
<value>No se han recibido correos (aún).</value>
|
||||
<comment>Message when no emails are available</comment>
|
||||
</data>
|
||||
<data name="EmailAddressInUseError" xml:space="preserve">
|
||||
<value>The current chosen email address is already in use. Please change the email address by editing this credential.</value>
|
||||
<value>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.</value>
|
||||
<comment>Error message when email address is already in use</comment>
|
||||
</data>
|
||||
<data name="EmailLoadError" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
<value>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.</value>
|
||||
<comment>Error message when email loading fails</comment>
|
||||
</data>
|
||||
<data name="LoadMoreButton" xml:space="preserve">
|
||||
<value>Load more</value>
|
||||
<value>Cargar más</value>
|
||||
<comment>Button text to load more emails</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,31 +59,31 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="UseDomainChooserButton" xml:space="preserve">
|
||||
<value>Use domain chooser</value>
|
||||
<value>Usar selector de dominio</value>
|
||||
<comment>Button to switch back to domain chooser</comment>
|
||||
</data>
|
||||
<data name="EnterCustomDomainButton" xml:space="preserve">
|
||||
<value>Enter custom domain</value>
|
||||
<value>Introduzca dominio personalizado</value>
|
||||
<comment>Button to switch to custom domain entry</comment>
|
||||
</data>
|
||||
<data name="SelectEmailDomainTitle" xml:space="preserve">
|
||||
<value>Select Email Domain</value>
|
||||
<value>Seleccionar dominio de correo</value>
|
||||
<comment>Title of domain selection popup</comment>
|
||||
</data>
|
||||
<data name="PrivateEmailTitle" xml:space="preserve">
|
||||
<value>Private Email (AliasVault server)</value>
|
||||
<value>Correo privado (servidor AliasVault)</value>
|
||||
<comment>Title for private email domains section</comment>
|
||||
</data>
|
||||
<data name="PrivateEmailDescription" xml:space="preserve">
|
||||
<value>E2E encrypted, fully private.</value>
|
||||
<value>E2E cifrado, totalmente privado.</value>
|
||||
<comment>Description of private email domains</comment>
|
||||
</data>
|
||||
<data name="PublicEmailTitle" xml:space="preserve">
|
||||
<value>Public Temp Email Providers</value>
|
||||
<value>Proveedores de Correo Temporal Públicos</value>
|
||||
<comment>Title for public email domains section</comment>
|
||||
</data>
|
||||
<data name="PublicEmailDescription" xml:space="preserve">
|
||||
<value>Anonymous but limited privacy. Email content is readable by anyone that knows the address.</value>
|
||||
<value>Privacidad anónima pero limitada. Contenido de correo electrónico puede ser leído por cualquiera que conozca la dirección.</value>
|
||||
<comment>Description of public email domains</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -59,83 +59,83 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="PageTitle" xml:space="preserve">
|
||||
<value>Reset Vault</value>
|
||||
<value>Restablecer bóveda</value>
|
||||
<comment>Page title</comment>
|
||||
</data>
|
||||
<data name="BreadcrumbImportExport" xml:space="preserve">
|
||||
<value>Import / Export</value>
|
||||
<value>Importar / Exportar</value>
|
||||
<comment>Breadcrumb for import export</comment>
|
||||
</data>
|
||||
<data name="BreadcrumbResetVault" xml:space="preserve">
|
||||
<value>Reset Vault</value>
|
||||
<value>Restablecer bóveda</value>
|
||||
<comment>Breadcrumb for reset vault</comment>
|
||||
</data>
|
||||
<data name="ResetVaultPleaseNote" xml:space="preserve">
|
||||
<value>Please note:</value>
|
||||
<value>Ten en cuenta:</value>
|
||||
<comment>Reset vault please note prefix</comment>
|
||||
</data>
|
||||
<data name="ResetVaultCredentialsDeletedNote" xml:space="preserve">
|
||||
<value>All encrypted credentials in your vault will be permanently deleted</value>
|
||||
<value>Todas las credenciales cifradas en tu bóveda serán eliminadas permanentemente</value>
|
||||
<comment>Reset vault note about credentials being deleted</comment>
|
||||
</data>
|
||||
<data name="ResetVaultEmailAliasesKeptNote" xml:space="preserve">
|
||||
<value>Your email aliases will be preserved and can be re-used after resetting your vault</value>
|
||||
<value>Sus alias de correo electrónico serán conservados y pueden ser reutilizados después de restablecer su bóveda</value>
|
||||
<comment>Reset vault note about email aliases being kept</comment>
|
||||
</data>
|
||||
<data name="ResetVaultSettingsKeptNote" xml:space="preserve">
|
||||
<value>Your account settings and preferences will be preserved</value>
|
||||
<value>Se conservarán los ajustes y preferencias de su cuenta</value>
|
||||
<comment>Reset vault note about settings being kept</comment>
|
||||
</data>
|
||||
<data name="ResetVaultIrreversibleNote" xml:space="preserve">
|
||||
<value>This action cannot be undone</value>
|
||||
<value>Esta acción no se puede deshacer</value>
|
||||
<comment>Reset vault note about action being irreversible</comment>
|
||||
</data>
|
||||
<data name="ResetVaultConfirmUsernameLabel" xml:space="preserve">
|
||||
<value>To continue, please type your username to confirm</value>
|
||||
<value>Para continuar, por favor escribe tu nombre de usuario para confirmar</value>
|
||||
<comment>Reset vault username confirmation label</comment>
|
||||
</data>
|
||||
<data name="ResetVaultContinueButton" xml:space="preserve">
|
||||
<value>Continue with vault reset</value>
|
||||
<value>Continuar con el restablecimiento de bóveda</value>
|
||||
<comment>Reset vault continue button</comment>
|
||||
</data>
|
||||
<data name="ResetVaultFinalWarning" xml:space="preserve">
|
||||
<value>Final warning: You are about to permanently delete all your credentials!</value>
|
||||
<value>Advertencia final: ¡Estás a punto de eliminar permanentemente todas tus credenciales!</value>
|
||||
<comment>Reset vault final warning message</comment>
|
||||
</data>
|
||||
<data name="ResetVaultDeletionIrreversibleNote" xml:space="preserve">
|
||||
<value>This deletion is irreversible and cannot be undone</value>
|
||||
<value>Esta acción es irreversible y no se puede deshacer</value>
|
||||
<comment>Reset vault final step irreversible note</comment>
|
||||
</data>
|
||||
<data name="ResetVaultEnterPasswordLabel" xml:space="preserve">
|
||||
<value>Enter your password to confirm</value>
|
||||
<value>Introduce tu contraseña para confirmar</value>
|
||||
<comment>Reset vault password confirmation label</comment>
|
||||
</data>
|
||||
<data name="ResetVaultConfirmButton" xml:space="preserve">
|
||||
<value>Reset my vault</value>
|
||||
<value>Restablecer bóveda</value>
|
||||
<comment>Reset vault final confirmation button</comment>
|
||||
</data>
|
||||
<data name="ResetVaultUsernameRequired" xml:space="preserve">
|
||||
<value>Username is required.</value>
|
||||
<value>Se requiere un nombre de usuario.</value>
|
||||
<comment>Reset vault username required error</comment>
|
||||
</data>
|
||||
<data name="ResetVaultUsernameDoesNotMatch" xml:space="preserve">
|
||||
<value>The username you entered does not match your account username.</value>
|
||||
<value>El nombre de usuario introducido no coincide con el nombre de usuario de su cuenta.</value>
|
||||
<comment>Reset vault username mismatch error</comment>
|
||||
</data>
|
||||
<data name="ResetVaultPasswordIncorrect" xml:space="preserve">
|
||||
<value>The password you entered is incorrect.</value>
|
||||
<value>La contraseña introducida es incorrecta.</value>
|
||||
<comment>Reset vault password incorrect error</comment>
|
||||
</data>
|
||||
<data name="ResetVaultProgressMessage" xml:space="preserve">
|
||||
<value>Resetting vault...</value>
|
||||
<value>Restableciendo bóveda...</value>
|
||||
<comment>Reset vault progress message</comment>
|
||||
</data>
|
||||
<data name="ResetVaultSuccessMessage" xml:space="preserve">
|
||||
<value>Your vault has been successfully reset. All credentials have been deleted and you can now start fresh.</value>
|
||||
<value>Tu bóveda se ha restablecido correctamente. Todas las credenciales han sido eliminadas y ahora puedes empezar a actualizar.</value>
|
||||
<comment>Reset vault success message</comment>
|
||||
</data>
|
||||
<data name="ResetVaultErrorMessage" xml:space="preserve">
|
||||
<value>An error occurred while resetting your vault. Please try again.</value>
|
||||
<value>Se ha producido un error al restablecer la bóveda. Por favor, inténtelo de nuevo.</value>
|
||||
<comment>Reset vault error message</comment>
|
||||
</data>
|
||||
</root>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user