mirror of
https://github.com/lanedirt/AliasVault.git
synced 2025-12-05 19:07:26 -06:00
Update env variables and refactor metadata storage (#1371)
This commit is contained in:
10
.env.example
10
.env.example
@@ -37,11 +37,19 @@ FORCE_HTTPS_REDIRECT=true
|
||||
# your DNS. Please refer to the full documentation for more instructions on DNS:
|
||||
# https://docs.aliasvault.net/installation/install.html#3-email-server-setup
|
||||
#
|
||||
# Set the private email domains below that are allowed to be used (comma separated values).
|
||||
# Set the private email domains below that the server should accept incoming mail for (comma separated values).
|
||||
# Example: PRIVATE_EMAIL_DOMAINS=example.com,example2.org
|
||||
# To disable the private email domains feature, keep this empty.
|
||||
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.
|
||||
# ⚠️ 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.
|
||||
|
||||
@@ -105,6 +105,10 @@ export async function handleStoreVault(
|
||||
await storage.setItem('session:privateEmailDomains', vaultRequest.privateEmailDomainList);
|
||||
}
|
||||
|
||||
if (vaultRequest.hiddenPrivateEmailDomainList) {
|
||||
await storage.setItem('session:hiddenPrivateEmailDomains', vaultRequest.hiddenPrivateEmailDomainList);
|
||||
}
|
||||
|
||||
if (vaultRequest.vaultRevisionNumber) {
|
||||
await storage.setItem('session:vaultRevisionNumber', vaultRequest.vaultRevisionNumber);
|
||||
}
|
||||
@@ -168,6 +172,7 @@ export async function handleSyncVault(
|
||||
{ key: 'session:encryptedVault', value: vaultResponse.vault.blob },
|
||||
{ key: 'session:publicEmailDomains', value: vaultResponse.vault.publicEmailDomainList },
|
||||
{ key: 'session:privateEmailDomains', value: vaultResponse.vault.privateEmailDomainList },
|
||||
{ key: 'session:hiddenPrivateEmailDomains', value: vaultResponse.vault.hiddenPrivateEmailDomainList },
|
||||
{ key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber }
|
||||
]);
|
||||
}
|
||||
@@ -186,6 +191,7 @@ export async function handleGetVault(
|
||||
const encryptedVault = await storage.getItem('session:encryptedVault') as string;
|
||||
const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[];
|
||||
const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[];
|
||||
const hiddenPrivateEmailDomains = await storage.getItem('session:hiddenPrivateEmailDomains') as string[] ?? [];
|
||||
const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number;
|
||||
|
||||
if (!encryptedVault) {
|
||||
@@ -208,6 +214,7 @@ export async function handleGetVault(
|
||||
vault: decryptedVault,
|
||||
publicEmailDomains: publicEmailDomains ?? [],
|
||||
privateEmailDomains: privateEmailDomains ?? [],
|
||||
hiddenPrivateEmailDomains: hiddenPrivateEmailDomains ?? [],
|
||||
vaultRevisionNumber: vaultRevisionNumber ?? 0
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -229,6 +236,7 @@ export function handleClearVault(
|
||||
'session:encryptionKeyDerivationParams',
|
||||
'session:publicEmailDomains',
|
||||
'session:privateEmailDomains',
|
||||
'session:hiddenPrivateEmailDomains',
|
||||
'session:vaultRevisionNumber'
|
||||
]);
|
||||
|
||||
@@ -497,13 +505,11 @@ async function uploadNewVaultToServer(sqliteClient: SqliteClient) : Promise<Vaul
|
||||
credentialsCount: sqliteClient.getAllCredentials().length,
|
||||
currentRevisionNumber: vaultRevisionNumber,
|
||||
emailAddressList: emailAddresses,
|
||||
privateEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
|
||||
publicEmailDomainList: [], // Empty on purpose, API will not use this for vault updates.
|
||||
encryptionPublicKey: '', // Empty on purpose, only required if new public/private key pair is generated.
|
||||
client: '', // Empty on purpose, API will not use this for vault updates.
|
||||
updatedAt: new Date().toISOString(),
|
||||
username: username,
|
||||
version: (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(() => {});
|
||||
|
||||
@@ -45,18 +45,18 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
const [selectedDomain, setSelectedDomain] = useState('');
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
||||
const [hiddenPrivateEmailDomains, setHiddenPrivateEmailDomains] = useState<string[]>([]);
|
||||
const popupRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Get private email domains from vault metadata
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Load private email domains from vault metadata.
|
||||
* Load private email domains from vault metadata, excluding hidden ones.
|
||||
*/
|
||||
const loadDomains = async (): Promise<void> => {
|
||||
const metadata = await dbContext.getVaultMetadata();
|
||||
if (metadata?.privateEmailDomains) {
|
||||
setPrivateEmailDomains(metadata.privateEmailDomains);
|
||||
}
|
||||
setPrivateEmailDomains(metadata?.privateEmailDomains ?? []);
|
||||
setHiddenPrivateEmailDomains(metadata?.hiddenPrivateEmailDomains ?? []);
|
||||
};
|
||||
loadDomains();
|
||||
}, [dbContext]);
|
||||
@@ -84,9 +84,10 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
setLocalPart(local);
|
||||
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) ||
|
||||
privateEmailDomains.includes(domain);
|
||||
privateEmailDomains.includes(domain) ||
|
||||
hiddenPrivateEmailDomains.includes(domain);
|
||||
setIsCustomDomain(!isKnownDomain);
|
||||
} else {
|
||||
setLocalPart(value);
|
||||
@@ -102,7 +103,7 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [value, privateEmailDomains, showPrivateDomains]);
|
||||
}, [value, privateEmailDomains, hiddenPrivateEmailDomains, showPrivateDomains]);
|
||||
|
||||
// Handle local part changes
|
||||
const handleLocalPartChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -246,20 +247,22 @@ const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
{t('credentials.privateEmailDescription')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{privateEmailDomains.map((domain) => (
|
||||
<button
|
||||
key={domain}
|
||||
type="button"
|
||||
onClick={() => selectDomain(domain)}
|
||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
selectedDomain === domain
|
||||
? 'bg-primary-600 text-white hover:bg-primary-700'
|
||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
{domain}
|
||||
</button>
|
||||
))}
|
||||
{privateEmailDomains
|
||||
.filter((domain) => !hiddenPrivateEmailDomains.includes(domain))
|
||||
.map((domain) => (
|
||||
<button
|
||||
key={domain}
|
||||
type="button"
|
||||
onClick={() => selectDomain(domain)}
|
||||
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
selectedDomain === domain
|
||||
? 'bg-primary-600 text-white hover:bg-primary-700'
|
||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
{domain}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -62,8 +62,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
setDbInitialized(true);
|
||||
setDbAvailable(true);
|
||||
setVaultMetadata({
|
||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList,
|
||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList,
|
||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList ?? [],
|
||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList ?? [],
|
||||
hiddenPrivateEmailDomains: vaultResponse.vault.hiddenPrivateEmailDomainList ?? [],
|
||||
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
||||
});
|
||||
|
||||
@@ -74,6 +75,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
vaultBlob: vaultResponse.vault.blob,
|
||||
publicEmailDomainList: vaultResponse.vault.publicEmailDomainList,
|
||||
privateEmailDomainList: vaultResponse.vault.privateEmailDomainList,
|
||||
hiddenPrivateEmailDomainList: vaultResponse.vault.hiddenPrivateEmailDomainList,
|
||||
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
|
||||
};
|
||||
|
||||
@@ -96,6 +98,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
setVaultMetadata({
|
||||
publicEmailDomains: response.publicEmailDomains ?? [],
|
||||
privateEmailDomains: response.privateEmailDomains ?? [],
|
||||
hiddenPrivateEmailDomains: response.hiddenPrivateEmailDomains ?? [],
|
||||
vaultRevisionNumber: response.vaultRevisionNumber ?? 0,
|
||||
});
|
||||
} else {
|
||||
@@ -123,6 +126,7 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
setVaultMetadata({
|
||||
publicEmailDomains: vaultMetadata?.publicEmailDomains ?? [],
|
||||
privateEmailDomains: vaultMetadata?.privateEmailDomains ?? [],
|
||||
hiddenPrivateEmailDomains: vaultMetadata?.hiddenPrivateEmailDomains ?? [],
|
||||
vaultRevisionNumber: revisionNumber,
|
||||
});
|
||||
}, [vaultMetadata]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
type VaultMetadata = {
|
||||
publicEmailDomains: string[];
|
||||
privateEmailDomains: string[];
|
||||
hiddenPrivateEmailDomains: string[];
|
||||
vaultRevisionNumber: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,18 +28,18 @@ type ApiErrorResponse = {
|
||||
* Vault type.
|
||||
*/
|
||||
type Vault = {
|
||||
blob: string;
|
||||
createdAt: string;
|
||||
credentialsCount: number;
|
||||
currentRevisionNumber: number;
|
||||
emailAddressList: string[];
|
||||
privateEmailDomainList: string[];
|
||||
publicEmailDomainList: string[];
|
||||
encryptionPublicKey: string;
|
||||
updatedAt: string;
|
||||
username: string;
|
||||
blob: 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;
|
||||
publicEmailDomainList?: string[];
|
||||
privateEmailDomainList?: string[];
|
||||
hiddenPrivateEmailDomainList?: string[];
|
||||
vaultRevisionNumber?: number;
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ export type VaultResponse = {
|
||||
vault?: string,
|
||||
publicEmailDomains?: string[],
|
||||
privateEmailDomains?: string[],
|
||||
hiddenPrivateEmailDomains?: string[],
|
||||
vaultRevisionNumber?: number
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ class VaultMetadataManager(
|
||||
JSONObject().apply {
|
||||
put("publicEmailDomains", JSONArray(updatedMetadata.publicEmailDomains))
|
||||
put("privateEmailDomains", JSONArray(updatedMetadata.privateEmailDomains))
|
||||
put("hiddenPrivateEmailDomains", JSONArray(updatedMetadata.hiddenPrivateEmailDomains))
|
||||
put("vaultRevisionNumber", updatedMetadata.vaultRevisionNumber)
|
||||
}.toString(),
|
||||
)
|
||||
@@ -158,6 +159,9 @@ class VaultMetadataManager(
|
||||
privateEmailDomains = json.optJSONArray("privateEmailDomains")?.let { array ->
|
||||
List(array.length()) { i -> array.getString(i) }
|
||||
} ?: emptyList(),
|
||||
hiddenPrivateEmailDomains = json.optJSONArray("hiddenPrivateEmailDomains")?.let { array ->
|
||||
List(array.length()) { i -> array.getString(i) }
|
||||
} ?: emptyList(),
|
||||
vaultRevisionNumber = json.optInt("vaultRevisionNumber", 0),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -33,13 +33,10 @@ class VaultMutate(
|
||||
json.put("credentialsCount", vault.credentialsCount)
|
||||
json.put("currentRevisionNumber", vault.currentRevisionNumber)
|
||||
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("updatedAt", vault.updatedAt)
|
||||
json.put("username", vault.username)
|
||||
json.put("version", vault.version)
|
||||
json.put("client", vault.client)
|
||||
|
||||
val response = webApiService.executeRequest(
|
||||
method = "POST",
|
||||
@@ -124,8 +121,6 @@ class VaultMutate(
|
||||
} catch (e: Exception) {
|
||||
"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)
|
||||
dateFormat.timeZone = java.util.TimeZone.getTimeZone("UTC")
|
||||
@@ -137,13 +132,11 @@ class VaultMutate(
|
||||
credentialsCount = credentials.size,
|
||||
currentRevisionNumber = currentRevision,
|
||||
emailAddressList = privateEmailAddresses,
|
||||
privateEmailDomainList = emptyList(),
|
||||
publicEmailDomainList = emptyList(),
|
||||
// TODO: add public RSA encryption key to payload when implementing vault creation from mobile app. Currently only web app does this.
|
||||
encryptionPublicKey = "",
|
||||
updatedAt = now,
|
||||
username = username,
|
||||
version = dbVersion,
|
||||
client = client,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -157,13 +150,10 @@ class VaultMutate(
|
||||
val credentialsCount: Int,
|
||||
val currentRevisionNumber: Int,
|
||||
val emailAddressList: List<String>,
|
||||
val privateEmailDomainList: List<String>,
|
||||
val publicEmailDomainList: List<String>,
|
||||
val encryptionPublicKey: String,
|
||||
val updatedAt: String,
|
||||
val username: String,
|
||||
val version: String,
|
||||
val client: String,
|
||||
)
|
||||
|
||||
private data class VaultPostResponse(
|
||||
|
||||
@@ -173,12 +173,34 @@ class VaultSync(
|
||||
database.storeEncryptedDatabase(vault.vault.blob)
|
||||
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()) {
|
||||
// Re-unlock with new data
|
||||
// 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 {
|
||||
return try {
|
||||
val json = JSONObject(body)
|
||||
@@ -196,6 +218,12 @@ class VaultSync(
|
||||
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 publicArray = vaultJson.getJSONArray("publicEmailDomainList")
|
||||
for (i in 0 until publicArray.length()) {
|
||||
@@ -213,6 +241,7 @@ class VaultSync(
|
||||
credentialsCount = vaultJson.getInt("credentialsCount"),
|
||||
emailAddressList = emailList,
|
||||
privateEmailDomainList = privateList,
|
||||
hiddenPrivateEmailDomainList = hiddenPrivateList,
|
||||
publicEmailDomainList = publicList,
|
||||
createdAt = vaultJson.getString("createdAt"),
|
||||
updatedAt = vaultJson.getString("updatedAt"),
|
||||
@@ -253,6 +282,7 @@ class VaultSync(
|
||||
val credentialsCount: Int,
|
||||
val emailAddressList: List<String>,
|
||||
val privateEmailDomainList: List<String>,
|
||||
val hiddenPrivateEmailDomainList: List<String>,
|
||||
val publicEmailDomainList: List<String>,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
|
||||
@@ -14,6 +14,12 @@ data class VaultMetadata(
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,8 @@ class VaultStoreTest {
|
||||
val metadata = """
|
||||
{
|
||||
"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
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -261,7 +261,6 @@ export default function Initialize() : React.ReactNode {
|
||||
|
||||
// Now perform vault sync (network operations - these are skippable)
|
||||
await syncVault({
|
||||
initialSync: true,
|
||||
abortSignal: abortControllerRef.current.signal,
|
||||
/**
|
||||
* Handle the status update.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { StyleSheet, View, Text, SafeAreaView, TextInput, ActivityIndicator, Ani
|
||||
import { useApiUrl } from '@/utils/ApiUrlUtility';
|
||||
import ConversionUtility from '@/utils/ConversionUtility';
|
||||
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 { SrpUtility } from '@/utils/SrpUtility';
|
||||
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.
|
||||
* @param token - The 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 initiateLoginResponse - The initiate login response
|
||||
*/
|
||||
const processVaultResponse = async (
|
||||
token: string,
|
||||
refreshToken: string,
|
||||
vaultResponseJson: VaultResponse,
|
||||
passwordHashBase64: string,
|
||||
initiateLoginResponse: LoginResponse
|
||||
) : Promise<void> => {
|
||||
@@ -109,7 +107,6 @@ export default function LoginScreen() : React.ReactNode {
|
||||
await continueProcessVaultResponse(
|
||||
token,
|
||||
refreshToken,
|
||||
vaultResponseJson,
|
||||
passwordHashBase64,
|
||||
initiateLoginResponse
|
||||
);
|
||||
@@ -126,7 +123,6 @@ export default function LoginScreen() : React.ReactNode {
|
||||
await continueProcessVaultResponse(
|
||||
token,
|
||||
refreshToken,
|
||||
vaultResponseJson,
|
||||
passwordHashBase64,
|
||||
initiateLoginResponse
|
||||
);
|
||||
@@ -140,7 +136,6 @@ export default function LoginScreen() : React.ReactNode {
|
||||
await continueProcessVaultResponse(
|
||||
token,
|
||||
refreshToken,
|
||||
vaultResponseJson,
|
||||
passwordHashBase64,
|
||||
initiateLoginResponse
|
||||
);
|
||||
@@ -151,7 +146,6 @@ export default function LoginScreen() : React.ReactNode {
|
||||
* Continue processing the vault response after biometric choice
|
||||
* @param token - The 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 initiateLoginResponse - The initiate login response
|
||||
* @param encryptionKeyDerivationParams - The encryption key derivation parameters
|
||||
@@ -159,7 +153,6 @@ export default function LoginScreen() : React.ReactNode {
|
||||
const continueProcessVaultResponse = async (
|
||||
token: string,
|
||||
refreshToken: string,
|
||||
vaultResponseJson: VaultResponse,
|
||||
passwordHashBase64: string,
|
||||
initiateLoginResponse: LoginResponse
|
||||
) : Promise<void> => {
|
||||
@@ -169,20 +162,29 @@ export default function LoginScreen() : React.ReactNode {
|
||||
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 dbContext.storeEncryptionKey(passwordHashBase64);
|
||||
await dbContext.storeEncryptionKeyDerivationParams(encryptionKeyDerivationParams);
|
||||
await dbContext.initializeDatabase(vaultResponseJson);
|
||||
|
||||
let checkSuccess = true;
|
||||
/**
|
||||
* After setting auth tokens, execute a server status check immediately
|
||||
* 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({
|
||||
initialSync: true,
|
||||
/**
|
||||
* Update login status during sync.
|
||||
*/
|
||||
onStatus: (status) => {
|
||||
setLoginStatus(status);
|
||||
},
|
||||
/**
|
||||
* Handle the status update.
|
||||
*/
|
||||
@@ -218,6 +220,12 @@ export default function LoginScreen() : React.ReactNode {
|
||||
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();
|
||||
|
||||
authContext.setOfflineMode(false);
|
||||
@@ -286,23 +294,10 @@ export default function LoginScreen() : React.ReactNode {
|
||||
|
||||
setLoginStatus(t('auth.syncingVault'));
|
||||
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(
|
||||
validationResponse.token.token,
|
||||
validationResponse.token.refreshToken,
|
||||
vaultResponseJson,
|
||||
passwordHashBase64,
|
||||
initiateLoginResponse
|
||||
);
|
||||
@@ -357,21 +352,10 @@ export default function LoginScreen() : React.ReactNode {
|
||||
|
||||
setLoginStatus(t('auth.syncingVault'));
|
||||
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(
|
||||
validationResponse.token.token,
|
||||
validationResponse.token.refreshToken,
|
||||
vaultResponseJson,
|
||||
passwordHashBase64,
|
||||
initiateLoginResponse
|
||||
);
|
||||
|
||||
@@ -245,7 +245,6 @@ export default function ReinitializeScreen() : React.ReactNode {
|
||||
|
||||
// Now perform vault sync (network operations - these are skippable)
|
||||
await syncVault({
|
||||
initialSync: true,
|
||||
/**
|
||||
* Handle the status update.
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,7 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) : React.Rea
|
||||
}, [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> => {
|
||||
// Get private domains from stored metadata
|
||||
@@ -84,7 +84,9 @@ export const EmailPreview: React.FC<EmailPreviewProps> = ({ email }) : React.Rea
|
||||
return false;
|
||||
}
|
||||
|
||||
return metadata.privateEmailDomains.includes(emailAddress.split('@')[1]);
|
||||
const domain = emailAddress.split('@')[1];
|
||||
return metadata.privateEmailDomains.includes(domain) ||
|
||||
(metadata.hiddenPrivateEmailDomains || []).includes(domain);
|
||||
}, [dbContext]);
|
||||
|
||||
// Handle app state changes
|
||||
|
||||
@@ -49,18 +49,21 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
const [selectedDomain, setSelectedDomain] = useState('');
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [privateEmailDomains, setPrivateEmailDomains] = useState<string[]>([]);
|
||||
const [hiddenPrivateEmailDomains, setHiddenPrivateEmailDomains] = useState<string[]>([]);
|
||||
|
||||
// Get private email domains from vault metadata
|
||||
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> => {
|
||||
try {
|
||||
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) {
|
||||
console.error('Error loading email domains:', err);
|
||||
}
|
||||
@@ -91,9 +94,10 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
setLocalPart(local);
|
||||
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) ||
|
||||
privateEmailDomains.includes(domain);
|
||||
privateEmailDomains.includes(domain) ||
|
||||
hiddenPrivateEmailDomains.includes(domain);
|
||||
setIsCustomDomain(!isKnownDomain);
|
||||
} else {
|
||||
setLocalPart(value);
|
||||
@@ -108,7 +112,7 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [value, privateEmailDomains, showPrivateDomains]);
|
||||
}, [value, privateEmailDomains, hiddenPrivateEmailDomains, showPrivateDomains]);
|
||||
|
||||
// Handle local part changes
|
||||
const handleLocalPartChange = useCallback((newText: string) => {
|
||||
@@ -410,7 +414,7 @@ export const EmailDomainField: React.FC<EmailDomainFieldProps> = ({
|
||||
{t('credentials.privateEmailDescription')}
|
||||
</Text>
|
||||
<View style={styles.domainList}>
|
||||
{privateEmailDomains.map((domain) => (
|
||||
{privateEmailDomains.filter(domain => !hiddenPrivateEmailDomains.includes(domain)).map((domain) => (
|
||||
<TouchableOpacity
|
||||
key={domain}
|
||||
style={[
|
||||
|
||||
@@ -18,6 +18,8 @@ type DbContextType = {
|
||||
getVaultMetadata: () => Promise<VaultMetadata | null>;
|
||||
testDatabaseConnection: (derivedKey: string) => Promise<boolean>;
|
||||
unlockVault: () => Promise<boolean>;
|
||||
checkStoredVault: () => Promise<void>;
|
||||
setDatabaseAvailable: () => void;
|
||||
}
|
||||
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
const initializeDatabase = useCallback(async (vaultResponse: VaultResponse) => {
|
||||
const metadata: VaultMetadata = {
|
||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList,
|
||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList,
|
||||
publicEmailDomains: vaultResponse.vault.publicEmailDomainList ?? [],
|
||||
privateEmailDomains: vaultResponse.vault.privateEmailDomainList ?? [],
|
||||
hiddenPrivateEmailDomains: vaultResponse.vault.hiddenPrivateEmailDomainList ?? [],
|
||||
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.storeMetadata(JSON.stringify(metadata));
|
||||
|
||||
// Initialize the database in the native module
|
||||
// Unlock the vault to make it available for queries
|
||||
await unlockVault();
|
||||
|
||||
setDbInitialized(true);
|
||||
@@ -156,6 +161,15 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
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
|
||||
*/
|
||||
@@ -199,7 +213,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
unlockVault,
|
||||
storeEncryptionKey,
|
||||
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 (
|
||||
<DbContext.Provider value={contextValue}>
|
||||
|
||||
@@ -186,12 +186,10 @@ export const NavigationProvider: React.FC<{ children: React.ReactNode }> = ({ ch
|
||||
}
|
||||
|
||||
// Database connection failed, navigate to reinitialize flow
|
||||
console.log('database connection failed, navigating to reinitialize');
|
||||
router.replace('/reinitialize');
|
||||
}
|
||||
} catch {
|
||||
// Database query failed, navigate to reinitialize flow
|
||||
console.log('database query failed, navigating to reinitialize');
|
||||
router.replace('/reinitialize');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ export function useVaultMutate() : {
|
||||
currentRevisionNumber: currentRevision,
|
||||
emailAddressList: privateEmailAddresses,
|
||||
privateEmailDomainList: [],
|
||||
hiddenPrivateEmailDomainList: [],
|
||||
publicEmailDomainList: [],
|
||||
encryptionPublicKey: '',
|
||||
client: '',
|
||||
|
||||
@@ -16,16 +16,7 @@ import { VaultSyncErrorCode, getVaultSyncErrorCode } from '@/utils/types/errors/
|
||||
/**
|
||||
* Utility function to ensure a minimum time has elapsed for an operation
|
||||
*/
|
||||
const withMinimumDelay = async <T>(
|
||||
operation: () => Promise<T>,
|
||||
minDelayMs: number,
|
||||
enableDelay: boolean = true
|
||||
): Promise<T> => {
|
||||
if (!enableDelay) {
|
||||
// If delay is disabled, return the result immediately.
|
||||
return operation();
|
||||
}
|
||||
|
||||
const withMinimumDelay = async <T>(operation: () => Promise<T>, minDelayMs: number): Promise<T> => {
|
||||
const startTime = Date.now();
|
||||
const result = await operation();
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
@@ -38,7 +29,6 @@ const withMinimumDelay = async <T>(
|
||||
};
|
||||
|
||||
type VaultSyncOptions = {
|
||||
initialSync?: boolean;
|
||||
onSuccess?: (hasNewVault: boolean) => void;
|
||||
onError?: (error: string) => void;
|
||||
onStatus?: (message: string) => void;
|
||||
@@ -59,10 +49,7 @@ export const useVaultSync = () : {
|
||||
const dbContext = useDb();
|
||||
|
||||
const syncVault = useCallback(async (options: VaultSyncOptions = {}) => {
|
||||
const { initialSync = false, 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;
|
||||
const { onSuccess, onError, onStatus, onOffline, onUpgradeRequired, abortSignal } = options;
|
||||
|
||||
try {
|
||||
// Check if operation was aborted
|
||||
@@ -87,11 +74,6 @@ export const useVaultSync = () : {
|
||||
// Update status
|
||||
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
|
||||
if (abortSignal?.aborted) {
|
||||
console.debug('VaultSync: Operation aborted after status update');
|
||||
@@ -128,8 +110,7 @@ export const useVaultSync = () : {
|
||||
// Run downloadVault with a min delay for UX purposes
|
||||
await withMinimumDelay(
|
||||
() => NativeVaultManager.downloadVault(newRevision!),
|
||||
enableDelay ? 500 : 300,
|
||||
true
|
||||
300
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -183,11 +164,6 @@ export const useVaultSync = () : {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add artificial delay for initial sync UX
|
||||
if (enableDelay) {
|
||||
await new Promise(resolve => setTimeout(resolve, hasNewVault ? 1000 : 300));
|
||||
}
|
||||
|
||||
onSuccess?.(hasNewVault);
|
||||
|
||||
// Register credential identities after sync
|
||||
|
||||
@@ -3,11 +3,13 @@ import Foundation
|
||||
public struct VaultMetadata: Codable {
|
||||
public var publicEmailDomains: [String]?
|
||||
public var privateEmailDomains: [String]?
|
||||
public var hiddenPrivateEmailDomains: [String]?
|
||||
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.privateEmailDomains = privateEmailDomains
|
||||
self.hiddenPrivateEmailDomains = hiddenPrivateEmailDomains
|
||||
self.vaultRevisionNumber = vaultRevisionNumber
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ extension VaultStore {
|
||||
metadata = VaultMetadata(
|
||||
publicEmailDomains: [],
|
||||
privateEmailDomains: [],
|
||||
hiddenPrivateEmailDomains: [],
|
||||
vaultRevisionNumber: revisionNumber
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,10 @@ public struct VaultUpload: Codable {
|
||||
public let credentialsCount: Int
|
||||
public let currentRevisionNumber: Int
|
||||
public let emailAddressList: [String]
|
||||
public let privateEmailDomainList: [String]
|
||||
public let publicEmailDomainList: [String]
|
||||
public let encryptionPublicKey: String
|
||||
public let updatedAt: String
|
||||
public let username: String
|
||||
public let version: String
|
||||
public let client: String
|
||||
}
|
||||
|
||||
/// Vault POST response from API
|
||||
@@ -86,11 +83,6 @@ extension VaultStore {
|
||||
// Get database version
|
||||
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()
|
||||
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
let now = dateFormatter.string(from: Date())
|
||||
@@ -101,13 +93,11 @@ extension VaultStore {
|
||||
credentialsCount: credentials.count,
|
||||
currentRevisionNumber: currentRevision,
|
||||
emailAddressList: privateEmailAddresses,
|
||||
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
|
||||
// TODO: add public RSA encryption key to payload when implementing vault creation from mobile app. Currently only web app does this.
|
||||
encryptionPublicKey: "",
|
||||
updatedAt: now,
|
||||
username: username,
|
||||
version: dbVersion,
|
||||
client: client
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public struct VaultData: Codable {
|
||||
public let credentialsCount: Int
|
||||
public let emailAddressList: [String]
|
||||
public let privateEmailDomainList: [String]
|
||||
public let hiddenPrivateEmailDomainList: [String]
|
||||
public let publicEmailDomainList: [String]
|
||||
public let createdAt: String
|
||||
public let updatedAt: String
|
||||
@@ -177,11 +178,31 @@ extension VaultStore {
|
||||
try storeEncryptedDatabase(vault.vault.blob)
|
||||
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 {
|
||||
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
|
||||
private func parseVaultResponse(_ body: String) throws -> VaultResponse {
|
||||
guard let vaultData = body.data(using: .utf8) else {
|
||||
|
||||
@@ -30,7 +30,9 @@ class SqliteClient {
|
||||
* Store the vault metadata via the native code implementation.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
public async storeMetadata(metadata: string): Promise<void> {
|
||||
@@ -77,7 +79,7 @@ class SqliteClient {
|
||||
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)
|
||||
@@ -98,7 +100,8 @@ class SqliteClient {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return firstPrivate;
|
||||
}
|
||||
|
||||
@@ -314,27 +314,6 @@ export class WebApiService {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
type VaultMetadata = {
|
||||
publicEmailDomains: string[];
|
||||
privateEmailDomains: string[];
|
||||
hiddenPrivateEmailDomains: string[];
|
||||
vaultRevisionNumber: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,18 +28,18 @@ type ApiErrorResponse = {
|
||||
* Vault type.
|
||||
*/
|
||||
type Vault = {
|
||||
blob: string;
|
||||
createdAt: string;
|
||||
credentialsCount: number;
|
||||
currentRevisionNumber: number;
|
||||
emailAddressList: string[];
|
||||
privateEmailDomainList: string[];
|
||||
publicEmailDomainList: string[];
|
||||
encryptionPublicKey: string;
|
||||
updatedAt: string;
|
||||
username: string;
|
||||
blob: 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.
|
||||
/// </summary>
|
||||
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,
|
||||
Version = string.Empty,
|
||||
CurrentRevisionNumber = 0,
|
||||
EncryptionPublicKey = string.Empty,
|
||||
CredentialsCount = 0,
|
||||
EmailAddressList = [],
|
||||
PrivateEmailDomainList = [],
|
||||
PublicEmailDomainList = [],
|
||||
CreatedAt = 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.
|
||||
var privateEmailDomainList = config.PrivateEmailDomains;
|
||||
var hiddenPrivateEmailDomainList = config.HiddenPrivateEmailDomains;
|
||||
|
||||
// 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",
|
||||
@@ -137,8 +134,8 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
CurrentRevisionNumber = vault.RevisionNumber,
|
||||
EncryptionPublicKey = string.Empty,
|
||||
CredentialsCount = 0,
|
||||
EmailAddressList = [],
|
||||
PrivateEmailDomainList = privateEmailDomainList,
|
||||
HiddenPrivateEmailDomainList = hiddenPrivateEmailDomainList,
|
||||
PublicEmailDomainList = publicEmailDomainList,
|
||||
CreatedAt = vault.CreatedAt,
|
||||
UpdatedAt = vault.UpdatedAt,
|
||||
@@ -176,11 +173,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
|
||||
Blob = x.VaultBlob,
|
||||
Version = x.Version,
|
||||
CurrentRevisionNumber = x.RevisionNumber,
|
||||
EncryptionPublicKey = string.Empty,
|
||||
CredentialsCount = 0,
|
||||
EmailAddressList = [],
|
||||
PrivateEmailDomainList = [],
|
||||
PublicEmailDomainList = [],
|
||||
CreatedAt = x.CreatedAt,
|
||||
UpdatedAt = x.UpdatedAt,
|
||||
}).ToList(),
|
||||
|
||||
@@ -48,6 +48,12 @@ var privateEmailDomains = Environment.GetEnvironmentVariable("PRIVATE_EMAIL_DOMA
|
||||
.Where(d => !string.IsNullOrWhiteSpace(d));
|
||||
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";
|
||||
config.IpLoggingEnabled = bool.Parse(ipLoggingEnabled);
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"JWT_KEY": "12345678901234567890123456789012",
|
||||
"DATA_PROTECTION_CERT_PASS": "Development",
|
||||
"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"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
@@ -24,7 +25,8 @@
|
||||
"JWT_KEY": "12345678901234567890123456789012",
|
||||
"DATA_PROTECTION_CERT_PASS": "Development",
|
||||
"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"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
|
||||
@@ -23,6 +23,12 @@ public class Config
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the list of public email domains that are allowed to be used by the client vault users.
|
||||
/// </summary>
|
||||
|
||||
@@ -99,7 +99,9 @@
|
||||
private string SelectedDomain = string.Empty;
|
||||
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 bool ShowPrivateDomains => PrivateDomains.Count > 0 && !(PrivateDomains.Count == 1 && PrivateDomains[0] == "DISABLED.TLD");
|
||||
|
||||
@@ -125,7 +125,9 @@
|
||||
@code {
|
||||
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 string DefaultEmailDomain { get; set; } = string.Empty;
|
||||
|
||||
@@ -196,6 +196,7 @@ else
|
||||
CredentialsCount = vault.CredentialsCount,
|
||||
EmailAddressList = vault.EmailAddressList,
|
||||
PrivateEmailDomainList = [],
|
||||
HiddenPrivateEmailDomainList = [],
|
||||
PublicEmailDomainList = [],
|
||||
CreatedAt = vault.CreatedAt,
|
||||
UpdatedAt = vault.UpdatedAt,
|
||||
|
||||
@@ -490,6 +490,7 @@ public sealed class DbService : IDisposable
|
||||
CredentialsCount = credentialsCount,
|
||||
EmailAddressList = emailAddresses,
|
||||
PrivateEmailDomainList = [],
|
||||
HiddenPrivateEmailDomainList = [],
|
||||
PublicEmailDomainList = [],
|
||||
CreatedAt = currentDateTime,
|
||||
UpdatedAt = currentDateTime,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#!/bin/sh
|
||||
# Set the default values
|
||||
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
||||
DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS=""
|
||||
DEFAULT_SUPPORT_EMAIL=""
|
||||
DEFAULT_PUBLIC_REGISTRATION_ENABLED="true"
|
||||
|
||||
# Use the provided environment variables if they exist, otherwise use defaults
|
||||
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}
|
||||
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 "]"}')
|
||||
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
|
||||
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
|
||||
if [ ! -z "$SUPPORT_EMAIL" ]; then
|
||||
sed -i "s|\"SupportEmail\": \".*\"|\"SupportEmail\": \"$SUPPORT_EMAIL\"|g" /usr/share/nginx/html/appsettings.json
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ApiUrl": "http://localhost:5092",
|
||||
"PrivateEmailDomains": ["example.tld"],
|
||||
"PrivateEmailDomains": ["example.tld", "example2.tld", "disabled.tld"],
|
||||
"HiddenPrivateEmailDomains": ["disabled.tld"],
|
||||
"SupportEmail": "support@example.tld",
|
||||
"PublicRegistrationEnabled": "true"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"PRIVATE_EMAIL_DOMAINS": "example.tld",
|
||||
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||
"SMTP_TLS_ENABLED": "false"
|
||||
},
|
||||
"dotnetRunMessages": true
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"PRIVATE_EMAIL_DOMAINS": "example.tld",
|
||||
"PRIVATE_EMAIL_DOMAINS": "example.tld,example2.tld,disabled.tld",
|
||||
"SMTP_TLS_ENABLED": "false"
|
||||
},
|
||||
"dotnetRunMessages": true
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace AliasVault.Shared.Models.WebApi.Vault;
|
||||
/// </summary>
|
||||
public class Vault
|
||||
{
|
||||
// ------------------------------------------------------------
|
||||
// Required properties, always part of the vault get/set model.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username that owns the vault.
|
||||
/// </summary>
|
||||
@@ -34,32 +38,12 @@ public class Vault
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the date and time of creation.
|
||||
/// </summary>
|
||||
@@ -69,4 +53,34 @@ public class Vault
|
||||
/// Gets or sets the date and time of last update.
|
||||
/// </summary>
|
||||
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 + "/";
|
||||
|
||||
// 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));
|
||||
string[] hiddenPrivateEmailDomains = ["disabled.tld"];
|
||||
Environment.SetEnvironmentVariable("HIDDEN_PRIVATE_EMAIL_DOMAINS", string.Join(",", hiddenPrivateEmailDomains));
|
||||
|
||||
// Start WebAPI in-memory.
|
||||
_apiFactory.Port = apiPort;
|
||||
|
||||
@@ -163,6 +163,7 @@ ENV ALIASVAULT_VERBOSITY=0 \
|
||||
IP_LOGGING_ENABLED=true \
|
||||
SUPPORT_EMAIL="" \
|
||||
PRIVATE_EMAIL_DOMAINS="" \
|
||||
HIDDEN_PRIVATE_EMAIL_DOMAINS="" \
|
||||
HOSTNAME=localhost \
|
||||
POSTGRES_HOST=localhost \
|
||||
POSTGRES_PORT=5432 \
|
||||
|
||||
@@ -30,6 +30,7 @@ done
|
||||
export ASPNETCORE_URLS="http://0.0.0.0:3001"
|
||||
export ASPNETCORE_PATHBASE="/api"
|
||||
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 IP_LOGGING_ENABLED="${IP_LOGGING_ENABLED:-true}"
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ done
|
||||
|
||||
# Client service entrypoint
|
||||
DEFAULT_PRIVATE_EMAIL_DOMAINS=""
|
||||
DEFAULT_HIDDEN_PRIVATE_EMAIL_DOMAINS=""
|
||||
DEFAULT_SUPPORT_EMAIL=""
|
||||
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}
|
||||
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 "]"}')
|
||||
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
|
||||
cat > /app/client/wwwroot/appsettings.json << EOF
|
||||
{
|
||||
"PrivateEmailDomains": $json_array,
|
||||
"HiddenPrivateEmailDomains": $hidden_json_array,
|
||||
"SupportEmail": "$SUPPORT_EMAIL",
|
||||
"PublicRegistrationEnabled": "$PUBLIC_REGISTRATION_ENABLED"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ for i in {1..30}; do
|
||||
done
|
||||
|
||||
export PRIVATE_EMAIL_DOMAINS="${PRIVATE_EMAIL_DOMAINS:-}"
|
||||
export HIDDEN_PRIVATE_EMAIL_DOMAINS="${HIDDEN_PRIVATE_EMAIL_DOMAINS:-}"
|
||||
export SMTP_TLS_ENABLED="${SMTP_TLS_ENABLED:-false}"
|
||||
|
||||
# Set .NET logging level based on verbosity
|
||||
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
FORCE_HTTPS_REDIRECT: "false"
|
||||
SUPPORT_EMAIL: ""
|
||||
PRIVATE_EMAIL_DOMAINS: ""
|
||||
HIDDEN_PRIVATE_EMAIL_DOMAINS: ""
|
||||
|
||||
volumes:
|
||||
avdata:
|
||||
|
||||
@@ -27,3 +27,4 @@ services:
|
||||
FORCE_HTTPS_REDIRECT: "false"
|
||||
SUPPORT_EMAIL: ""
|
||||
PRIVATE_EMAIL_DOMAINS: ""
|
||||
HIDDEN_PRIVATE_EMAIL_DOMAINS: ""
|
||||
|
||||
@@ -3183,6 +3183,12 @@ check_and_populate_env() {
|
||||
printf " Set PRIVATE_EMAIL_DOMAINS\n"
|
||||
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
|
||||
if ! grep -q "^HTTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
|
||||
update_env_var "HTTP_PORT" "80"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type VaultMetadata = {
|
||||
publicEmailDomains: string[],
|
||||
privateEmailDomains: string[],
|
||||
hiddenPrivateEmailDomains: string[],
|
||||
vaultRevisionNumber: number
|
||||
};
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
* Vault type.
|
||||
*/
|
||||
export type Vault = {
|
||||
blob: string;
|
||||
createdAt: string;
|
||||
credentialsCount: number;
|
||||
currentRevisionNumber: number;
|
||||
emailAddressList: string[];
|
||||
privateEmailDomainList: string[];
|
||||
publicEmailDomainList: string[];
|
||||
encryptionPublicKey: string;
|
||||
updatedAt: string;
|
||||
// Required properties, always part of the vault get/set model.
|
||||
username: string;
|
||||
blob: 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