Update env variables and refactor metadata storage (#1371)
This commit is contained in:
+9
-1
@@ -37,11 +37,19 @@ FORCE_HTTPS_REDIRECT=true
|
|||||||
# your DNS. Please refer to the full documentation for more instructions on DNS:
|
# your DNS. Please refer to the full documentation for more instructions on DNS:
|
||||||
# https://docs.aliasvault.net/installation/install.html#3-email-server-setup
|
# https://docs.aliasvault.net/installation/install.html#3-email-server-setup
|
||||||
#
|
#
|
||||||
# Set the private email domains below that are allowed to be used (comma separated values).
|
# Set the private email domains below that the server should accept incoming mail for (comma separated values).
|
||||||
# Example: PRIVATE_EMAIL_DOMAINS=example.com,example2.org
|
# Example: PRIVATE_EMAIL_DOMAINS=example.com,example2.org
|
||||||
# To disable the private email domains feature, keep this empty.
|
# To disable the private email domains feature, keep this empty.
|
||||||
PRIVATE_EMAIL_DOMAINS=
|
PRIVATE_EMAIL_DOMAINS=
|
||||||
|
|
||||||
|
# Set private email domains that should be hidden from UI components (comma separated values).
|
||||||
|
# These domains will still function as private email domains for receiving email and claims,
|
||||||
|
# but will not appear in domain selection dropdowns or settings. This is useful for deprecating
|
||||||
|
# legacy domains while maintaining backwards compatibility.
|
||||||
|
# Example: HIDDEN_PRIVATE_EMAIL_DOMAINS=old-domain.com,deprecated.org
|
||||||
|
# Note: Domains listed here should ALSO be included in PRIVATE_EMAIL_DOMAINS above.
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS=
|
||||||
|
|
||||||
# Enable TLS for SMTP.
|
# Enable TLS for SMTP.
|
||||||
# ⚠️ Requires valid TLS certificates on your mail server (not provided by the AliasVault installer).
|
# ⚠️ Requires valid TLS certificates on your mail server (not provided by the AliasVault installer).
|
||||||
# If set to true without proper certificates, the SMTP service will fail to start.
|
# If set to true without proper certificates, the SMTP service will fail to start.
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ export async function handleStoreVault(
|
|||||||
await storage.setItem('session:privateEmailDomains', vaultRequest.privateEmailDomainList);
|
await storage.setItem('session:privateEmailDomains', vaultRequest.privateEmailDomainList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vaultRequest.hiddenPrivateEmailDomainList) {
|
||||||
|
await storage.setItem('session:hiddenPrivateEmailDomains', vaultRequest.hiddenPrivateEmailDomainList);
|
||||||
|
}
|
||||||
|
|
||||||
if (vaultRequest.vaultRevisionNumber) {
|
if (vaultRequest.vaultRevisionNumber) {
|
||||||
await storage.setItem('session:vaultRevisionNumber', vaultRequest.vaultRevisionNumber);
|
await storage.setItem('session:vaultRevisionNumber', vaultRequest.vaultRevisionNumber);
|
||||||
}
|
}
|
||||||
@@ -168,6 +172,7 @@ export async function handleSyncVault(
|
|||||||
{ key: 'session:encryptedVault', value: vaultResponse.vault.blob },
|
{ key: 'session:encryptedVault', value: vaultResponse.vault.blob },
|
||||||
{ key: 'session:publicEmailDomains', value: vaultResponse.vault.publicEmailDomainList },
|
{ key: 'session:publicEmailDomains', value: vaultResponse.vault.publicEmailDomainList },
|
||||||
{ key: 'session:privateEmailDomains', value: vaultResponse.vault.privateEmailDomainList },
|
{ key: 'session:privateEmailDomains', value: vaultResponse.vault.privateEmailDomainList },
|
||||||
|
{ key: 'session:hiddenPrivateEmailDomains', value: vaultResponse.vault.hiddenPrivateEmailDomainList },
|
||||||
{ key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber }
|
{ key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -186,6 +191,7 @@ export async function handleGetVault(
|
|||||||
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
|
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
|
||||||
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[];
|
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[];
|
||||||
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[];
|
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[];
|
||||||
|
const hiddenPrivateEmailDomains = await storage.getItem('session:hiddenPrivateEmailDomains') as string[] ?? [];
|
||||||
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
|
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
|
||||||
|
|
||||||
if (!encryptedVault) {
|
if (!encryptedVault) {
|
||||||
@@ -208,6 +214,7 @@ export async function handleGetVault(
|
|||||||
vault: decryptedVault,
|
vault: decryptedVault,
|
||||||
publicEmailDomains: publicEmailDomains ?? [],
|
publicEmailDomains: publicEmailDomains ?? [],
|
||||||
privateEmailDomains: privateEmailDomains ?? [],
|
privateEmailDomains: privateEmailDomains ?? [],
|
||||||
|
hiddenPrivateEmailDomains: hiddenPrivateEmailDomains ?? [],
|
||||||
vaultRevisionNumber: vaultRevisionNumber ?? 0
|
vaultRevisionNumber: vaultRevisionNumber ?? 0
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -229,6 +236,7 @@ export function handleClearVault(
|
|||||||
'session:encryptionKeyDerivationParams',
|
'session:encryptionKeyDerivationParams',
|
||||||
'session:publicEmailDomains',
|
'session:publicEmailDomains',
|
||||||
'session:privateEmailDomains',
|
'session:privateEmailDomains',
|
||||||
|
'session:hiddenPrivateEmailDomains',
|
||||||
'session:vaultRevisionNumber'
|
'session:vaultRevisionNumber'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -497,13 +505,11 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
|
|||||||
credentialsCount: sqliteClient.getAllCredentials().length,
|
credentialsCount: sqliteClient.getAllCredentials().length,
|
||||||
currentRevisionNumber: vaultRevisionNumber,
|
currentRevisionNumber: vaultRevisionNumber,
|
||||||
emailAddressList: emailAddresses,
|
emailAddressList: emailAddresses,
|
||||||
privateEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
|
|
||||||
publicEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
|
|
||||||
encryptionPublicKey: '', // Empty on purpose, only required if new public/private key pair is generated.
|
|
||||||
client: '', // Empty on purpose, API will not use this for vault updates.
|
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
username: username,
|
username: username,
|
||||||
version: (await sqliteClient.getDatabaseVersion()).version
|
version: (await sqliteClient.getDatabaseVersion()).version,
|
||||||
|
// TODO: add public RSA encryption key to payload when implementing vault creation from browser extension. Currently only web app does this.
|
||||||
|
encryptionPublicKey: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const webApi = new WebApiService(() => {});
|
const webApi = new WebApiService(() => {});
|
||||||
|
|||||||
+24
-21
@@ -45,18 +45,18 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
const [selectedDomain, setSelectedDomain] = useState('');
|
const [selectedDomain, setSelectedDomain] = useState('');
|
||||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
||||||
|
const [hiddenPrivateEmailDomains, setHiddenPrivateEmailDomains] = useState<string[]>([]);
|
||||||
const popupRef = useRef<HTMLDivElement>(null);
|
const popupRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Get private email domains from vault metadata
|
// Get private email domains from vault metadata
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
* Load private email domains from vault metadata.
|
* Load private email domains from vault metadata, excluding hidden ones.
|
||||||
*/
|
*/
|
||||||
const loadDomains = async (): Promise<void> => {
|
const loadDomains = async (): Promise<void> => {
|
||||||
const metadata = await dbContext.getVaultMetadata();
|
const metadata = await dbContext.getVaultMetadata();
|
||||||
if (metadata?.privateEmailDomains) {
|
setPrivateEmailDomains(metadata?.privateEmailDomains ?? []);
|
||||||
setPrivateEmailDomains(metadata.privateEmailDomains);
|
setHiddenPrivateEmailDomains(metadata?.hiddenPrivateEmailDomains ?? []);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
loadDomains();
|
loadDomains();
|
||||||
}, [dbContext]);
|
}, [dbContext]);
|
||||||
@@ -84,9 +84,10 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
setLocalPart(local);
|
setLocalPart(local);
|
||||||
setSelectedDomain(domain);
|
setSelectedDomain(domain);
|
||||||
|
|
||||||
// Check if it's a custom domain
|
// Check if it's a custom domain (including hidden private domains as known domains)
|
||||||
const isKnownDomain = PUBLIC_EMAIL_DOMAINS.includes(domain) ||
|
const isKnownDomain = PUBLIC_EMAIL_DOMAINS.includes(domain) ||
|
||||||
privateEmailDomains.includes(domain);
|
privateEmailDomains.includes(domain) ||
|
||||||
|
hiddenPrivateEmailDomains.includes(domain);
|
||||||
setIsCustomDomain(!isKnownDomain);
|
setIsCustomDomain(!isKnownDomain);
|
||||||
} else {
|
} else {
|
||||||
setLocalPart(value);
|
setLocalPart(value);
|
||||||
@@ -102,7 +103,7 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [value, privateEmailDomains, showPrivateDomains]);
|
}, [value, privateEmailDomains, hiddenPrivateEmailDomains, showPrivateDomains]);
|
||||||
|
|
||||||
// Handle local part changes
|
// Handle local part changes
|
||||||
const handleLocalPartChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleLocalPartChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -246,20 +247,22 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
{t('credentials.privateEmailDescription')}
|
{t('credentials.privateEmailDescription')}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{privateEmailDomains.map((domain) => (
|
{privateEmailDomains
|
||||||
<button
|
.filter((domain) => !hiddenPrivateEmailDomains.includes(domain))
|
||||||
key={domain}
|
.map((domain) => (
|
||||||
type="button"
|
<button
|
||||||
onClick={() => selectDomain(domain)}
|
key={domain}
|
||||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
type="button"
|
||||||
selectedDomain === domain
|
onClick={() => selectDomain(domain)}
|
||||||
? 'bg-primary-600 text-white hover:bg-primary-700'
|
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
|
selectedDomain === domain
|
||||||
}`}
|
? 'bg-primary-600 text-white hover:bg-primary-700'
|
||||||
>
|
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
|
||||||
{domain}
|
}`}
|
||||||
</button>
|
>
|
||||||
))}
|
{domain}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
setDbInitialized(true);
|
setDbInitialized(true);
|
||||||
setDbAvailable(true);
|
setDbAvailable(true);
|
||||||
setVaultMetadata({
|
setVaultMetadata({
|
||||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList,
|
publicEmailDomains: vaultResponse.vault.publicEmailDomainList ?? [],
|
||||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList,
|
privateEmailDomains: vaultResponse.vault.privateEmailDomainList ?? [],
|
||||||
|
hiddenPrivateEmailDomains: vaultResponse.vault.hiddenPrivateEmailDomainList ?? [],
|
||||||
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
vaultBlob: vaultResponse.vault.blob,
|
vaultBlob: vaultResponse.vault.blob,
|
||||||
publicEmailDomainList: vaultResponse.vault.publicEmailDomainList,
|
publicEmailDomainList: vaultResponse.vault.publicEmailDomainList,
|
||||||
privateEmailDomainList: vaultResponse.vault.privateEmailDomainList,
|
privateEmailDomainList: vaultResponse.vault.privateEmailDomainList,
|
||||||
|
hiddenPrivateEmailDomainList: vaultResponse.vault.hiddenPrivateEmailDomainList,
|
||||||
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
setVaultMetadata({
|
setVaultMetadata({
|
||||||
publicEmailDomains: response.publicEmailDomains ?? [],
|
publicEmailDomains: response.publicEmailDomains ?? [],
|
||||||
privateEmailDomains: response.privateEmailDomains ?? [],
|
privateEmailDomains: response.privateEmailDomains ?? [],
|
||||||
|
hiddenPrivateEmailDomains: response.hiddenPrivateEmailDomains ?? [],
|
||||||
vaultRevisionNumber: response.vaultRevisionNumber ?? 0,
|
vaultRevisionNumber: response.vaultRevisionNumber ?? 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -123,6 +126,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
setVaultMetadata({
|
setVaultMetadata({
|
||||||
publicEmailDomains: vaultMetadata?.publicEmailDomains ?? [],
|
publicEmailDomains: vaultMetadata?.publicEmailDomains ?? [],
|
||||||
privateEmailDomains: vaultMetadata?.privateEmailDomains ?? [],
|
privateEmailDomains: vaultMetadata?.privateEmailDomains ?? [],
|
||||||
|
hiddenPrivateEmailDomains: vaultMetadata?.hiddenPrivateEmailDomains ?? [],
|
||||||
vaultRevisionNumber: revisionNumber,
|
vaultRevisionNumber: revisionNumber,
|
||||||
});
|
});
|
||||||
}, [vaultMetadata]);
|
}, [vaultMetadata]);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
type VaultMetadata = {
|
type VaultMetadata = {
|
||||||
publicEmailDomains: string[];
|
publicEmailDomains: string[];
|
||||||
privateEmailDomains: string[];
|
privateEmailDomains: string[];
|
||||||
|
hiddenPrivateEmailDomains: string[];
|
||||||
vaultRevisionNumber: number;
|
vaultRevisionNumber: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,18 @@ type ApiErrorResponse = {
|
|||||||
* Vault type.
|
* Vault type.
|
||||||
*/
|
*/
|
||||||
type Vault = {
|
type Vault = {
|
||||||
blob: string;
|
|
||||||
createdAt: string;
|
|
||||||
credentialsCount: number;
|
|
||||||
currentRevisionNumber: number;
|
|
||||||
emailAddressList: string[];
|
|
||||||
privateEmailDomainList: string[];
|
|
||||||
publicEmailDomainList: string[];
|
|
||||||
encryptionPublicKey: string;
|
|
||||||
updatedAt: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
|
blob: string;
|
||||||
version: string;
|
version: string;
|
||||||
client: string;
|
currentRevisionNumber: number;
|
||||||
|
credentialsCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
encryptionPublicKey?: string;
|
||||||
|
emailAddressList?: string[];
|
||||||
|
privateEmailDomainList?: string[];
|
||||||
|
hiddenPrivateEmailDomainList?: string[];
|
||||||
|
publicEmailDomainList?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ export type StoreVaultRequest = {
|
|||||||
vaultBlob: string;
|
vaultBlob: string;
|
||||||
publicEmailDomainList?: string[];
|
publicEmailDomainList?: string[];
|
||||||
privateEmailDomainList?: string[];
|
privateEmailDomainList?: string[];
|
||||||
|
hiddenPrivateEmailDomainList?: string[];
|
||||||
vaultRevisionNumber?: number;
|
vaultRevisionNumber?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ export type VaultResponse = {
|
|||||||
vault?: string,
|
vault?: string,
|
||||||
publicEmailDomains?: string[],
|
publicEmailDomains?: string[],
|
||||||
privateEmailDomains?: string[],
|
privateEmailDomains?: string[],
|
||||||
|
hiddenPrivateEmailDomains?: string[],
|
||||||
vaultRevisionNumber?: number
|
vaultRevisionNumber?: number
|
||||||
};
|
};
|
||||||
|
|||||||
+4
@@ -47,6 +47,7 @@ class VaultMetadataManager(
|
|||||||
JSONObject().apply {
|
JSONObject().apply {
|
||||||
put("publicEmailDomains", JSONArray(updatedMetadata.publicEmailDomains))
|
put("publicEmailDomains", JSONArray(updatedMetadata.publicEmailDomains))
|
||||||
put("privateEmailDomains", JSONArray(updatedMetadata.privateEmailDomains))
|
put("privateEmailDomains", JSONArray(updatedMetadata.privateEmailDomains))
|
||||||
|
put("hiddenPrivateEmailDomains", JSONArray(updatedMetadata.hiddenPrivateEmailDomains))
|
||||||
put("vaultRevisionNumber", updatedMetadata.vaultRevisionNumber)
|
put("vaultRevisionNumber", updatedMetadata.vaultRevisionNumber)
|
||||||
}.toString(),
|
}.toString(),
|
||||||
)
|
)
|
||||||
@@ -158,6 +159,9 @@ class VaultMetadataManager(
|
|||||||
privateEmailDomains = json.optJSONArray("privateEmailDomains")?.let { array ->
|
privateEmailDomains = json.optJSONArray("privateEmailDomains")?.let { array ->
|
||||||
List(array.length()) { i -> array.getString(i) }
|
List(array.length()) { i -> array.getString(i) }
|
||||||
} ?: emptyList(),
|
} ?: emptyList(),
|
||||||
|
hiddenPrivateEmailDomains = json.optJSONArray("hiddenPrivateEmailDomains")?.let { array ->
|
||||||
|
List(array.length()) { i -> array.getString(i) }
|
||||||
|
} ?: emptyList(),
|
||||||
vaultRevisionNumber = json.optInt("vaultRevisionNumber", 0),
|
vaultRevisionNumber = json.optInt("vaultRevisionNumber", 0),
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+1
-11
@@ -33,13 +33,10 @@ class VaultMutate(
|
|||||||
json.put("credentialsCount", vault.credentialsCount)
|
json.put("credentialsCount", vault.credentialsCount)
|
||||||
json.put("currentRevisionNumber", vault.currentRevisionNumber)
|
json.put("currentRevisionNumber", vault.currentRevisionNumber)
|
||||||
json.put("emailAddressList", JSONArray(vault.emailAddressList))
|
json.put("emailAddressList", JSONArray(vault.emailAddressList))
|
||||||
json.put("privateEmailDomainList", JSONArray(vault.privateEmailDomainList))
|
|
||||||
json.put("publicEmailDomainList", JSONArray(vault.publicEmailDomainList))
|
|
||||||
json.put("encryptionPublicKey", vault.encryptionPublicKey)
|
json.put("encryptionPublicKey", vault.encryptionPublicKey)
|
||||||
json.put("updatedAt", vault.updatedAt)
|
json.put("updatedAt", vault.updatedAt)
|
||||||
json.put("username", vault.username)
|
json.put("username", vault.username)
|
||||||
json.put("version", vault.version)
|
json.put("version", vault.version)
|
||||||
json.put("client", vault.client)
|
|
||||||
|
|
||||||
val response = webApiService.executeRequest(
|
val response = webApiService.executeRequest(
|
||||||
method = "POST",
|
method = "POST",
|
||||||
@@ -124,8 +121,6 @@ class VaultMutate(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"0.0.0"
|
"0.0.0"
|
||||||
}
|
}
|
||||||
val baseVersion = version.split("-").firstOrNull() ?: "0.0.0"
|
|
||||||
val client = "android-$baseVersion"
|
|
||||||
|
|
||||||
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.US)
|
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.US)
|
||||||
dateFormat.timeZone = java.util.TimeZone.getTimeZone("UTC")
|
dateFormat.timeZone = java.util.TimeZone.getTimeZone("UTC")
|
||||||
@@ -137,13 +132,11 @@ class VaultMutate(
|
|||||||
credentialsCount = credentials.size,
|
credentialsCount = credentials.size,
|
||||||
currentRevisionNumber = currentRevision,
|
currentRevisionNumber = currentRevision,
|
||||||
emailAddressList = privateEmailAddresses,
|
emailAddressList = privateEmailAddresses,
|
||||||
privateEmailDomainList = emptyList(),
|
// TODO: add public RSA encryption key to payload when implementing vault creation from mobile app. Currently only web app does this.
|
||||||
publicEmailDomainList = emptyList(),
|
|
||||||
encryptionPublicKey = "",
|
encryptionPublicKey = "",
|
||||||
updatedAt = now,
|
updatedAt = now,
|
||||||
username = username,
|
username = username,
|
||||||
version = dbVersion,
|
version = dbVersion,
|
||||||
client = client,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,13 +150,10 @@ class VaultMutate(
|
|||||||
val credentialsCount: Int,
|
val credentialsCount: Int,
|
||||||
val currentRevisionNumber: Int,
|
val currentRevisionNumber: Int,
|
||||||
val emailAddressList: List<String>,
|
val emailAddressList: List<String>,
|
||||||
val privateEmailDomainList: List<String>,
|
|
||||||
val publicEmailDomainList: List<String>,
|
|
||||||
val encryptionPublicKey: String,
|
val encryptionPublicKey: String,
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
val username: String,
|
val username: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val client: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class VaultPostResponse(
|
private data class VaultPostResponse(
|
||||||
|
|||||||
@@ -173,12 +173,34 @@ class VaultSync(
|
|||||||
database.storeEncryptedDatabase(vault.vault.blob)
|
database.storeEncryptedDatabase(vault.vault.blob)
|
||||||
metadata.setVaultRevisionNumber(newRevision)
|
metadata.setVaultRevisionNumber(newRevision)
|
||||||
|
|
||||||
|
// Store vault metadata (public/private email domains)
|
||||||
|
val vaultMetadata = net.aliasvault.app.vaultstore.models.VaultMetadata(
|
||||||
|
publicEmailDomains = vault.vault.publicEmailDomainList,
|
||||||
|
privateEmailDomains = vault.vault.privateEmailDomainList,
|
||||||
|
hiddenPrivateEmailDomains = vault.vault.hiddenPrivateEmailDomainList,
|
||||||
|
vaultRevisionNumber = newRevision,
|
||||||
|
)
|
||||||
|
storeVaultMetadata(vaultMetadata)
|
||||||
|
|
||||||
if (database.isVaultUnlocked()) {
|
if (database.isVaultUnlocked()) {
|
||||||
// Re-unlock with new data
|
// Re-unlock with new data
|
||||||
// Note: This requires auth methods to be passed, handled by VaultStore
|
// Note: This requires auth methods to be passed, handled by VaultStore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store vault metadata as JSON string.
|
||||||
|
*/
|
||||||
|
private fun storeVaultMetadata(vaultMetadata: net.aliasvault.app.vaultstore.models.VaultMetadata) {
|
||||||
|
val json = JSONObject().apply {
|
||||||
|
put("publicEmailDomains", org.json.JSONArray(vaultMetadata.publicEmailDomains))
|
||||||
|
put("privateEmailDomains", org.json.JSONArray(vaultMetadata.privateEmailDomains))
|
||||||
|
put("hiddenPrivateEmailDomains", org.json.JSONArray(vaultMetadata.hiddenPrivateEmailDomains))
|
||||||
|
put("vaultRevisionNumber", vaultMetadata.vaultRevisionNumber)
|
||||||
|
}
|
||||||
|
metadata.storeMetadata(json.toString())
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseVaultResponse(body: String): VaultResponse {
|
private fun parseVaultResponse(body: String): VaultResponse {
|
||||||
return try {
|
return try {
|
||||||
val json = JSONObject(body)
|
val json = JSONObject(body)
|
||||||
@@ -196,6 +218,12 @@ class VaultSync(
|
|||||||
privateList.add(privateArray.getString(i))
|
privateList.add(privateArray.getString(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hiddenPrivateList = mutableListOf<String>()
|
||||||
|
val hiddenPrivateArray = vaultJson.getJSONArray("hiddenPrivateEmailDomainList")
|
||||||
|
for (i in 0 until hiddenPrivateArray.length()) {
|
||||||
|
hiddenPrivateList.add(hiddenPrivateArray.getString(i))
|
||||||
|
}
|
||||||
|
|
||||||
val publicList = mutableListOf<String>()
|
val publicList = mutableListOf<String>()
|
||||||
val publicArray = vaultJson.getJSONArray("publicEmailDomainList")
|
val publicArray = vaultJson.getJSONArray("publicEmailDomainList")
|
||||||
for (i in 0 until publicArray.length()) {
|
for (i in 0 until publicArray.length()) {
|
||||||
@@ -213,6 +241,7 @@ class VaultSync(
|
|||||||
credentialsCount = vaultJson.getInt("credentialsCount"),
|
credentialsCount = vaultJson.getInt("credentialsCount"),
|
||||||
emailAddressList = emailList,
|
emailAddressList = emailList,
|
||||||
privateEmailDomainList = privateList,
|
privateEmailDomainList = privateList,
|
||||||
|
hiddenPrivateEmailDomainList = hiddenPrivateList,
|
||||||
publicEmailDomainList = publicList,
|
publicEmailDomainList = publicList,
|
||||||
createdAt = vaultJson.getString("createdAt"),
|
createdAt = vaultJson.getString("createdAt"),
|
||||||
updatedAt = vaultJson.getString("updatedAt"),
|
updatedAt = vaultJson.getString("updatedAt"),
|
||||||
@@ -253,6 +282,7 @@ class VaultSync(
|
|||||||
val credentialsCount: Int,
|
val credentialsCount: Int,
|
||||||
val emailAddressList: List<String>,
|
val emailAddressList: List<String>,
|
||||||
val privateEmailDomainList: List<String>,
|
val privateEmailDomainList: List<String>,
|
||||||
|
val hiddenPrivateEmailDomainList: List<String>,
|
||||||
val publicEmailDomainList: List<String>,
|
val publicEmailDomainList: List<String>,
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
|
|||||||
+6
@@ -14,6 +14,12 @@ data class VaultMetadata(
|
|||||||
*/
|
*/
|
||||||
val privateEmailDomains: List<String> = emptyList(),
|
val privateEmailDomains: List<String> = emptyList(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hidden private email domains of the vault.
|
||||||
|
* These domains still function as private email domains but are hidden from UI components.
|
||||||
|
*/
|
||||||
|
val hiddenPrivateEmailDomains: List<String> = emptyList(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The revision number of the vault.
|
* The revision number of the vault.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ class VaultStoreTest {
|
|||||||
val metadata = """
|
val metadata = """
|
||||||
{
|
{
|
||||||
"publicEmailDomains": ["spamok.com", "spamok.nl"],
|
"publicEmailDomains": ["spamok.com", "spamok.nl"],
|
||||||
"privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"],
|
"privateEmailDomains": ["aliasvault.net", "main.aliasvault.net", "hidden.aliasvault.net"],
|
||||||
|
"hiddenPrivateEmailDomains": ["hidden.aliasvault.net"],
|
||||||
"vaultRevisionNumber": 1
|
"vaultRevisionNumber": 1
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -261,7 +261,6 @@ export default function Initialize() : React.ReactNode {
|
|||||||
|
|
||||||
// Now perform vault sync (network operations - these are skippable)
|
// Now perform vault sync (network operations - these are skippable)
|
||||||
await syncVault({
|
await syncVault({
|
||||||
initialSync: true,
|
|
||||||
abortSignal: abortControllerRef.current.signal,
|
abortSignal: abortControllerRef.current.signal,
|
||||||
/**
|
/**
|
||||||
* Handle the status update.
|
* Handle the status update.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { StyleSheet, View, Text, SafeAreaView, TextInput, ActivityIndicator, Ani
|
|||||||
import { useApiUrl } from '@/utils/ApiUrlUtility';
|
import { useApiUrl } from '@/utils/ApiUrlUtility';
|
||||||
import ConversionUtility from '@/utils/ConversionUtility';
|
import ConversionUtility from '@/utils/ConversionUtility';
|
||||||
import type { EncryptionKeyDerivationParams } from '@/utils/dist/shared/models/metadata';
|
import type { EncryptionKeyDerivationParams } from '@/utils/dist/shared/models/metadata';
|
||||||
import type { LoginResponse, VaultResponse } from '@/utils/dist/shared/models/webapi';
|
import type { LoginResponse } from '@/utils/dist/shared/models/webapi';
|
||||||
import EncryptionUtility from '@/utils/EncryptionUtility';
|
import EncryptionUtility from '@/utils/EncryptionUtility';
|
||||||
import { SrpUtility } from '@/utils/SrpUtility';
|
import { SrpUtility } from '@/utils/SrpUtility';
|
||||||
import { ApiAuthError } from '@/utils/types/errors/ApiAuthError';
|
import { ApiAuthError } from '@/utils/types/errors/ApiAuthError';
|
||||||
@@ -77,14 +77,12 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
* Process the vault response by storing the vault and logging in the user.
|
* Process the vault response by storing the vault and logging in the user.
|
||||||
* @param token - The token to use for the vault
|
* @param token - The token to use for the vault
|
||||||
* @param refreshToken - The refresh token to use for the vault
|
* @param refreshToken - The refresh token to use for the vault
|
||||||
* @param vaultResponseJson - The vault response
|
|
||||||
* @param passwordHashBase64 - The password hash base64
|
* @param passwordHashBase64 - The password hash base64
|
||||||
* @param initiateLoginResponse - The initiate login response
|
* @param initiateLoginResponse - The initiate login response
|
||||||
*/
|
*/
|
||||||
const processVaultResponse = async (
|
const processVaultResponse = async (
|
||||||
token: string,
|
token: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
vaultResponseJson: VaultResponse,
|
|
||||||
passwordHashBase64: string,
|
passwordHashBase64: string,
|
||||||
initiateLoginResponse: LoginResponse
|
initiateLoginResponse: LoginResponse
|
||||||
) : Promise<void> => {
|
) : Promise<void> => {
|
||||||
@@ -109,7 +107,6 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
await continueProcessVaultResponse(
|
await continueProcessVaultResponse(
|
||||||
token,
|
token,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
vaultResponseJson,
|
|
||||||
passwordHashBase64,
|
passwordHashBase64,
|
||||||
initiateLoginResponse
|
initiateLoginResponse
|
||||||
);
|
);
|
||||||
@@ -126,7 +123,6 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
await continueProcessVaultResponse(
|
await continueProcessVaultResponse(
|
||||||
token,
|
token,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
vaultResponseJson,
|
|
||||||
passwordHashBase64,
|
passwordHashBase64,
|
||||||
initiateLoginResponse
|
initiateLoginResponse
|
||||||
);
|
);
|
||||||
@@ -140,7 +136,6 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
await continueProcessVaultResponse(
|
await continueProcessVaultResponse(
|
||||||
token,
|
token,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
vaultResponseJson,
|
|
||||||
passwordHashBase64,
|
passwordHashBase64,
|
||||||
initiateLoginResponse
|
initiateLoginResponse
|
||||||
);
|
);
|
||||||
@@ -151,7 +146,6 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
* Continue processing the vault response after biometric choice
|
* Continue processing the vault response after biometric choice
|
||||||
* @param token - The token to use for the vault
|
* @param token - The token to use for the vault
|
||||||
* @param refreshToken - The refresh token to use for the vault
|
* @param refreshToken - The refresh token to use for the vault
|
||||||
* @param vaultResponseJson - The vault response
|
|
||||||
* @param passwordHashBase64 - The password hash base64
|
* @param passwordHashBase64 - The password hash base64
|
||||||
* @param initiateLoginResponse - The initiate login response
|
* @param initiateLoginResponse - The initiate login response
|
||||||
* @param encryptionKeyDerivationParams - The encryption key derivation parameters
|
* @param encryptionKeyDerivationParams - The encryption key derivation parameters
|
||||||
@@ -159,7 +153,6 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
const continueProcessVaultResponse = async (
|
const continueProcessVaultResponse = async (
|
||||||
token: string,
|
token: string,
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
vaultResponseJson: VaultResponse,
|
|
||||||
passwordHashBase64: string,
|
passwordHashBase64: string,
|
||||||
initiateLoginResponse: LoginResponse
|
initiateLoginResponse: LoginResponse
|
||||||
) : Promise<void> => {
|
) : Promise<void> => {
|
||||||
@@ -169,20 +162,29 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
salt: initiateLoginResponse.salt,
|
salt: initiateLoginResponse.salt,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set auth tokens, store encryption key and key derivation params, and initialize database
|
/*
|
||||||
|
* Set auth tokens, store encryption key and key derivation params.
|
||||||
|
* Note: We don't call initializeDatabase here anymore - instead, syncVault will download
|
||||||
|
* the vault and store it (including metadata) through native code.
|
||||||
|
*/
|
||||||
await authContext.setAuthTokens(ConversionUtility.normalizeUsername(credentials.username), token, refreshToken);
|
await authContext.setAuthTokens(ConversionUtility.normalizeUsername(credentials.username), token, refreshToken);
|
||||||
await dbContext.storeEncryptionKey(passwordHashBase64);
|
await dbContext.storeEncryptionKey(passwordHashBase64);
|
||||||
await dbContext.storeEncryptionKeyDerivationParams(encryptionKeyDerivationParams);
|
await dbContext.storeEncryptionKeyDerivationParams(encryptionKeyDerivationParams);
|
||||||
await dbContext.initializeDatabase(vaultResponseJson);
|
|
||||||
|
|
||||||
let checkSuccess = true;
|
let checkSuccess = true;
|
||||||
/**
|
/**
|
||||||
* After setting auth tokens, execute a server status check immediately
|
* After setting auth tokens, execute a server status check immediately
|
||||||
* which takes care of certain sanity checks such as ensuring client/server
|
* which takes care of certain sanity checks such as ensuring client/server
|
||||||
* compatibility.
|
* compatibility. This also downloads the vault and stores it (including metadata)
|
||||||
|
* through native code.
|
||||||
*/
|
*/
|
||||||
await syncVault({
|
await syncVault({
|
||||||
initialSync: true,
|
/**
|
||||||
|
* Update login status during sync.
|
||||||
|
*/
|
||||||
|
onStatus: (status) => {
|
||||||
|
setLoginStatus(status);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Handle the status update.
|
* Handle the status update.
|
||||||
*/
|
*/
|
||||||
@@ -218,6 +220,12 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After syncVault completes, the vault has been downloaded and stored by native code.
|
||||||
|
* Immediately mark the database as available without file system checks for faster bootstrap.
|
||||||
|
*/
|
||||||
|
dbContext.setDatabaseAvailable();
|
||||||
|
|
||||||
await authContext.login();
|
await authContext.login();
|
||||||
|
|
||||||
authContext.setOfflineMode(false);
|
authContext.setOfflineMode(false);
|
||||||
@@ -286,23 +294,10 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
|
|
||||||
setLoginStatus(t('auth.syncingVault'));
|
setLoginStatus(t('auth.syncingVault'));
|
||||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
const vaultResponseJson = await webApi.authFetch<VaultResponse>('Vault', { method: 'GET', headers: {
|
|
||||||
'Authorization': `Bearer ${validationResponse.token.token}`
|
|
||||||
} });
|
|
||||||
|
|
||||||
const vaultError = webApi.validateVaultResponse(vaultResponseJson);
|
|
||||||
if (vaultError) {
|
|
||||||
console.error('vaultError', vaultError);
|
|
||||||
setError(vaultError);
|
|
||||||
setIsLoading(false);
|
|
||||||
setLoginStatus(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processVaultResponse(
|
await processVaultResponse(
|
||||||
validationResponse.token.token,
|
validationResponse.token.token,
|
||||||
validationResponse.token.refreshToken,
|
validationResponse.token.refreshToken,
|
||||||
vaultResponseJson,
|
|
||||||
passwordHashBase64,
|
passwordHashBase64,
|
||||||
initiateLoginResponse
|
initiateLoginResponse
|
||||||
);
|
);
|
||||||
@@ -357,21 +352,10 @@ export default function LoginScreen() : React.ReactNode {
|
|||||||
|
|
||||||
setLoginStatus(t('auth.syncingVault'));
|
setLoginStatus(t('auth.syncingVault'));
|
||||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
const vaultResponseJson = await webApi.authFetch<VaultResponse>('Vault', { method: 'GET', headers: {
|
|
||||||
'Authorization': `Bearer ${validationResponse.token.token}`
|
|
||||||
} });
|
|
||||||
|
|
||||||
const vaultError = webApi.validateVaultResponse(vaultResponseJson);
|
|
||||||
if (vaultError) {
|
|
||||||
setError(vaultError);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await processVaultResponse(
|
await processVaultResponse(
|
||||||
validationResponse.token.token,
|
validationResponse.token.token,
|
||||||
validationResponse.token.refreshToken,
|
validationResponse.token.refreshToken,
|
||||||
vaultResponseJson,
|
|
||||||
passwordHashBase64,
|
passwordHashBase64,
|
||||||
initiateLoginResponse
|
initiateLoginResponse
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ export default function ReinitializeScreen() : React.ReactNode {
|
|||||||
|
|
||||||
// Now perform vault sync (network operations - these are skippable)
|
// Now perform vault sync (network operations - these are skippable)
|
||||||
await syncVault({
|
await syncVault({
|
||||||
initialSync: true,
|
|
||||||
/**
|
/**
|
||||||
* Handle the status update.
|
* Handle the status update.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) : React.Rea
|
|||||||
}, [dbContext]);
|
}, [dbContext]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the email is a private domain.
|
* Check if the email is a private domain (including hidden domains).
|
||||||
*/
|
*/
|
||||||
const isPrivateDomain = useCallback(async (emailAddress: string): Promise<boolean> => {
|
const isPrivateDomain = useCallback(async (emailAddress: string): Promise<boolean> => {
|
||||||
// Get private domains from stored metadata
|
// Get private domains from stored metadata
|
||||||
@@ -84,7 +84,9 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) : React.Rea
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata.privateEmailDomains.includes(emailAddress.split('@')[1]);
|
const domain = emailAddress.split('@')[1];
|
||||||
|
return metadata.privateEmailDomains.includes(domain) ||
|
||||||
|
(metadata.hiddenPrivateEmailDomains || []).includes(domain);
|
||||||
}, [dbContext]);
|
}, [dbContext]);
|
||||||
|
|
||||||
// Handle app state changes
|
// Handle app state changes
|
||||||
|
|||||||
@@ -49,18 +49,21 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
const [selectedDomain, setSelectedDomain] = useState('');
|
const [selectedDomain, setSelectedDomain] = useState('');
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
||||||
|
const [hiddenPrivateEmailDomains, setHiddenPrivateEmailDomains] = useState<string[]>([]);
|
||||||
|
|
||||||
// Get private email domains from vault metadata
|
// Get private email domains from vault metadata
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
* Load private email domains from vault metadata.
|
* Load private email domains from vault metadata, excluding hidden ones from UI.
|
||||||
*/
|
*/
|
||||||
const loadDomains = async (): Promise<void> => {
|
const loadDomains = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const metadata = await dbContext.getVaultMetadata();
|
const metadata = await dbContext.getVaultMetadata();
|
||||||
if (metadata?.privateEmailDomains) {
|
setPrivateEmailDomains(metadata?.privateEmailDomains ?? []);
|
||||||
setPrivateEmailDomains(metadata.privateEmailDomains);
|
setHiddenPrivateEmailDomains(metadata?.hiddenPrivateEmailDomains ?? []);
|
||||||
}
|
|
||||||
|
console.log('privateEmailDomains', metadata?.privateEmailDomains);
|
||||||
|
console.log('hiddenPrivateEmailDomains', metadata?.hiddenPrivateEmailDomains);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading email domains:', err);
|
console.error('Error loading email domains:', err);
|
||||||
}
|
}
|
||||||
@@ -91,9 +94,10 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
setLocalPart(local);
|
setLocalPart(local);
|
||||||
setSelectedDomain(domain);
|
setSelectedDomain(domain);
|
||||||
|
|
||||||
// Check if it's a custom domain
|
// Check if it's a custom domain (including hidden private domains as known domains)
|
||||||
const isKnownDomain = PUBLIC_EMAIL_DOMAINS.includes(domain) ||
|
const isKnownDomain = PUBLIC_EMAIL_DOMAINS.includes(domain) ||
|
||||||
privateEmailDomains.includes(domain);
|
privateEmailDomains.includes(domain) ||
|
||||||
|
hiddenPrivateEmailDomains.includes(domain);
|
||||||
setIsCustomDomain(!isKnownDomain);
|
setIsCustomDomain(!isKnownDomain);
|
||||||
} else {
|
} else {
|
||||||
setLocalPart(value);
|
setLocalPart(value);
|
||||||
@@ -108,7 +112,7 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [value, privateEmailDomains, showPrivateDomains]);
|
}, [value, privateEmailDomains, hiddenPrivateEmailDomains, showPrivateDomains]);
|
||||||
|
|
||||||
// Handle local part changes
|
// Handle local part changes
|
||||||
const handleLocalPartChange = useCallback((newText: string) => {
|
const handleLocalPartChange = useCallback((newText: string) => {
|
||||||
@@ -410,7 +414,7 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
|||||||
{t('credentials.privateEmailDescription')}
|
{t('credentials.privateEmailDescription')}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.domainList}>
|
<View style={styles.domainList}>
|
||||||
{privateEmailDomains.map((domain) => (
|
{privateEmailDomains.filter(domain => !hiddenPrivateEmailDomains.includes(domain)).map((domain) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={domain}
|
key={domain}
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type DbContextType = {
|
|||||||
getVaultMetadata: () => Promise<VaultMetadata | null>;
|
getVaultMetadata: () => Promise<VaultMetadata | null>;
|
||||||
testDatabaseConnection: (derivedKey: string) => Promise<boolean>;
|
testDatabaseConnection: (derivedKey: string) => Promise<boolean>;
|
||||||
unlockVault: () => Promise<boolean>;
|
unlockVault: () => Promise<boolean>;
|
||||||
|
checkStoredVault: () => Promise<void>;
|
||||||
|
setDatabaseAvailable: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DbContext = createContext<DbContextType | undefined>(undefined);
|
const DbContext = createContext<DbContextType | undefined>(undefined);
|
||||||
@@ -80,13 +82,16 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the database in the native module.
|
* Initialize the database in the native module.
|
||||||
|
* This is called during initial login/registration to set up the vault.
|
||||||
|
* Note: During sync operations, metadata is stored automatically by native VaultSync.
|
||||||
*
|
*
|
||||||
* @param vaultResponse The vault response from the API
|
* @param vaultResponse The vault response from the API
|
||||||
*/
|
*/
|
||||||
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse) => {
|
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse) => {
|
||||||
const metadata: VaultMetadata = {
|
const metadata: VaultMetadata = {
|
||||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList,
|
publicEmailDomains: vaultResponse.vault.publicEmailDomainList ?? [],
|
||||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList,
|
privateEmailDomains: vaultResponse.vault.privateEmailDomainList ?? [],
|
||||||
|
hiddenPrivateEmailDomains: vaultResponse.vault.hiddenPrivateEmailDomainList ?? [],
|
||||||
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,7 +99,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
await sqliteClient.storeEncryptedDatabase(vaultResponse.vault.blob);
|
await sqliteClient.storeEncryptedDatabase(vaultResponse.vault.blob);
|
||||||
await sqliteClient.storeMetadata(JSON.stringify(metadata));
|
await sqliteClient.storeMetadata(JSON.stringify(metadata));
|
||||||
|
|
||||||
// Initialize the database in the native module
|
// Unlock the vault to make it available for queries
|
||||||
await unlockVault();
|
await unlockVault();
|
||||||
|
|
||||||
setDbInitialized(true);
|
setDbInitialized(true);
|
||||||
@@ -156,6 +161,15 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
NativeVaultManager.clearVault();
|
NativeVaultManager.clearVault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually set the database as available. Used after vault sync to immediately
|
||||||
|
* mark the database as ready without file system checks.
|
||||||
|
*/
|
||||||
|
const setDatabaseAvailable = useCallback(() : void => {
|
||||||
|
setDbInitialized(true);
|
||||||
|
setDbAvailable(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current vault metadata directly from SQLite client
|
* Get the current vault metadata directly from SQLite client
|
||||||
*/
|
*/
|
||||||
@@ -199,7 +213,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
|||||||
unlockVault,
|
unlockVault,
|
||||||
storeEncryptionKey,
|
storeEncryptionKey,
|
||||||
storeEncryptionKeyDerivationParams,
|
storeEncryptionKeyDerivationParams,
|
||||||
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, hasPendingMigrations, clearDatabase, getVaultMetadata, testDatabaseConnection, unlockVault, storeEncryptionKey, storeEncryptionKeyDerivationParams]);
|
checkStoredVault,
|
||||||
|
setDatabaseAvailable,
|
||||||
|
}), [sqliteClient, dbInitialized, dbAvailable, initializeDatabase, hasPendingMigrations, clearDatabase, getVaultMetadata, testDatabaseConnection, unlockVault, storeEncryptionKey, storeEncryptionKeyDerivationParams, checkStoredVault, setDatabaseAvailable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DbContext.Provider value={contextValue}>
|
<DbContext.Provider value={contextValue}>
|
||||||
|
|||||||
@@ -186,12 +186,10 @@ export const NavigationProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Database connection failed, navigate to reinitialize flow
|
// Database connection failed, navigate to reinitialize flow
|
||||||
console.log('database connection failed, navigating to reinitialize');
|
|
||||||
router.replace('/reinitialize');
|
router.replace('/reinitialize');
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Database query failed, navigate to reinitialize flow
|
// Database query failed, navigate to reinitialize flow
|
||||||
console.log('database query failed, navigating to reinitialize');
|
|
||||||
router.replace('/reinitialize');
|
router.replace('/reinitialize');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export function useVaultMutate() : {
|
|||||||
currentRevisionNumber: currentRevision,
|
currentRevisionNumber: currentRevision,
|
||||||
emailAddressList: privateEmailAddresses,
|
emailAddressList: privateEmailAddresses,
|
||||||
privateEmailDomainList: [],
|
privateEmailDomainList: [],
|
||||||
|
hiddenPrivateEmailDomainList: [],
|
||||||
publicEmailDomainList: [],
|
publicEmailDomainList: [],
|
||||||
encryptionPublicKey: '',
|
encryptionPublicKey: '',
|
||||||
client: '',
|
client: '',
|
||||||
|
|||||||
@@ -16,16 +16,7 @@ import { VaultSyncErrorCode, getVaultSyncErrorCode } from '@/utils/types/errors/
|
|||||||
/**
|
/**
|
||||||
* Utility function to ensure a minimum time has elapsed for an operation
|
* Utility function to ensure a minimum time has elapsed for an operation
|
||||||
*/
|
*/
|
||||||
const withMinimumDelay = async <T>(
|
const withMinimumDelay = async <T>(operation: () => Promise<T>, minDelayMs: number): Promise<T> => {
|
||||||
operation: () => Promise<T>,
|
|
||||||
minDelayMs: number,
|
|
||||||
enableDelay: boolean = true
|
|
||||||
): Promise<T> => {
|
|
||||||
if (!enableDelay) {
|
|
||||||
// If delay is disabled, return the result immediately.
|
|
||||||
return operation();
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const result = await operation();
|
const result = await operation();
|
||||||
const elapsedTime = Date.now() - startTime;
|
const elapsedTime = Date.now() - startTime;
|
||||||
@@ -38,7 +29,6 @@ const withMinimumDelay = async <T>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
type VaultSyncOptions = {
|
type VaultSyncOptions = {
|
||||||
initialSync?: boolean;
|
|
||||||
onSuccess?: (hasNewVault: boolean) => void;
|
onSuccess?: (hasNewVault: boolean) => void;
|
||||||
onError?: (error: string) => void;
|
onError?: (error: string) => void;
|
||||||
onStatus?: (message: string) => void;
|
onStatus?: (message: string) => void;
|
||||||
@@ -59,10 +49,7 @@ export const useVaultSync = () : {
|
|||||||
const dbContext = useDb();
|
const dbContext = useDb();
|
||||||
|
|
||||||
const syncVault = useCallback(async (options: VaultSyncOptions = {}) => {
|
const syncVault = useCallback(async (options: VaultSyncOptions = {}) => {
|
||||||
const { initialSync = false, onSuccess, onError, onStatus, onOffline, onUpgradeRequired, abortSignal } = options;
|
const { onSuccess, onError, onStatus, onOffline, onUpgradeRequired, abortSignal } = options;
|
||||||
|
|
||||||
// For the initial sync, we add an artifical delay to various steps which makes it feel more fluid.
|
|
||||||
const enableDelay = initialSync;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if operation was aborted
|
// Check if operation was aborted
|
||||||
@@ -87,11 +74,6 @@ export const useVaultSync = () : {
|
|||||||
// Update status
|
// Update status
|
||||||
onStatus?.(t('vault.checkingVaultUpdates'));
|
onStatus?.(t('vault.checkingVaultUpdates'));
|
||||||
|
|
||||||
// Add artificial delay for initial sync UX
|
|
||||||
if (enableDelay) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if operation was aborted
|
// Check if operation was aborted
|
||||||
if (abortSignal?.aborted) {
|
if (abortSignal?.aborted) {
|
||||||
console.debug('VaultSync: Operation aborted after status update');
|
console.debug('VaultSync: Operation aborted after status update');
|
||||||
@@ -128,8 +110,7 @@ export const useVaultSync = () : {
|
|||||||
// Run downloadVault with a min delay for UX purposes
|
// Run downloadVault with a min delay for UX purposes
|
||||||
await withMinimumDelay(
|
await withMinimumDelay(
|
||||||
() => NativeVaultManager.downloadVault(newRevision!),
|
() => NativeVaultManager.downloadVault(newRevision!),
|
||||||
enableDelay ? 500 : 300,
|
300
|
||||||
true
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -183,11 +164,6 @@ export const useVaultSync = () : {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add artificial delay for initial sync UX
|
|
||||||
if (enableDelay) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, hasNewVault ? 1000 : 300));
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess?.(hasNewVault);
|
onSuccess?.(hasNewVault);
|
||||||
|
|
||||||
// Register credential identities after sync
|
// Register credential identities after sync
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import Foundation
|
|||||||
public struct VaultMetadata: Codable {
|
public struct VaultMetadata: Codable {
|
||||||
public var publicEmailDomains: [String]?
|
public var publicEmailDomains: [String]?
|
||||||
public var privateEmailDomains: [String]?
|
public var privateEmailDomains: [String]?
|
||||||
|
public var hiddenPrivateEmailDomains: [String]?
|
||||||
public var vaultRevisionNumber: Int
|
public var vaultRevisionNumber: Int
|
||||||
|
|
||||||
public init(publicEmailDomains: [String]? = nil, privateEmailDomains: [String]? = nil, vaultRevisionNumber: Int) {
|
public init(publicEmailDomains: [String]? = nil, privateEmailDomains: [String]? = nil, hiddenPrivateEmailDomains: [String]? = nil, vaultRevisionNumber: Int) {
|
||||||
self.publicEmailDomains = publicEmailDomains
|
self.publicEmailDomains = publicEmailDomains
|
||||||
self.privateEmailDomains = privateEmailDomains
|
self.privateEmailDomains = privateEmailDomains
|
||||||
|
self.hiddenPrivateEmailDomains = hiddenPrivateEmailDomains
|
||||||
self.vaultRevisionNumber = vaultRevisionNumber
|
self.vaultRevisionNumber = vaultRevisionNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ extension VaultStore {
|
|||||||
metadata = VaultMetadata(
|
metadata = VaultMetadata(
|
||||||
publicEmailDomains: [],
|
publicEmailDomains: [],
|
||||||
privateEmailDomains: [],
|
privateEmailDomains: [],
|
||||||
|
hiddenPrivateEmailDomains: [],
|
||||||
vaultRevisionNumber: revisionNumber
|
vaultRevisionNumber: revisionNumber
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,10 @@ public struct VaultUpload: Codable {
|
|||||||
public let credentialsCount: Int
|
public let credentialsCount: Int
|
||||||
public let currentRevisionNumber: Int
|
public let currentRevisionNumber: Int
|
||||||
public let emailAddressList: [String]
|
public let emailAddressList: [String]
|
||||||
public let privateEmailDomainList: [String]
|
|
||||||
public let publicEmailDomainList: [String]
|
|
||||||
public let encryptionPublicKey: String
|
public let encryptionPublicKey: String
|
||||||
public let updatedAt: String
|
public let updatedAt: String
|
||||||
public let username: String
|
public let username: String
|
||||||
public let version: String
|
public let version: String
|
||||||
public let client: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vault POST response from API
|
/// Vault POST response from API
|
||||||
@@ -86,11 +83,6 @@ extension VaultStore {
|
|||||||
// Get database version
|
// Get database version
|
||||||
let dbVersion = try getDatabaseVersion()
|
let dbVersion = try getDatabaseVersion()
|
||||||
|
|
||||||
// Get client version
|
|
||||||
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
|
||||||
let baseVersion = version.split(separator: "-").first.map(String.init) ?? "0.0.0"
|
|
||||||
let client = "ios-\(baseVersion)"
|
|
||||||
|
|
||||||
let dateFormatter = ISO8601DateFormatter()
|
let dateFormatter = ISO8601DateFormatter()
|
||||||
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||||
let now = dateFormatter.string(from: Date())
|
let now = dateFormatter.string(from: Date())
|
||||||
@@ -101,13 +93,11 @@ extension VaultStore {
|
|||||||
credentialsCount: credentials.count,
|
credentialsCount: credentials.count,
|
||||||
currentRevisionNumber: currentRevision,
|
currentRevisionNumber: currentRevision,
|
||||||
emailAddressList: privateEmailAddresses,
|
emailAddressList: privateEmailAddresses,
|
||||||
privateEmailDomainList: [], // Empty on purpose, API will not use this for vault updates
|
// TODO: add public RSA encryption key to payload when implementing vault creation from mobile app. Currently only web app does this.
|
||||||
publicEmailDomainList: [], // Empty on purpose, API will not use this for vault updates
|
encryptionPublicKey: "",
|
||||||
encryptionPublicKey: "", // Empty on purpose, only required if new public/private key pair is generated
|
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
username: username,
|
username: username,
|
||||||
version: dbVersion,
|
version: dbVersion,
|
||||||
client: client
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public struct VaultData: Codable {
|
|||||||
public let credentialsCount: Int
|
public let credentialsCount: Int
|
||||||
public let emailAddressList: [String]
|
public let emailAddressList: [String]
|
||||||
public let privateEmailDomainList: [String]
|
public let privateEmailDomainList: [String]
|
||||||
|
public let hiddenPrivateEmailDomainList: [String]
|
||||||
public let publicEmailDomainList: [String]
|
public let publicEmailDomainList: [String]
|
||||||
public let createdAt: String
|
public let createdAt: String
|
||||||
public let updatedAt: String
|
public let updatedAt: String
|
||||||
@@ -177,11 +178,31 @@ extension VaultStore {
|
|||||||
try storeEncryptedDatabase(vault.vault.blob)
|
try storeEncryptedDatabase(vault.vault.blob)
|
||||||
setCurrentVaultRevisionNumber(newRevision)
|
setCurrentVaultRevisionNumber(newRevision)
|
||||||
|
|
||||||
|
// Store vault metadata (public/private email domains)
|
||||||
|
let metadata = VaultMetadata(
|
||||||
|
publicEmailDomains: vault.vault.publicEmailDomainList,
|
||||||
|
privateEmailDomains: vault.vault.privateEmailDomainList,
|
||||||
|
hiddenPrivateEmailDomains: vault.vault.hiddenPrivateEmailDomainList,
|
||||||
|
vaultRevisionNumber: newRevision
|
||||||
|
)
|
||||||
|
try storeVaultMetadata(metadata)
|
||||||
|
|
||||||
if isVaultUnlocked {
|
if isVaultUnlocked {
|
||||||
try unlockVault()
|
try unlockVault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store vault metadata
|
||||||
|
private func storeVaultMetadata(_ metadata: VaultMetadata) throws {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
guard let metadataData = try? encoder.encode(metadata),
|
||||||
|
let metadataJson = String(data: metadataData, encoding: .utf8) else {
|
||||||
|
throw VaultSyncError.parseError(message: "Failed to encode vault metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
try storeMetadata(metadataJson)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse vault response from JSON
|
/// Parse vault response from JSON
|
||||||
private func parseVaultResponse(_ body: String) throws -> VaultResponse {
|
private func parseVaultResponse(_ body: String) throws -> VaultResponse {
|
||||||
guard let vaultData = body.data(using: .utf8) else {
|
guard let vaultData = body.data(using: .utf8) else {
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class SqliteClient {
|
|||||||
* Store the vault metadata via the native code implementation.
|
* Store the vault metadata via the native code implementation.
|
||||||
*
|
*
|
||||||
* Metadata is stored in plain text in UserDefaults. The metadata consists of the following:
|
* Metadata is stored in plain text in UserDefaults. The metadata consists of the following:
|
||||||
* - public and private email domains
|
* - public email domains
|
||||||
|
* - private email domains
|
||||||
|
* - hidden private email domains
|
||||||
* - vault revision number
|
* - vault revision number
|
||||||
*/
|
*/
|
||||||
public async storeMetadata(metadata: string): Promise<void> {
|
public async storeMetadata(metadata: string): Promise<void> {
|
||||||
@@ -77,7 +79,7 @@ class SqliteClient {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { privateEmailDomains, publicEmailDomains } = metadata;
|
const { privateEmailDomains, publicEmailDomains, hiddenPrivateEmailDomains } = metadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a domain is valid (not empty, not 'DISABLED.TLD', and exists in either private or public domains)
|
* Check if a domain is valid (not empty, not 'DISABLED.TLD', and exists in either private or public domains)
|
||||||
@@ -98,7 +100,8 @@ class SqliteClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If default domain is not valid, fall back to first available private domain
|
// If default domain is not valid, fall back to first available private domain
|
||||||
const firstPrivate = privateEmailDomains?.find(isValidDomain);
|
// Filter out hidden private domains from the list of private domains
|
||||||
|
const firstPrivate = privateEmailDomains?.filter(domain => !hiddenPrivateEmailDomains?.includes(domain)).find(isValidDomain);
|
||||||
if (firstPrivate) {
|
if (firstPrivate) {
|
||||||
return firstPrivate;
|
return firstPrivate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,27 +314,6 @@ export class WebApiService {
|
|||||||
return this.get<AuthLogModel[]>('Security/authlogs');
|
return this.get<AuthLogModel[]>('Security/authlogs');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the vault response and returns an error message if validation fails
|
|
||||||
*/
|
|
||||||
public validateVaultResponse(vaultResponseJson: VaultResponse): string | null {
|
|
||||||
/**
|
|
||||||
* Status 0 = OK, vault is ready.
|
|
||||||
* Status 1 = Merge required, which only the web client supports.
|
|
||||||
* Status 2 = Outdated, which means the local vault is outdated and the client should fetch the latest vault from the server before saving can continue.
|
|
||||||
*/
|
|
||||||
if (vaultResponseJson.status === 1) {
|
|
||||||
// Note: vault merge is no longer allowed by the API as of 0.20.0, updates with the same revision number are rejected. So this check can be removed later.
|
|
||||||
return i18n.t('vault.errors.vaultOutdated');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vaultResponseJson.status === 2) {
|
|
||||||
return i18n.t('vault.errors.vaultOutdated');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the currently configured API URL from native storage.
|
* Get the currently configured API URL from native storage.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
type VaultMetadata = {
|
type VaultMetadata = {
|
||||||
publicEmailDomains: string[];
|
publicEmailDomains: string[];
|
||||||
privateEmailDomains: string[];
|
privateEmailDomains: string[];
|
||||||
|
hiddenPrivateEmailDomains: string[];
|
||||||
vaultRevisionNumber: number;
|
vaultRevisionNumber: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -28,18 +28,18 @@ type ApiErrorResponse = {
|
|||||||
* Vault type.
|
* Vault type.
|
||||||
*/
|
*/
|
||||||
type Vault = {
|
type Vault = {
|
||||||
blob: string;
|
|
||||||
createdAt: string;
|
|
||||||
credentialsCount: number;
|
|
||||||
currentRevisionNumber: number;
|
|
||||||
emailAddressList: string[];
|
|
||||||
privateEmailDomainList: string[];
|
|
||||||
publicEmailDomainList: string[];
|
|
||||||
encryptionPublicKey: string;
|
|
||||||
updatedAt: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
|
blob: string;
|
||||||
version: string;
|
version: string;
|
||||||
client: string;
|
currentRevisionNumber: number;
|
||||||
|
credentialsCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
encryptionPublicKey?: string;
|
||||||
|
emailAddressList?: string[];
|
||||||
|
privateEmailDomainList?: string[];
|
||||||
|
hiddenPrivateEmailDomainList?: string[];
|
||||||
|
publicEmailDomainList?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,4 +23,10 @@ public class Config : SharedConfig
|
|||||||
/// Gets or sets the list of private email domains that are available.
|
/// Gets or sets the list of private email domains that are available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> PrivateEmailDomains { get; set; } = [];
|
public List<string> PrivateEmailDomains { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of private email domains that should be hidden from UI components.
|
||||||
|
/// These domains still function as private email domains but are not shown in domain selection dropdowns.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> HiddenPrivateEmailDomains { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,11 +89,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
|||||||
Blob = string.Empty,
|
Blob = string.Empty,
|
||||||
Version = string.Empty,
|
Version = string.Empty,
|
||||||
CurrentRevisionNumber = 0,
|
CurrentRevisionNumber = 0,
|
||||||
EncryptionPublicKey = string.Empty,
|
|
||||||
CredentialsCount = 0,
|
CredentialsCount = 0,
|
||||||
EmailAddressList = [],
|
|
||||||
PrivateEmailDomainList = [],
|
|
||||||
PublicEmailDomainList = [],
|
|
||||||
CreatedAt = DateTime.MinValue,
|
CreatedAt = DateTime.MinValue,
|
||||||
UpdatedAt = DateTime.MinValue,
|
UpdatedAt = DateTime.MinValue,
|
||||||
},
|
},
|
||||||
@@ -121,6 +117,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
|||||||
|
|
||||||
// Get dynamic list of private email domains from config.
|
// Get dynamic list of private email domains from config.
|
||||||
var privateEmailDomainList = config.PrivateEmailDomains;
|
var privateEmailDomainList = config.PrivateEmailDomains;
|
||||||
|
var hiddenPrivateEmailDomainList = config.HiddenPrivateEmailDomains;
|
||||||
|
|
||||||
// Hardcoded list of public (SpamOK) email domains that are available to the client.
|
// Hardcoded list of public (SpamOK) email domains that are available to the client.
|
||||||
var publicEmailDomainList = new List<string>(["spamok.com", "solarflarecorp.com", "spamok.nl", "3060.nl",
|
var publicEmailDomainList = new List<string>(["spamok.com", "solarflarecorp.com", "spamok.nl", "3060.nl",
|
||||||
@@ -137,8 +134,8 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
|||||||
CurrentRevisionNumber = vault.RevisionNumber,
|
CurrentRevisionNumber = vault.RevisionNumber,
|
||||||
EncryptionPublicKey = string.Empty,
|
EncryptionPublicKey = string.Empty,
|
||||||
CredentialsCount = 0,
|
CredentialsCount = 0,
|
||||||
EmailAddressList = [],
|
|
||||||
PrivateEmailDomainList = privateEmailDomainList,
|
PrivateEmailDomainList = privateEmailDomainList,
|
||||||
|
HiddenPrivateEmailDomainList = hiddenPrivateEmailDomainList,
|
||||||
PublicEmailDomainList = publicEmailDomainList,
|
PublicEmailDomainList = publicEmailDomainList,
|
||||||
CreatedAt = vault.CreatedAt,
|
CreatedAt = vault.CreatedAt,
|
||||||
UpdatedAt = vault.UpdatedAt,
|
UpdatedAt = vault.UpdatedAt,
|
||||||
@@ -176,11 +173,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
|||||||
Blob = x.VaultBlob,
|
Blob = x.VaultBlob,
|
||||||
Version = x.Version,
|
Version = x.Version,
|
||||||
CurrentRevisionNumber = x.RevisionNumber,
|
CurrentRevisionNumber = x.RevisionNumber,
|
||||||
EncryptionPublicKey = string.Empty,
|
|
||||||
CredentialsCount = 0,
|
CredentialsCount = 0,
|
||||||
EmailAddressList = [],
|
|
||||||
PrivateEmailDomainList = [],
|
|
||||||
PublicEmailDomainList = [],
|
|
||||||
CreatedAt = x.CreatedAt,
|
CreatedAt = x.CreatedAt,
|
||||||
UpdatedAt = x.UpdatedAt,
|
UpdatedAt = x.UpdatedAt,
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ var privateEmailDomains = Environment.GetEnvironmentVariable("PRIVATE_EMAIL_DOMA
|
|||||||
.Where(d => !string.IsNullOrWhiteSpace(d));
|
.Where(d => !string.IsNullOrWhiteSpace(d));
|
||||||
config.PrivateEmailDomains = privateEmailDomains?.ToList() ?? new List<string>();
|
config.PrivateEmailDomains = privateEmailDomains?.ToList() ?? new List<string>();
|
||||||
|
|
||||||
|
var hiddenPrivateEmailDomains = Environment.GetEnvironmentVariable("HIDDEN_PRIVATE_EMAIL_DOMAINS")?
|
||||||
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(d => d.Trim())
|
||||||
|
.Where(d => !string.IsNullOrWhiteSpace(d));
|
||||||
|
config.HiddenPrivateEmailDomains = hiddenPrivateEmailDomains?.ToList() ?? new List<string>();
|
||||||
|
|
||||||
var ipLoggingEnabled = Environment.GetEnvironmentVariable("IP_LOGGING_ENABLED") ?? "false";
|
var ipLoggingEnabled = Environment.GetEnvironmentVariable("IP_LOGGING_ENABLED") ?? "false";
|
||||||
config.IpLoggingEnabled = bool.Parse(ipLoggingEnabled);
|
config.IpLoggingEnabled = bool.Parse(ipLoggingEnabled);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"JWT_KEY": "12345678901234567890123456789012",
|
"JWT_KEY": "12345678901234567890123456789012",
|
||||||
"DATA_PROTECTION_CERT_PASS": "Development",
|
"DATA_PROTECTION_CERT_PASS": "Development",
|
||||||
"PUBLIC_REGISTRATION_ENABLED": "true",
|
"PUBLIC_REGISTRATION_ENABLED": "true",
|
||||||
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld",
|
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||||
|
"HIDDEN_PRIVATE_EMAIL_DOMAINS": "disabled.tld",
|
||||||
"IP_LOGGING_ENABLED": "true"
|
"IP_LOGGING_ENABLED": "true"
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
"JWT_KEY": "12345678901234567890123456789012",
|
"JWT_KEY": "12345678901234567890123456789012",
|
||||||
"DATA_PROTECTION_CERT_PASS": "Development",
|
"DATA_PROTECTION_CERT_PASS": "Development",
|
||||||
"PUBLIC_REGISTRATION_ENABLED": "true",
|
"PUBLIC_REGISTRATION_ENABLED": "true",
|
||||||
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld",
|
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||||
|
"HIDDEN_PRIVATE_EMAIL_DOMAINS": "disabled.tld",
|
||||||
"IP_LOGGING_ENABLED": "true"
|
"IP_LOGGING_ENABLED": "true"
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ public class Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> PrivateEmailDomains { get; set; } = [];
|
public List<string> PrivateEmailDomains { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of private email domains that should be hidden from UI components.
|
||||||
|
/// These domains still function as private email domains but are not shown in domain selection dropdowns.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> HiddenPrivateEmailDomains { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the list of public email domains that are allowed to be used by the client vault users.
|
/// Gets or sets the list of public email domains that are allowed to be used by the client vault users.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -99,7 +99,9 @@
|
|||||||
private string SelectedDomain = string.Empty;
|
private string SelectedDomain = string.Empty;
|
||||||
private bool IsPopupVisible = false;
|
private bool IsPopupVisible = false;
|
||||||
|
|
||||||
private List<string> PrivateDomains => Config.PrivateEmailDomains;
|
private List<string> PrivateDomains => Config.PrivateEmailDomains
|
||||||
|
.Where(d => !Config.HiddenPrivateEmailDomains.Contains(d))
|
||||||
|
.ToList();
|
||||||
private List<string> PublicDomains => Config.PublicEmailDomains;
|
private List<string> PublicDomains => Config.PublicEmailDomains;
|
||||||
|
|
||||||
private bool ShowPrivateDomains => PrivateDomains.Count > 0 && !(PrivateDomains.Count == 1 && PrivateDomains[0] == "DISABLED.TLD");
|
private bool ShowPrivateDomains => PrivateDomains.Count > 0 && !(PrivateDomains.Count == 1 && PrivateDomains[0] == "DISABLED.TLD");
|
||||||
|
|||||||
@@ -125,7 +125,9 @@
|
|||||||
@code {
|
@code {
|
||||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.General", "AliasVault.Client");
|
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.General", "AliasVault.Client");
|
||||||
|
|
||||||
private List<string> PrivateDomains => Config.PrivateEmailDomains;
|
private List<string> PrivateDomains => Config.PrivateEmailDomains
|
||||||
|
.Where(d => !Config.HiddenPrivateEmailDomains.Contains(d))
|
||||||
|
.ToList();
|
||||||
private List<string> PublicDomains => Config.PublicEmailDomains;
|
private List<string> PublicDomains => Config.PublicEmailDomains;
|
||||||
|
|
||||||
private string DefaultEmailDomain { get; set; } = string.Empty;
|
private string DefaultEmailDomain { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ else
|
|||||||
CredentialsCount = vault.CredentialsCount,
|
CredentialsCount = vault.CredentialsCount,
|
||||||
EmailAddressList = vault.EmailAddressList,
|
EmailAddressList = vault.EmailAddressList,
|
||||||
PrivateEmailDomainList = [],
|
PrivateEmailDomainList = [],
|
||||||
|
HiddenPrivateEmailDomainList = [],
|
||||||
PublicEmailDomainList = [],
|
PublicEmailDomainList = [],
|
||||||
CreatedAt = vault.CreatedAt,
|
CreatedAt = vault.CreatedAt,
|
||||||
UpdatedAt = vault.UpdatedAt,
|
UpdatedAt = vault.UpdatedAt,
|
||||||
|
|||||||
@@ -490,6 +490,7 @@ public sealed class DbService : IDisposable
|
|||||||
CredentialsCount = credentialsCount,
|
CredentialsCount = credentialsCount,
|
||||||
EmailAddressList = emailAddresses,
|
EmailAddressList = emailAddresses,
|
||||||
PrivateEmailDomainList = [],
|
PrivateEmailDomainList = [],
|
||||||
|
HiddenPrivateEmailDomainList = [],
|
||||||
PublicEmailDomainList = [],
|
PublicEmailDomainList = [],
|
||||||
CreatedAt = currentDateTime,
|
CreatedAt = currentDateTime,
|
||||||
UpdatedAt = currentDateTime,
|
UpdatedAt = currentDateTime,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Set the default values
|
# Set the default values
|
||||||
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
||||||
|
DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS=""
|
||||||
DEFAULT_SUPPORT_EMAIL=""
|
DEFAULT_SUPPORT_EMAIL=""
|
||||||
DEFAULT_PUBLIC_REGISTRATION_ENABLED="true"
|
DEFAULT_PUBLIC_REGISTRATION_ENABLED="true"
|
||||||
|
|
||||||
# Use the provided environment variables if they exist, otherwise use defaults
|
# Use the provided environment variables if they exist, otherwise use defaults
|
||||||
PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}
|
PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS=${HIDDEN_PRIVATE_EMAIL_DOMAINS:-$DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS}
|
||||||
SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}
|
SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}
|
||||||
PUBLIC_REGISTRATION_ENABLED=${PUBLIC_REGISTRATION_ENABLED:-$DEFAULT_PUBLIC_REGISTRATION_ENABLED}
|
PUBLIC_REGISTRATION_ENABLED=${PUBLIC_REGISTRATION_ENABLED:-$DEFAULT_PUBLIC_REGISTRATION_ENABLED}
|
||||||
|
|
||||||
@@ -37,9 +39,25 @@ else
|
|||||||
json_array=$(echo $PRIVATE_EMAIL_DOMAINS | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
json_array=$(echo $PRIVATE_EMAIL_DOMAINS | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Handle empty HIDDEN_PRIVATE_EMAIL_DOMAINS by defaulting to empty array
|
||||||
|
if [ -z "$HIDDEN_PRIVATE_EMAIL_DOMAINS" ]; then
|
||||||
|
hidden_json_array="[]"
|
||||||
|
else
|
||||||
|
# Convert comma-separated list to JSON array
|
||||||
|
hidden_json_array=$(echo $HIDDEN_PRIVATE_EMAIL_DOMAINS | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
||||||
|
fi
|
||||||
|
|
||||||
# Use sed to update the PrivateEmailDomains field in appsettings.json
|
# Use sed to update the PrivateEmailDomains field in appsettings.json
|
||||||
sed -i.bak "s|\"PrivateEmailDomains\": \[.*\]|\"PrivateEmailDomains\": $json_array|" /usr/share/nginx/html/appsettings.json
|
sed -i.bak "s|\"PrivateEmailDomains\": \[.*\]|\"PrivateEmailDomains\": $json_array|" /usr/share/nginx/html/appsettings.json
|
||||||
|
|
||||||
|
# Add HiddenPrivateEmailDomains field if it doesn't exist, or update it if it does
|
||||||
|
if grep -q "HiddenPrivateEmailDomains" /usr/share/nginx/html/appsettings.json; then
|
||||||
|
sed -i "s|\"HiddenPrivateEmailDomains\": \[.*\]|\"HiddenPrivateEmailDomains\": $hidden_json_array|" /usr/share/nginx/html/appsettings.json
|
||||||
|
else
|
||||||
|
# Insert HiddenPrivateEmailDomains after PrivateEmailDomains
|
||||||
|
sed -i "s|\"PrivateEmailDomains\": $json_array|\"PrivateEmailDomains\": $json_array,\n \"HiddenPrivateEmailDomains\": $hidden_json_array|" /usr/share/nginx/html/appsettings.json
|
||||||
|
fi
|
||||||
|
|
||||||
# Update support email in appsettings.json
|
# Update support email in appsettings.json
|
||||||
if [ ! -z "$SUPPORT_EMAIL" ]; then
|
if [ ! -z "$SUPPORT_EMAIL" ]; then
|
||||||
sed -i "s|\"SupportEmail\": \".*\"|\"SupportEmail\": \"$SUPPORT_EMAIL\"|g" /usr/share/nginx/html/appsettings.json
|
sed -i "s|\"SupportEmail\": \".*\"|\"SupportEmail\": \"$SUPPORT_EMAIL\"|g" /usr/share/nginx/html/appsettings.json
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ApiUrl": "http://localhost:5092",
|
"ApiUrl": "http://localhost:5092",
|
||||||
"PrivateEmailDomains": ["example.tld"],
|
"PrivateEmailDomains": ["example.tld", "example2.tld", "disabled.tld"],
|
||||||
|
"HiddenPrivateEmailDomains": ["disabled.tld"],
|
||||||
"SupportEmail": "support@example.tld",
|
"SupportEmail": "support@example.tld",
|
||||||
"PublicRegistrationEnabled": "true"
|
"PublicRegistrationEnabled": "true"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"DOTNET_ENVIRONMENT": "Development",
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
"PRIVATE_EMAIL_DOMAINS": "example.tld",
|
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||||
"SMTP_TLS_ENABLED": "false"
|
"SMTP_TLS_ENABLED": "false"
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"DOTNET_ENVIRONMENT": "Development",
|
"DOTNET_ENVIRONMENT": "Development",
|
||||||
"PRIVATE_EMAIL_DOMAINS": "example.tld",
|
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||||
"SMTP_TLS_ENABLED": "false"
|
"SMTP_TLS_ENABLED": "false"
|
||||||
},
|
},
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ namespace AliasVault.Shared.Models.WebApi.Vault;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Vault
|
public class Vault
|
||||||
{
|
{
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Required properties, always part of the vault get/set model.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the username that owns the vault.
|
/// Gets or sets the username that owns the vault.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,32 +38,12 @@ public class Vault
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required long CurrentRevisionNumber { get; set; }
|
public required long CurrentRevisionNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the public encryption key that server requires to encrypt user data such as received emails.
|
|
||||||
/// </summary>
|
|
||||||
public required string EncryptionPublicKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the number of credentials stored in the vault. This anonymous data is used in case a vault back-up
|
/// Gets or sets the number of credentials stored in the vault. This anonymous data is used in case a vault back-up
|
||||||
/// needs to be restored to get a better idea of the vault size.
|
/// needs to be restored to get a better idea of the vault size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required int CredentialsCount { get; set; }
|
public required int CredentialsCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of email addresses that are used in the vault and should be registered on the server.
|
|
||||||
/// </summary>
|
|
||||||
public required List<string> EmailAddressList { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of private email domains that are available.
|
|
||||||
/// </summary>
|
|
||||||
public required List<string> PrivateEmailDomainList { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of public email domains that are available.
|
|
||||||
/// </summary>
|
|
||||||
public required List<string> PublicEmailDomainList { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the date and time of creation.
|
/// Gets or sets the date and time of creation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -69,4 +53,34 @@ public class Vault
|
|||||||
/// Gets or sets the date and time of last update.
|
/// Gets or sets the date and time of last update.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required DateTime UpdatedAt { get; set; }
|
public required DateTime UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Optional properties, only part of the vault get/set model if available and applicable.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the public encryption key that server requires to encrypt user data such as received emails.
|
||||||
|
/// </summary>
|
||||||
|
public string EncryptionPublicKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of email addresses that are used in the vault and should be registered on the server.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> EmailAddressList { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of private email domains that are available.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> PrivateEmailDomainList { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of private email domains that should be hidden from UI components.
|
||||||
|
/// These domains still function as private email domains but are not shown in domain selection dropdowns.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> HiddenPrivateEmailDomainList { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of public email domains that are available.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> PublicEmailDomainList { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,10 @@ public class ClientPlaywrightTest : PlaywrightTest
|
|||||||
ApiBaseUrl = "http://localhost:" + apiPort + "/";
|
ApiBaseUrl = "http://localhost:" + apiPort + "/";
|
||||||
|
|
||||||
// Set environment variables for the API.
|
// Set environment variables for the API.
|
||||||
string[] privateEmailDomains = ["example.tld", "example2.tld"];
|
string[] privateEmailDomains = ["example.tld", "example2.tld","disabled.tld"];
|
||||||
Environment.SetEnvironmentVariable("PRIVATE_EMAIL_DOMAINS", string.Join(",", privateEmailDomains));
|
Environment.SetEnvironmentVariable("PRIVATE_EMAIL_DOMAINS", string.Join(",", privateEmailDomains));
|
||||||
|
string[] hiddenPrivateEmailDomains = ["disabled.tld"];
|
||||||
|
Environment.SetEnvironmentVariable("HIDDEN_PRIVATE_EMAIL_DOMAINS", string.Join(",", hiddenPrivateEmailDomains));
|
||||||
|
|
||||||
// Start WebAPI in-memory.
|
// Start WebAPI in-memory.
|
||||||
_apiFactory.Port = apiPort;
|
_apiFactory.Port = apiPort;
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ ENV ALIASVAULT_VERBOSITY=0 \
|
|||||||
IP_LOGGING_ENABLED=true \
|
IP_LOGGING_ENABLED=true \
|
||||||
SUPPORT_EMAIL="" \
|
SUPPORT_EMAIL="" \
|
||||||
PRIVATE_EMAIL_DOMAINS="" \
|
PRIVATE_EMAIL_DOMAINS="" \
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS="" \
|
||||||
HOSTNAME=localhost \
|
HOSTNAME=localhost \
|
||||||
POSTGRES_HOST=localhost \
|
POSTGRES_HOST=localhost \
|
||||||
POSTGRES_PORT=5432 \
|
POSTGRES_PORT=5432 \
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ done
|
|||||||
export ASPNETCORE_URLS="http://0.0.0.0:3001"
|
export ASPNETCORE_URLS="http://0.0.0.0:3001"
|
||||||
export ASPNETCORE_PATHBASE="/api"
|
export ASPNETCORE_PATHBASE="/api"
|
||||||
export PRIVATE_EMAIL_DOMAINS="${PRIVATE_EMAIL_DOMAINS:-}"
|
export PRIVATE_EMAIL_DOMAINS="${PRIVATE_EMAIL_DOMAINS:-}"
|
||||||
|
export HIDDEN_PRIVATE_EMAIL_DOMAINS="${HIDDEN_PRIVATE_EMAIL_DOMAINS:-}"
|
||||||
export PUBLIC_REGISTRATION_ENABLED="${PUBLIC_REGISTRATION_ENABLED:-true}"
|
export PUBLIC_REGISTRATION_ENABLED="${PUBLIC_REGISTRATION_ENABLED:-true}"
|
||||||
export IP_LOGGING_ENABLED="${IP_LOGGING_ENABLED:-true}"
|
export IP_LOGGING_ENABLED="${IP_LOGGING_ENABLED:-true}"
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ done
|
|||||||
|
|
||||||
# Client service entrypoint
|
# Client service entrypoint
|
||||||
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
||||||
|
DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS=""
|
||||||
DEFAULT_SUPPORT_EMAIL=""
|
DEFAULT_SUPPORT_EMAIL=""
|
||||||
PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}
|
PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS=${HIDDEN_PRIVATE_EMAIL_DOMAINS:-$DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS}
|
||||||
SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}
|
SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}
|
||||||
PUBLIC_REGISTRATION_ENABLED=${PUBLIC_REGISTRATION_ENABLED:-true}
|
PUBLIC_REGISTRATION_ENABLED=${PUBLIC_REGISTRATION_ENABLED:-true}
|
||||||
|
|
||||||
@@ -58,10 +60,19 @@ else
|
|||||||
json_array=$(echo "$PRIVATE_EMAIL_DOMAINS" | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
json_array=$(echo "$PRIVATE_EMAIL_DOMAINS" | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Convert comma-separated HIDDEN_PRIVATE_EMAIL_DOMAINS to JSON array
|
||||||
|
if [ -z "$HIDDEN_PRIVATE_EMAIL_DOMAINS" ]; then
|
||||||
|
hidden_json_array="[]"
|
||||||
|
else
|
||||||
|
# Convert comma-separated list to JSON array
|
||||||
|
hidden_json_array=$(echo "$HIDDEN_PRIVATE_EMAIL_DOMAINS" | awk '{split($0,a,","); printf "["; for(i=1;i<=length(a);i++) {printf "\"%s\"", a[i]; if(i<length(a)) printf ","} printf "]"}')
|
||||||
|
fi
|
||||||
|
|
||||||
# Create JSON with environment variables
|
# Create JSON with environment variables
|
||||||
cat > /app/client/wwwroot/appsettings.json << EOF
|
cat > /app/client/wwwroot/appsettings.json << EOF
|
||||||
{
|
{
|
||||||
"PrivateEmailDomains": $json_array,
|
"PrivateEmailDomains": $json_array,
|
||||||
|
"HiddenPrivateEmailDomains": $hidden_json_array,
|
||||||
"SupportEmail": "$SUPPORT_EMAIL",
|
"SupportEmail": "$SUPPORT_EMAIL",
|
||||||
"PublicRegistrationEnabled": "$PUBLIC_REGISTRATION_ENABLED"
|
"PublicRegistrationEnabled": "$PUBLIC_REGISTRATION_ENABLED"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ for i in {1..30}; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
export PRIVATE_EMAIL_DOMAINS="${PRIVATE_EMAIL_DOMAINS:-}"
|
export PRIVATE_EMAIL_DOMAINS="${PRIVATE_EMAIL_DOMAINS:-}"
|
||||||
|
export HIDDEN_PRIVATE_EMAIL_DOMAINS="${HIDDEN_PRIVATE_EMAIL_DOMAINS:-}"
|
||||||
export SMTP_TLS_ENABLED="${SMTP_TLS_ENABLED:-false}"
|
export SMTP_TLS_ENABLED="${SMTP_TLS_ENABLED:-false}"
|
||||||
|
|
||||||
# Set .NET logging level based on verbosity
|
# Set .NET logging level based on verbosity
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ services:
|
|||||||
FORCE_HTTPS_REDIRECT: "false"
|
FORCE_HTTPS_REDIRECT: "false"
|
||||||
SUPPORT_EMAIL: ""
|
SUPPORT_EMAIL: ""
|
||||||
PRIVATE_EMAIL_DOMAINS: ""
|
PRIVATE_EMAIL_DOMAINS: ""
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS: ""
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
avdata:
|
avdata:
|
||||||
|
|||||||
@@ -27,3 +27,4 @@ services:
|
|||||||
FORCE_HTTPS_REDIRECT: "false"
|
FORCE_HTTPS_REDIRECT: "false"
|
||||||
SUPPORT_EMAIL: ""
|
SUPPORT_EMAIL: ""
|
||||||
PRIVATE_EMAIL_DOMAINS: ""
|
PRIVATE_EMAIL_DOMAINS: ""
|
||||||
|
HIDDEN_PRIVATE_EMAIL_DOMAINS: ""
|
||||||
|
|||||||
@@ -3183,6 +3183,12 @@ check_and_populate_env() {
|
|||||||
printf " Set PRIVATE_EMAIL_DOMAINS\n"
|
printf " Set PRIVATE_EMAIL_DOMAINS\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# HIDDEN_PRIVATE_EMAIL_DOMAINS
|
||||||
|
if ! grep -q "^HIDDEN_PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE"; then
|
||||||
|
update_env_var "HIDDEN_PRIVATE_EMAIL_DOMAINS" ""
|
||||||
|
printf " Set HIDDEN_PRIVATE_EMAIL_DOMAINS\n"
|
||||||
|
fi
|
||||||
|
|
||||||
# HTTP_PORT
|
# HTTP_PORT
|
||||||
if ! grep -q "^HTTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
if ! grep -q "^HTTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||||
update_env_var "HTTP_PORT" "80"
|
update_env_var "HTTP_PORT" "80"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export type VaultMetadata = {
|
export type VaultMetadata = {
|
||||||
publicEmailDomains: string[],
|
publicEmailDomains: string[],
|
||||||
privateEmailDomains: string[],
|
privateEmailDomains: string[],
|
||||||
|
hiddenPrivateEmailDomains: string[],
|
||||||
vaultRevisionNumber: number
|
vaultRevisionNumber: number
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
* Vault type.
|
* Vault type.
|
||||||
*/
|
*/
|
||||||
export type Vault = {
|
export type Vault = {
|
||||||
blob: string;
|
// Required properties, always part of the vault get/set model.
|
||||||
createdAt: string;
|
|
||||||
credentialsCount: number;
|
|
||||||
currentRevisionNumber: number;
|
|
||||||
emailAddressList: string[];
|
|
||||||
privateEmailDomainList: string[];
|
|
||||||
publicEmailDomainList: string[];
|
|
||||||
encryptionPublicKey: string;
|
|
||||||
updatedAt: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
|
blob: string;
|
||||||
version: string;
|
version: string;
|
||||||
client: string;
|
currentRevisionNumber: number;
|
||||||
|
credentialsCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
// Optional properties, only part of the vault get/set model if available and applicable.
|
||||||
|
encryptionPublicKey?: string;
|
||||||
|
emailAddressList?: string[];
|
||||||
|
privateEmailDomainList?: string[];
|
||||||
|
hiddenPrivateEmailDomainList?: string[];
|
||||||
|
publicEmailDomainList?: string[];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user