Extract service icons during both credential create and update (#771)

This commit is contained in:
Leendert de Borst
2025-05-03 08:51:06 +02:00
parent 043ce5c588
commit c763b79c3c
5 changed files with 37 additions and 37 deletions

View File

@@ -123,8 +123,9 @@ export class EncryptionUtility {
*/
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
/**
* TODO: ensure the key pair is generated in the correct format where private key is in expected
* JWK format that the WASM app already outputs.
* TODO: this method is currently unused. When we enable the browser extension to actually generate keys,
* check if the key pair is generated in the correct format where private key is in expected JWK format
* that the WASM app already outputs.
*/
const keyPair = await crypto.subtle.generateKey(
{

View File

@@ -120,8 +120,8 @@ export default function AddEditCredentialScreen() {
}
// Assemble the credential to save
let credentialToSave = {
Id: isEditMode ? id : undefined,
let credentialToSave: Credential = {
Id: isEditMode ? id : '',
Username: watch('Username'),
Password: watch('Password'),
ServiceName: watch('ServiceName'),
@@ -139,28 +139,28 @@ export default function AddEditCredentialScreen() {
// Convert user birthdate entry format (yyyy-mm-dd) into valid ISO 8601 format for database storage
credentialToSave.Alias.BirthDate = IdentityHelperUtils.normalizeBirthDateForDb(credentialToSave.Alias.BirthDate);
// Extract favicon from service URL if the credential has one
if (credentialToSave.ServiceUrl) {
try {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Favicon extraction timed out')), 5000)
);
const faviconPromise = webApi.get<FaviconExtractModel>('Favicon/Extract?url=' + credentialToSave.ServiceUrl);
const faviconResponse = await Promise.race([faviconPromise, timeoutPromise]) as FaviconExtractModel;
if (faviconResponse?.image) {
const decodedImage = Uint8Array.from(Buffer.from(faviconResponse.image as string, 'base64'));
credentialToSave.Logo = decodedImage;
}
} catch (error) {
console.log('Favicon extraction failed or timed out:', error);
}
}
await executeVaultMutation(async () => {
if (isEditMode) {
await dbContext.sqliteClient!.updateCredentialById(credentialToSave);
} else {
// For new credentials, try to extract favicon
if (credentialToSave.ServiceUrl) {
try {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Favicon extraction timed out')), 5000)
);
const faviconPromise = webApi.get<FaviconExtractModel>('Favicon/Extract?url=' + credentialToSave.ServiceUrl);
const faviconResponse = await Promise.race([faviconPromise, timeoutPromise]) as FaviconExtractModel;
if (faviconResponse?.image) {
const decodedImage = Uint8Array.from(Buffer.from(faviconResponse.image as string, 'base64'));
credentialToSave.Logo = decodedImage;
}
} catch (error) {
console.log('Favicon extraction failed or timed out:', error);
}
}
const credentialId = await dbContext.sqliteClient!.createCredential(credentialToSave);
credentialToSave.Id = credentialId;
}

View File

@@ -23,7 +23,7 @@ class CredentialIdentityStore {
guard let username = credential.username, !username.isEmpty else {
return nil
}
let effectiveDomain = Self.effectiveDomain(from: host)
return ASPasswordCredentialIdentity(
@@ -43,7 +43,7 @@ class CredentialIdentityStore {
print("Credential identity store is not enabled.")
return
}
do {
try await store.saveCredentialIdentities(identities)
} catch {
@@ -65,14 +65,13 @@ class CredentialIdentityStore {
return ASPasswordCredentialIdentity(
serviceIdentifier: serviceIdentifier,
user: credential.username ?? "",
// TODO: Use the actual record identifier when implementing the actual vault
recordIdentifier: UUID().uuidString
recordIdentifier: credential.id.uuidString
)
}
try await store.removeCredentialIdentities(identities)
}
private func storeState() async -> ASCredentialIdentityStoreState {
await withCheckedContinuation { continuation in
store.getState { state in
@@ -80,7 +79,7 @@ class CredentialIdentityStore {
}
}
}
private static func effectiveDomain(from host: String) -> String {
let parts = host.split(separator: ".")
guard parts.count >= 2 else { return host }

View File

@@ -110,8 +110,8 @@ class EncryptionUtility {
* Generates a new RSA key pair for asymmetric encryption
*/
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
// TODO: ensure the key pair is generated in the correct format where private key is in expected
// JWK format that the WASM app already outputs.
// TODO: this method is currently unused. When we enable the app to actually generate keys, check if the key pair is
// generated in the correct format where private key is in expected JWK format that the WASM app already outputs.
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",

View File

@@ -559,7 +559,7 @@ class SqliteClient {
try {
/*
* Check if TotpCodes table exists (for backward compatibility).
* TODO: whenever the browser extension has a minimum client DB version of 1.5.0+,
* TODO: whenever the mobile app has a minimum client DB version of 1.5.0+,
* we can remove this check as the TotpCodes table then is guaranteed to exist.
*/
if (!await this.tableExists('TotpCodes')) {
@@ -679,13 +679,11 @@ class SqliteClient {
}
// 1. Update Service
// TODO: make Logo update optional, currently not supported as its becoming null.
// Logo = ?,
const serviceQuery = `
UPDATE Services
SET Name = ?,
Url = ?,
Logo = COALESCE(?, Logo),
UpdatedAt = ?
WHERE Id = (
SELECT ServiceId
@@ -693,12 +691,14 @@ class SqliteClient {
WHERE Id = ?
)`;
/*let logoData = null;
let logoData = null;
try {
if (credential.Logo) {
// Handle object-like array conversion
if (typeof credential.Logo === 'object' && !ArrayBuffer.isView(credential.Logo)) {
const values = Object.values(credential.Logo);
logoData = new Uint8Array(values);
// Handle existing array types
} else if (Array.isArray(credential.Logo) || credential.Logo instanceof ArrayBuffer || credential.Logo instanceof Uint8Array) {
logoData = new Uint8Array(credential.Logo);
}
@@ -706,12 +706,12 @@ class SqliteClient {
} catch (error) {
console.warn('Failed to convert logo to Uint8Array:', error);
logoData = null;
}*/
}
await this.executeUpdate(serviceQuery, [
credential.ServiceName,
credential.ServiceUrl ?? null,
//logoData,
logoData,
currentDateTime,
credential.Id
]);