mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 21:40:41 -04:00
Refactor Credential to Item based structure in AliasVault.Client (#1404)
This commit is contained in:
@@ -37,7 +37,7 @@ const AttachmentUploader: React.FC<AttachmentUploaderProps> = ({
|
||||
const byteArray = new Uint8Array(arrayBuffer);
|
||||
|
||||
const attachment: Attachment = {
|
||||
Id: crypto.randomUUID(),
|
||||
Id: crypto.randomUUID().toUpperCase(),
|
||||
Filename: file.name,
|
||||
Blob: byteArray,
|
||||
ItemId: '', // Will be set when saving item
|
||||
|
||||
@@ -97,7 +97,7 @@ export class FolderRepository extends BaseRepository {
|
||||
*/
|
||||
public async create(name: string, parentFolderId?: string | null): Promise<string> {
|
||||
return this.withTransaction(async () => {
|
||||
const folderId = crypto.randomUUID();
|
||||
const folderId = crypto.randomUUID().toUpperCase();
|
||||
const currentDateTime = this.now();
|
||||
|
||||
this.client.executeUpdate(FolderQueries.INSERT, [
|
||||
|
||||
@@ -160,11 +160,6 @@ declare const FieldKey: {
|
||||
* Type: Password
|
||||
*/
|
||||
readonly LoginPassword: "login.password";
|
||||
/**
|
||||
* Login notes field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly LoginNotes: "login.notes";
|
||||
/**
|
||||
* Login email field
|
||||
* Type: Email
|
||||
@@ -175,11 +170,6 @@ declare const FieldKey: {
|
||||
* Type: URL
|
||||
*/
|
||||
readonly LoginUrl: "login.url";
|
||||
/**
|
||||
* Login recovery codes field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly LoginRecoveryCodes: "login.recovery_codes";
|
||||
/**
|
||||
* Credit card number field
|
||||
* Type: Text
|
||||
@@ -210,66 +200,6 @@ declare const FieldKey: {
|
||||
* Type: Password
|
||||
*/
|
||||
readonly CardPin: "card.pin";
|
||||
/**
|
||||
* Identity title field (e.g., Mr., Mrs., Dr.)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityTitle: "identity.title";
|
||||
/**
|
||||
* Identity first name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityFirstName: "identity.first_name";
|
||||
/**
|
||||
* Identity middle name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityMiddleName: "identity.middle_name";
|
||||
/**
|
||||
* Identity last name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityLastName: "identity.last_name";
|
||||
/**
|
||||
* Identity email field
|
||||
* Type: Email
|
||||
*/
|
||||
readonly IdentityEmail: "identity.email";
|
||||
/**
|
||||
* Identity phone number field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityPhoneNumbers: "identity.phone_numbers";
|
||||
/**
|
||||
* Identity address line 1 field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityAddressLine1: "identity.address_line1";
|
||||
/**
|
||||
* Identity address line 2 field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityAddressLine2: "identity.address_line2";
|
||||
/**
|
||||
* Identity city field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityCity: "identity.city";
|
||||
/**
|
||||
* Identity state/province field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityState: "identity.state";
|
||||
/**
|
||||
* Identity postal code field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityPostalCode: "identity.postal_code";
|
||||
/**
|
||||
* Identity country field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityCountry: "identity.country";
|
||||
/**
|
||||
* Alias first name field
|
||||
* Type: Text
|
||||
|
||||
@@ -14,11 +14,6 @@ var FieldKey = {
|
||||
* Type: Password
|
||||
*/
|
||||
LoginPassword: "login.password",
|
||||
/**
|
||||
* Login notes field
|
||||
* Type: Text
|
||||
*/
|
||||
LoginNotes: "login.notes",
|
||||
/**
|
||||
* Login email field
|
||||
* Type: Email
|
||||
@@ -29,11 +24,6 @@ var FieldKey = {
|
||||
* Type: URL
|
||||
*/
|
||||
LoginUrl: "login.url",
|
||||
/**
|
||||
* Login recovery codes field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
LoginRecoveryCodes: "login.recovery_codes",
|
||||
/**
|
||||
* Credit card number field
|
||||
* Type: Text
|
||||
@@ -64,66 +54,6 @@ var FieldKey = {
|
||||
* Type: Password
|
||||
*/
|
||||
CardPin: "card.pin",
|
||||
/**
|
||||
* Identity title field (e.g., Mr., Mrs., Dr.)
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityTitle: "identity.title",
|
||||
/**
|
||||
* Identity first name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityFirstName: "identity.first_name",
|
||||
/**
|
||||
* Identity middle name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityMiddleName: "identity.middle_name",
|
||||
/**
|
||||
* Identity last name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityLastName: "identity.last_name",
|
||||
/**
|
||||
* Identity email field
|
||||
* Type: Email
|
||||
*/
|
||||
IdentityEmail: "identity.email",
|
||||
/**
|
||||
* Identity phone number field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityPhoneNumbers: "identity.phone_numbers",
|
||||
/**
|
||||
* Identity address line 1 field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityAddressLine1: "identity.address_line1",
|
||||
/**
|
||||
* Identity address line 2 field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityAddressLine2: "identity.address_line2",
|
||||
/**
|
||||
* Identity city field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityCity: "identity.city",
|
||||
/**
|
||||
* Identity state/province field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityState: "identity.state",
|
||||
/**
|
||||
* Identity postal code field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityPostalCode: "identity.postal_code",
|
||||
/**
|
||||
* Identity country field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityCountry: "identity.country",
|
||||
/**
|
||||
* Alias first name field
|
||||
* Type: Text
|
||||
@@ -402,7 +332,7 @@ function getAllSystemFieldKeys() {
|
||||
return Object.keys(SystemFieldRegistry);
|
||||
}
|
||||
function isSystemFieldPrefix(fieldKey) {
|
||||
return fieldKey.startsWith("login.") || fieldKey.startsWith("alias.") || fieldKey.startsWith("card.") || fieldKey.startsWith("identity.") || fieldKey.startsWith("api.") || fieldKey.startsWith("notes.") || fieldKey.startsWith("metadata.");
|
||||
return fieldKey.startsWith("login.") || fieldKey.startsWith("alias.") || fieldKey.startsWith("card.") || fieldKey.startsWith("notes.");
|
||||
}
|
||||
|
||||
// src/vault/ItemMethods.ts
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -882,7 +882,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -1011,7 +1015,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -1028,7 +1036,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1044,7 +1056,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1060,7 +1076,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1082,7 +1102,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1099,7 +1123,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1116,7 +1144,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1133,7 +1165,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1150,11 +1186,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2039,7 +2079,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2168,7 +2212,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2185,7 +2233,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2201,7 +2253,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2217,7 +2273,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2239,7 +2299,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2256,7 +2320,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2273,7 +2341,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2290,7 +2362,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2307,11 +2383,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -850,7 +850,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -979,7 +983,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -996,7 +1004,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1012,7 +1024,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1028,7 +1044,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1050,7 +1070,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1067,7 +1091,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1084,7 +1112,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1101,7 +1133,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1118,11 +1154,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2007,7 +2047,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2136,7 +2180,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2153,7 +2201,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2169,7 +2221,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2185,7 +2241,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2207,7 +2267,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2224,7 +2288,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2241,7 +2309,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2258,7 +2330,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2275,11 +2351,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
package net.aliasvault.app.vaultstore.models
|
||||
@@ -19,11 +19,6 @@ object FieldKey {
|
||||
*/
|
||||
const val LOGIN_PASSWORD = "login.password"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val LOGIN_NOTES = "login.notes"
|
||||
|
||||
/**
|
||||
* Type: Email.
|
||||
*/
|
||||
@@ -34,11 +29,6 @@ object FieldKey {
|
||||
*/
|
||||
const val LOGIN_URL = "login.url"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val LOGIN_RECOVERY_CODES = "login.recovery_codes"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
@@ -69,66 +59,6 @@ object FieldKey {
|
||||
*/
|
||||
const val CARD_PIN = "card.pin"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_TITLE = "identity.title"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_FIRST_NAME = "identity.first_name"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_MIDDLE_NAME = "identity.middle_name"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_LAST_NAME = "identity.last_name"
|
||||
|
||||
/**
|
||||
* Type: Email.
|
||||
*/
|
||||
const val IDENTITY_EMAIL = "identity.email"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_PHONE_NUMBERS = "identity.phone_numbers"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_ADDRESS_LINE1 = "identity.address_line1"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_ADDRESS_LINE2 = "identity.address_line2"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_CITY = "identity.city"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_STATE = "identity.state"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_POSTAL_CODE = "identity.postal_code"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
const val IDENTITY_COUNTRY = "identity.country"
|
||||
|
||||
/**
|
||||
* Type: Text.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
import Foundation
|
||||
@@ -13,18 +13,12 @@ public struct FieldKey {
|
||||
/// Type: Password
|
||||
public static let loginPassword = "login.password"
|
||||
|
||||
/// Type: Text
|
||||
public static let loginNotes = "login.notes"
|
||||
|
||||
/// Type: Email
|
||||
public static let loginEmail = "login.email"
|
||||
|
||||
/// Type: URL
|
||||
public static let loginUrl = "login.url"
|
||||
|
||||
/// Type: Text
|
||||
public static let loginRecoveryCodes = "login.recovery_codes"
|
||||
|
||||
/// Type: Text
|
||||
public static let cardNumber = "card.number"
|
||||
|
||||
@@ -43,42 +37,6 @@ public struct FieldKey {
|
||||
/// Type: Password
|
||||
public static let cardPin = "card.pin"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityTitle = "identity.title"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityFirstName = "identity.first_name"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityMiddleName = "identity.middle_name"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityLastName = "identity.last_name"
|
||||
|
||||
/// Type: Email
|
||||
public static let identityEmail = "identity.email"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityPhoneNumbers = "identity.phone_numbers"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityAddressLine1 = "identity.address_line1"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityAddressLine2 = "identity.address_line2"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityCity = "identity.city"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityState = "identity.state"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityPostalCode = "identity.postal_code"
|
||||
|
||||
/// Type: Text
|
||||
public static let identityCountry = "identity.country"
|
||||
|
||||
/// Type: Text
|
||||
public static let aliasFirstName = "alias.first_name"
|
||||
|
||||
|
||||
@@ -160,11 +160,6 @@ declare const FieldKey: {
|
||||
* Type: Password
|
||||
*/
|
||||
readonly LoginPassword: "login.password";
|
||||
/**
|
||||
* Login notes field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly LoginNotes: "login.notes";
|
||||
/**
|
||||
* Login email field
|
||||
* Type: Email
|
||||
@@ -175,11 +170,6 @@ declare const FieldKey: {
|
||||
* Type: URL
|
||||
*/
|
||||
readonly LoginUrl: "login.url";
|
||||
/**
|
||||
* Login recovery codes field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly LoginRecoveryCodes: "login.recovery_codes";
|
||||
/**
|
||||
* Credit card number field
|
||||
* Type: Text
|
||||
@@ -210,66 +200,6 @@ declare const FieldKey: {
|
||||
* Type: Password
|
||||
*/
|
||||
readonly CardPin: "card.pin";
|
||||
/**
|
||||
* Identity title field (e.g., Mr., Mrs., Dr.)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityTitle: "identity.title";
|
||||
/**
|
||||
* Identity first name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityFirstName: "identity.first_name";
|
||||
/**
|
||||
* Identity middle name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityMiddleName: "identity.middle_name";
|
||||
/**
|
||||
* Identity last name field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityLastName: "identity.last_name";
|
||||
/**
|
||||
* Identity email field
|
||||
* Type: Email
|
||||
*/
|
||||
readonly IdentityEmail: "identity.email";
|
||||
/**
|
||||
* Identity phone number field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityPhoneNumbers: "identity.phone_numbers";
|
||||
/**
|
||||
* Identity address line 1 field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityAddressLine1: "identity.address_line1";
|
||||
/**
|
||||
* Identity address line 2 field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityAddressLine2: "identity.address_line2";
|
||||
/**
|
||||
* Identity city field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityCity: "identity.city";
|
||||
/**
|
||||
* Identity state/province field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityState: "identity.state";
|
||||
/**
|
||||
* Identity postal code field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityPostalCode: "identity.postal_code";
|
||||
/**
|
||||
* Identity country field
|
||||
* Type: Text
|
||||
*/
|
||||
readonly IdentityCountry: "identity.country";
|
||||
/**
|
||||
* Alias first name field
|
||||
* Type: Text
|
||||
|
||||
@@ -14,11 +14,6 @@ var FieldKey = {
|
||||
* Type: Password
|
||||
*/
|
||||
LoginPassword: "login.password",
|
||||
/**
|
||||
* Login notes field
|
||||
* Type: Text
|
||||
*/
|
||||
LoginNotes: "login.notes",
|
||||
/**
|
||||
* Login email field
|
||||
* Type: Email
|
||||
@@ -29,11 +24,6 @@ var FieldKey = {
|
||||
* Type: URL
|
||||
*/
|
||||
LoginUrl: "login.url",
|
||||
/**
|
||||
* Login recovery codes field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
LoginRecoveryCodes: "login.recovery_codes",
|
||||
/**
|
||||
* Credit card number field
|
||||
* Type: Text
|
||||
@@ -64,66 +54,6 @@ var FieldKey = {
|
||||
* Type: Password
|
||||
*/
|
||||
CardPin: "card.pin",
|
||||
/**
|
||||
* Identity title field (e.g., Mr., Mrs., Dr.)
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityTitle: "identity.title",
|
||||
/**
|
||||
* Identity first name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityFirstName: "identity.first_name",
|
||||
/**
|
||||
* Identity middle name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityMiddleName: "identity.middle_name",
|
||||
/**
|
||||
* Identity last name field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityLastName: "identity.last_name",
|
||||
/**
|
||||
* Identity email field
|
||||
* Type: Email
|
||||
*/
|
||||
IdentityEmail: "identity.email",
|
||||
/**
|
||||
* Identity phone number field (multi-value)
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityPhoneNumbers: "identity.phone_numbers",
|
||||
/**
|
||||
* Identity address line 1 field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityAddressLine1: "identity.address_line1",
|
||||
/**
|
||||
* Identity address line 2 field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityAddressLine2: "identity.address_line2",
|
||||
/**
|
||||
* Identity city field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityCity: "identity.city",
|
||||
/**
|
||||
* Identity state/province field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityState: "identity.state",
|
||||
/**
|
||||
* Identity postal code field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityPostalCode: "identity.postal_code",
|
||||
/**
|
||||
* Identity country field
|
||||
* Type: Text
|
||||
*/
|
||||
IdentityCountry: "identity.country",
|
||||
/**
|
||||
* Alias first name field
|
||||
* Type: Text
|
||||
@@ -402,7 +332,7 @@ function getAllSystemFieldKeys() {
|
||||
return Object.keys(SystemFieldRegistry);
|
||||
}
|
||||
function isSystemFieldPrefix(fieldKey) {
|
||||
return fieldKey.startsWith("login.") || fieldKey.startsWith("alias.") || fieldKey.startsWith("card.") || fieldKey.startsWith("identity.") || fieldKey.startsWith("api.") || fieldKey.startsWith("notes.") || fieldKey.startsWith("metadata.");
|
||||
return fieldKey.startsWith("login.") || fieldKey.startsWith("alias.") || fieldKey.startsWith("card.") || fieldKey.startsWith("notes.");
|
||||
}
|
||||
|
||||
// src/vault/ItemMethods.ts
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
124
apps/mobile-app/utils/dist/core/vault/index.js
vendored
124
apps/mobile-app/utils/dist/core/vault/index.js
vendored
@@ -882,7 +882,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -1011,7 +1015,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -1028,7 +1036,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1044,7 +1056,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1060,7 +1076,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1082,7 +1102,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1099,7 +1123,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1116,7 +1144,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1133,7 +1165,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1150,11 +1186,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2039,7 +2079,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2168,7 +2212,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2185,7 +2233,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2201,7 +2253,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2217,7 +2273,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2239,7 +2299,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2256,7 +2320,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2273,7 +2341,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2290,7 +2362,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2307,11 +2383,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
124
apps/mobile-app/utils/dist/core/vault/index.mjs
vendored
124
apps/mobile-app/utils/dist/core/vault/index.mjs
vendored
@@ -850,7 +850,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -979,7 +983,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -996,7 +1004,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1012,7 +1024,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1028,7 +1044,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1050,7 +1070,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1067,7 +1091,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1084,7 +1112,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1101,7 +1133,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1118,11 +1154,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2007,7 +2047,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2136,7 +2180,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2153,7 +2201,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2169,7 +2221,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2185,7 +2241,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2207,7 +2267,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2224,7 +2288,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2241,7 +2309,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2258,7 +2330,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2275,11 +2351,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
/// Gets or sets the credentials object to show.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public required CredentialListEntry Obj { get; set; }
|
||||
public required ItemListEntry Obj { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for the credential, showing username by default,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/// Gets or sets the list of credentials to show.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public List<CredentialListEntry> Credentials { get; set; } = [];
|
||||
public List<ItemListEntry> Credentials { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order for the credentials (used to determine initial table state).
|
||||
@@ -60,7 +60,7 @@
|
||||
/// <summary>
|
||||
/// Gets the credentials to display, with optional column-based sorting applied.
|
||||
/// </summary>
|
||||
private IEnumerable<CredentialListEntry> DisplayedCredentials
|
||||
private IEnumerable<ItemListEntry> DisplayedCredentials
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -135,7 +135,7 @@
|
||||
/// <param name="credentials">The list of credentials to sort.</param>
|
||||
/// <param name="sortColumn">The column to sort by.</param>
|
||||
/// <param name="sortDirection">The direction to sort by.</param>
|
||||
private static IEnumerable<CredentialListEntry> SortList(List<CredentialListEntry> credentials, string sortColumn, SortDirection sortDirection)
|
||||
private static IEnumerable<ItemListEntry> SortList(List<ItemListEntry> credentials, string sortColumn, SortDirection sortDirection)
|
||||
{
|
||||
return sortColumn switch
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@inject DbService DbService
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
|
||||
<label for="@Id" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Label</label>
|
||||
<div class="flex">
|
||||
@@ -169,7 +169,7 @@
|
||||
/// </summary>
|
||||
private async Task GeneratePassword()
|
||||
{
|
||||
string newPassword = await CredentialService.GenerateRandomPasswordAsync(_internalPasswordSettings);
|
||||
string newPassword = await ItemService.GenerateRandomPasswordAsync(_internalPasswordSettings);
|
||||
|
||||
// Update the local value.
|
||||
Value = newPassword;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@inject DbService DbService
|
||||
@inject GlobalLoadingService GlobalLoadingService
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject IStringLocalizerFactory LocalizerFactory
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_previewPassword = await CredentialService.GenerateRandomPasswordAsync(_workingSettings);
|
||||
_previewPassword = await ItemService.GenerateRandomPasswordAsync(_workingSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
@using AliasVault.Client.Resources
|
||||
@inherits AliasVault.Client.Main.Pages.MainBase
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService
|
||||
@inject LanguageService LanguageService
|
||||
@implements IAsyncDisposable
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
|
||||
<button @ref="buttonRef" @onclick="TogglePopup" id="quickIdentityButton" class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 focus:outline-none dark:from-primary-400 dark:to-primary-500 dark:hover:from-primary-500 dark:hover:to-primary-600 rounded-md shadow-sm transition duration-150 ease-in-out transform hover:scale-105 active:scale-95 focus:shadow-outline">
|
||||
@Localizer["NewAliasButtonShort"] <span class="hidden md:inline">@Localizer["NewAliasButtonText"].Value.Substring(1).Trim()</span>
|
||||
@@ -89,7 +91,7 @@
|
||||
/// </summary>
|
||||
private void OnFocusUrlInput(FocusEventArgs e)
|
||||
{
|
||||
if (Model.ServiceUrl != CredentialService.DefaultServiceUrl)
|
||||
if (Model.ServiceUrl != ItemService.DefaultServiceUrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -97,7 +99,7 @@
|
||||
// Use a small delay to ensure the focus is set after the browser's default behavior.
|
||||
Task.Delay(1).ContinueWith(_ =>
|
||||
{
|
||||
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('serviceUrl').setSelectionRange({CredentialService.DefaultServiceUrl.Length}, {CredentialService.DefaultServiceUrl.Length})");
|
||||
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('serviceUrl').setSelectionRange({ItemService.DefaultServiceUrl.Length}, {ItemService.DefaultServiceUrl.Length})");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,7 +124,7 @@
|
||||
|
||||
// Clear the input fields
|
||||
Model = new();
|
||||
Model.ServiceUrl = CredentialService.DefaultServiceUrl;
|
||||
Model.ServiceUrl = ItemService.DefaultServiceUrl;
|
||||
|
||||
await UpdatePopupStyle();
|
||||
await Task.Delay(100); // Give time for the DOM to update
|
||||
@@ -166,21 +168,26 @@
|
||||
GlobalLoadingSpinner.Show(Localizer["CreatingNewAliasMessage"]);
|
||||
StateHasChanged();
|
||||
|
||||
var credential = new Credential();
|
||||
credential.Alias = new Alias();
|
||||
credential.Alias.Email = "@" + CredentialService.GetDefaultEmailDomain();
|
||||
credential.Service = new Service();
|
||||
credential.Service.Name = Model.ServiceName;
|
||||
|
||||
if (Model.ServiceUrl != CredentialService.DefaultServiceUrl)
|
||||
var item = new Item
|
||||
{
|
||||
credential.Service.Url = Model.ServiceUrl;
|
||||
Name = Model.ServiceName,
|
||||
ItemType = "Login",
|
||||
FieldValues = new List<FieldValue>()
|
||||
};
|
||||
|
||||
// Set email with default domain
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginEmail, "@" + ItemService.GetDefaultEmailDomain());
|
||||
|
||||
// Set URL if provided
|
||||
if (Model.ServiceUrl != ItemService.DefaultServiceUrl)
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginUrl, Model.ServiceUrl);
|
||||
}
|
||||
|
||||
credential.Passwords = new List<Password> { new() };
|
||||
await CredentialService.GenerateRandomIdentityAsync(credential);
|
||||
// Generate random identity
|
||||
await ItemService.GenerateRandomIdentityAsync(item);
|
||||
|
||||
var id = await CredentialService.InsertEntryAsync(credential);
|
||||
var id = await ItemService.InsertEntryAsync(item);
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
// Error saving.
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
@inject JsInteropService JsInteropService
|
||||
@inject IStringLocalizerFactory LocalizerFactory
|
||||
@inject LanguageService LanguageService
|
||||
@inject ItemService ItemService
|
||||
@implements IAsyncDisposable
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using System.Timers
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
|
||||
<div class="relative" id="searchWidgetContainer">
|
||||
<input
|
||||
@@ -65,16 +68,20 @@
|
||||
<div
|
||||
class="search-result @(i == SelectedIndex ? "bg-gray-100 dark:bg-gray-700" : "") px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center"
|
||||
@onclick="() => SelectResult(result)">
|
||||
<DisplayFavicon FaviconBytes="@result.Service.Logo" Width="24" />
|
||||
<DisplayFavicon FaviconBytes="@result.Logo?.FileData" Width="24" />
|
||||
<div class="ml-2">
|
||||
<div>@result.Service.Name</div>
|
||||
@if (!string.IsNullOrEmpty(result.Alias.Email))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Alias.Email)</span>
|
||||
<div>@result.Name</div>
|
||||
@{
|
||||
var email = ItemService.GetFieldValue(result, FieldKey.LoginEmail);
|
||||
var username = ItemService.GetFieldValue(result, FieldKey.LoginUsername);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(result.Username))
|
||||
@if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Username)</span>
|
||||
<span class="text-gray-500">(@email)</span>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
<span class="text-gray-500">(@username)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,7 +106,7 @@
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client");
|
||||
|
||||
private string SearchTerm { get; set; } = string.Empty;
|
||||
private List<Credential> SearchResults { get; set; } = new();
|
||||
private List<Item> SearchResults { get; set; } = new();
|
||||
private bool ShowResults { get; set; }
|
||||
private bool ShowHelpText { get; set; }
|
||||
private int SelectedIndex { get; set; } = -1;
|
||||
@@ -199,26 +206,31 @@
|
||||
{
|
||||
var searchTerms = SearchTerm.Trim().ToLowerInvariant().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var query = context.Credentials
|
||||
.Include(x => x.Service)
|
||||
.Include(x => x.Alias)
|
||||
var query = context.Items
|
||||
.Include(x => x.Logo)
|
||||
.Include(x => x.FieldValues.Where(fv => !fv.IsDeleted))
|
||||
.Where(x => !x.IsDeleted)
|
||||
.Where(x => x.DeletedAt == null)
|
||||
.AsQueryable();
|
||||
|
||||
foreach (var term in searchTerms)
|
||||
{
|
||||
// We filter credentials by searching in the following fields:
|
||||
// - Service name
|
||||
// - Username
|
||||
// - Alias email
|
||||
// - Service URL
|
||||
// - Notes
|
||||
// We filter items by searching in the following fields:
|
||||
// - Item name
|
||||
// - Username (from field values)
|
||||
// - Email (from field values)
|
||||
// - URL (from field values)
|
||||
// - Notes (from field values)
|
||||
query = query.Where(x =>
|
||||
(x.Service.Name != null && EF.Functions.Like(x.Service.Name.ToLower(), $"%{term}%")) ||
|
||||
(x.Alias.Email != null && EF.Functions.Like(x.Alias.Email.ToLower(), $"%{term}%")) ||
|
||||
(x.Username != null && EF.Functions.Like(x.Username.ToLower(), $"%{term}%")) ||
|
||||
(x.Service.Url != null && EF.Functions.Like(x.Service.Url.ToLower(), $"%{term}%")) ||
|
||||
(x.Notes != null && EF.Functions.Like(x.Notes.ToLower(), $"%{term}%"))
|
||||
(x.Name != null && EF.Functions.Like(x.Name.ToLower(), $"%{term}%")) ||
|
||||
x.FieldValues.Any(fv =>
|
||||
!fv.IsDeleted &&
|
||||
fv.Value != null &&
|
||||
(fv.FieldKey == FieldKey.LoginEmail ||
|
||||
fv.FieldKey == FieldKey.LoginUsername ||
|
||||
fv.FieldKey == FieldKey.LoginUrl ||
|
||||
fv.FieldKey == FieldKey.NotesContent) &&
|
||||
EF.Functions.Like(fv.Value.ToLower(), $"%{term}%"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,10 +275,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SelectResult(Credential credential)
|
||||
private async Task SelectResult(Item item)
|
||||
{
|
||||
await JsInteropService.BlurElementById("searchWidget");
|
||||
NavigationManager.NavigateTo($"/credentials/{credential.Id}");
|
||||
NavigationManager.NavigateTo($"/credentials/{item.Id}");
|
||||
}
|
||||
|
||||
private void ResetSearchField(object? sender, LocationChangedEventArgs e)
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="CredentialEdit.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Main.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using AliasClientDb;
|
||||
using AliasVault.Client.Main.Models.FormValidation;
|
||||
using AliasVault.Client.Resources;
|
||||
|
||||
/// <summary>
|
||||
/// Credential edit model.
|
||||
/// </summary>
|
||||
public sealed class CredentialEdit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Id of the login.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets notes field (free text input).
|
||||
/// </summary>
|
||||
public string Notes { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets username field.
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the service.
|
||||
/// </summary>
|
||||
[Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.ServiceNameRequired))]
|
||||
[Display(Name = "Service Name")]
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL of the service.
|
||||
/// </summary>
|
||||
public string? ServiceUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logo of the service.
|
||||
/// </summary>
|
||||
public byte[]? ServiceLogo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias Identity object.
|
||||
/// </summary>
|
||||
public Alias Alias { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias BirthDate. Can be empty string or a date in yyyy-MM-dd format.
|
||||
/// </summary>
|
||||
[StringDateFormat("yyyy-MM-dd", AllowEmpty = true)]
|
||||
public string AliasBirthDate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias Password object.
|
||||
/// </summary>
|
||||
public Password Password { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias CreateDate.
|
||||
/// </summary>
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias LastUpdate.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Attachment list.
|
||||
/// </summary>
|
||||
public List<Attachment> Attachments { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TOTP codes list.
|
||||
/// </summary>
|
||||
public List<TotpCode> TotpCodes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Passkeys list.
|
||||
/// </summary>
|
||||
public List<Passkey> Passkeys { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a CredentialEdit instance from a Credential entity.
|
||||
/// </summary>
|
||||
/// <param name="credential">The credential entity to convert.</param>
|
||||
/// <returns>A new CredentialEdit instance.</returns>
|
||||
public static CredentialEdit FromEntity(Credential credential)
|
||||
{
|
||||
// Create a deep copy of the credential object to prevent changes to the original object
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
MaxDepth = 128,
|
||||
};
|
||||
|
||||
// Create a deep copy of the credential object
|
||||
var credentialJson = JsonSerializer.Serialize(credential, options);
|
||||
var credentialCopy = JsonSerializer.Deserialize<Credential>(credentialJson, options)!;
|
||||
|
||||
return new CredentialEdit
|
||||
{
|
||||
Id = credentialCopy.Id,
|
||||
Notes = credentialCopy.Notes ?? string.Empty,
|
||||
Username = credentialCopy.Username ?? string.Empty,
|
||||
ServiceName = credentialCopy.Service.Name ?? string.Empty,
|
||||
ServiceUrl = credentialCopy.Service.Url,
|
||||
ServiceLogo = credentialCopy.Service.Logo,
|
||||
Password = credentialCopy.Passwords.FirstOrDefault() ?? new Password
|
||||
{
|
||||
Value = string.Empty,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
},
|
||||
Alias = credentialCopy.Alias,
|
||||
AliasBirthDate = credentialCopy.Alias.BirthDate == DateTime.MinValue ? string.Empty : credentialCopy.Alias.BirthDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
|
||||
Attachments = credentialCopy.Attachments.ToList(),
|
||||
TotpCodes = credentialCopy.TotpCodes.ToList(),
|
||||
Passkeys = credentialCopy.Passkeys.Where(p => !p.IsDeleted).ToList(),
|
||||
CreateDate = credentialCopy.CreatedAt,
|
||||
LastUpdate = credentialCopy.UpdatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this CredentialEdit instance to a Credential entity.
|
||||
/// </summary>
|
||||
/// <returns>A new Credential entity.</returns>
|
||||
public Credential ToEntity()
|
||||
{
|
||||
var credential = new Credential
|
||||
{
|
||||
Id = Id,
|
||||
Notes = Notes,
|
||||
Username = Username,
|
||||
Service = new Service
|
||||
{
|
||||
Name = ServiceName,
|
||||
Url = ServiceUrl,
|
||||
Logo = ServiceLogo,
|
||||
},
|
||||
Passwords =
|
||||
[
|
||||
Password,
|
||||
],
|
||||
Alias = Alias,
|
||||
Attachments = Attachments,
|
||||
TotpCodes = TotpCodes,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(AliasBirthDate))
|
||||
{
|
||||
credential.Alias.BirthDate = DateTime.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
credential.Alias.BirthDate = DateTime.Parse(AliasBirthDate, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
222
apps/server/AliasVault.Client/Main/Models/ItemEdit.cs
Normal file
222
apps/server/AliasVault.Client/Main/Models/ItemEdit.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ItemEdit.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Main.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using AliasClientDb;
|
||||
using AliasClientDb.Models;
|
||||
using AliasVault.Client.Main.Models.FormValidation;
|
||||
using AliasVault.Client.Resources;
|
||||
using AliasVault.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Item edit model for add/edit forms.
|
||||
/// </summary>
|
||||
public sealed class ItemEdit
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Id of the item.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item type (Login, Alias, CreditCard, Note).
|
||||
/// </summary>
|
||||
public string ItemType { get; set; } = "Login";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the service/item.
|
||||
/// </summary>
|
||||
[Required(ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = nameof(ValidationMessages.ServiceNameRequired))]
|
||||
[Display(Name = "Service Name")]
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL of the service.
|
||||
/// </summary>
|
||||
public string? ServiceUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logo ID.
|
||||
/// </summary>
|
||||
public Guid? LogoId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logo bytes.
|
||||
/// </summary>
|
||||
public byte[]? ServiceLogo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username field.
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password field.
|
||||
/// </summary>
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email field.
|
||||
/// </summary>
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the notes field.
|
||||
/// </summary>
|
||||
public string Notes { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias first name.
|
||||
/// </summary>
|
||||
public string AliasFirstName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias last name.
|
||||
/// </summary>
|
||||
public string AliasLastName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias gender.
|
||||
/// </summary>
|
||||
public string AliasGender { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias BirthDate. Can be empty string or a date in yyyy-MM-dd format.
|
||||
/// </summary>
|
||||
[StringDateFormat("yyyy-MM-dd", AllowEmpty = true)]
|
||||
public string AliasBirthDate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the create date.
|
||||
/// </summary>
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last update date.
|
||||
/// </summary>
|
||||
public DateTime LastUpdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Attachment list.
|
||||
/// </summary>
|
||||
public List<Attachment> Attachments { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TOTP codes list.
|
||||
/// </summary>
|
||||
public List<TotpCode> TotpCodes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Passkeys list.
|
||||
/// </summary>
|
||||
public List<Passkey> Passkeys { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ItemEdit instance from an Item entity.
|
||||
/// </summary>
|
||||
/// <param name="item">The item entity to convert.</param>
|
||||
/// <returns>A new ItemEdit instance.</returns>
|
||||
public static ItemEdit FromEntity(Item item)
|
||||
{
|
||||
var birthDate = ItemService.GetFieldValue(item, FieldKey.AliasBirthdate);
|
||||
|
||||
return new ItemEdit
|
||||
{
|
||||
Id = item.Id,
|
||||
ItemType = item.ItemType,
|
||||
ServiceName = item.Name ?? string.Empty,
|
||||
ServiceUrl = ItemService.GetFieldValue(item, FieldKey.LoginUrl),
|
||||
LogoId = item.LogoId,
|
||||
ServiceLogo = item.Logo?.FileData,
|
||||
Username = ItemService.GetFieldValue(item, FieldKey.LoginUsername) ?? string.Empty,
|
||||
Password = ItemService.GetFieldValue(item, FieldKey.LoginPassword) ?? string.Empty,
|
||||
Email = ItemService.GetFieldValue(item, FieldKey.LoginEmail) ?? string.Empty,
|
||||
Notes = ItemService.GetFieldValue(item, FieldKey.NotesContent) ?? string.Empty,
|
||||
AliasFirstName = ItemService.GetFieldValue(item, FieldKey.AliasFirstName) ?? string.Empty,
|
||||
AliasLastName = ItemService.GetFieldValue(item, FieldKey.AliasLastName) ?? string.Empty,
|
||||
AliasGender = ItemService.GetFieldValue(item, FieldKey.AliasGender) ?? string.Empty,
|
||||
AliasBirthDate = birthDate ?? string.Empty,
|
||||
Attachments = item.Attachments.Where(a => !a.IsDeleted).ToList(),
|
||||
TotpCodes = item.TotpCodes.Where(t => !t.IsDeleted).ToList(),
|
||||
Passkeys = item.Passkeys.Where(p => !p.IsDeleted).ToList(),
|
||||
CreateDate = item.CreatedAt,
|
||||
LastUpdate = item.UpdatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this ItemEdit instance to an Item entity.
|
||||
/// </summary>
|
||||
/// <returns>A new Item entity.</returns>
|
||||
public Item ToEntity()
|
||||
{
|
||||
var item = new Item
|
||||
{
|
||||
Id = Id,
|
||||
Name = ServiceName,
|
||||
ItemType = ItemType,
|
||||
LogoId = LogoId,
|
||||
Attachments = Attachments,
|
||||
TotpCodes = TotpCodes,
|
||||
};
|
||||
|
||||
// Add login fields
|
||||
if (!string.IsNullOrEmpty(ServiceUrl))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginUrl, ServiceUrl);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Username))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginUsername, Username);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Password))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginPassword, Password);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Email))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginEmail, Email);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Notes))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.NotesContent, Notes);
|
||||
}
|
||||
|
||||
// Add alias fields
|
||||
if (!string.IsNullOrEmpty(AliasFirstName))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.AliasFirstName, AliasFirstName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(AliasLastName))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.AliasLastName, AliasLastName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(AliasGender))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.AliasGender, AliasGender);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(AliasBirthDate))
|
||||
{
|
||||
ItemService.SetFieldValue(item, FieldKey.AliasBirthdate, AliasBirthDate);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="CredentialListEntry.cs" company="aliasvault">
|
||||
// <copyright file="ItemListEntry.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
@@ -7,58 +7,60 @@
|
||||
|
||||
namespace AliasVault.Client.Main.Models;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Alias list entry model. This model is used to represent an alias in a list with simplified properties.
|
||||
/// Item list entry model for displaying items in lists.
|
||||
/// </summary>
|
||||
public sealed class CredentialListEntry
|
||||
public sealed class ItemListEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the alias id.
|
||||
/// Gets or sets the Item ID.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias logo byte array.
|
||||
/// Gets or sets the Logo (favicon) bytes.
|
||||
/// </summary>
|
||||
public byte[]? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias service name.
|
||||
/// Gets or sets the Service name.
|
||||
/// </summary>
|
||||
public string? Service { get; set; } = string.Empty;
|
||||
public string? Service { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias username.
|
||||
/// Gets or sets the Username.
|
||||
/// </summary>
|
||||
public string? Username { get; set; } = string.Empty;
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias email.
|
||||
/// Gets or sets the Email.
|
||||
/// </summary>
|
||||
public string? Email { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias create date.
|
||||
/// Gets or sets the created timestamp.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this credential has a passkey.
|
||||
/// Gets or sets a value indicating whether this item has a passkey.
|
||||
/// </summary>
|
||||
public bool HasPasskey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this credential has alias fields (identity information).
|
||||
/// Gets or sets a value indicating whether this item has alias identity data.
|
||||
/// </summary>
|
||||
public bool HasAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this credential has a username or password.
|
||||
/// Gets or sets a value indicating whether this item has a username or password.
|
||||
/// </summary>
|
||||
public bool HasUsernameOrPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this credential has one or more attachments.
|
||||
/// Gets or sets a value indicating whether this item has attachments.
|
||||
/// </summary>
|
||||
public bool HasAttachment { get; set; }
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
@page "/credentials/create"
|
||||
@page "/credentials/{id:guid}/edit"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService
|
||||
@using AliasVault.Client.Services.JsInterop.Models
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<PageHeader
|
||||
@@ -159,23 +161,23 @@ else
|
||||
</div>
|
||||
}
|
||||
<div class="col-span-6">
|
||||
<EditEmailFormRow Id="email" Label="@Localizer["EmailLabel"]" @bind-Value="Obj.Alias.Email"></EditEmailFormRow>
|
||||
<EditEmailFormRow Id="email" Label="@Localizer["EmailLabel"]" @bind-Value="Obj.Email"></EditEmailFormRow>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<EditPasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" @bind-Value="Obj.Password.Value" ShowPassword="IsPasswordVisible"></EditPasswordFormRow>
|
||||
<EditPasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" @bind-Value="Obj.Password" ShowPassword="IsPasswordVisible"></EditPasswordFormRow>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Without passkey: Email, Username, Password *@
|
||||
<div class="col-span-6">
|
||||
<EditEmailFormRow Id="email" Label="@Localizer["EmailLabel"]" @bind-Value="Obj.Alias.Email"></EditEmailFormRow>
|
||||
<EditEmailFormRow Id="email" Label="@Localizer["EmailLabel"]" @bind-Value="Obj.Email"></EditEmailFormRow>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<EditUsernameFormRow Id="username" Label="@Localizer["UsernameLabel"]" @bind-Value="Obj.Username" OnGenerateNewUsername="GenerateRandomUsername"></EditUsernameFormRow>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<EditPasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" @bind-Value="Obj.Password.Value" ShowPassword="IsPasswordVisible"></EditPasswordFormRow>
|
||||
<EditPasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" @bind-Value="Obj.Password" ShowPassword="IsPasswordVisible"></EditPasswordFormRow>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -206,16 +208,16 @@ else
|
||||
</div>
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="first-name" Label="@Localizer["FirstNameLabel"]" @bind-Value="Obj.Alias.FirstName"></EditFormRow>
|
||||
<EditFormRow Id="first-name" Label="@Localizer["FirstNameLabel"]" @bind-Value="Obj.AliasFirstName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="last-name" Label="@Localizer["LastNameLabel"]" @bind-Value="Obj.Alias.LastName"></EditFormRow>
|
||||
<EditFormRow Id="last-name" Label="@Localizer["LastNameLabel"]" @bind-Value="Obj.AliasLastName"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="nickname" Label="@Localizer["NickNameLabel"]" @bind-Value="Obj.Alias.NickName"></EditFormRow>
|
||||
<EditFormRow Id="nickname" Label="@Localizer["NickNameLabel"]" @bind-Value="Obj.Username"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="gender" Label="@Localizer["GenderLabel"]" @bind-Value="Obj.Alias.Gender"></EditFormRow>
|
||||
<EditFormRow Id="gender" Label="@Localizer["GenderLabel"]" @bind-Value="Obj.AliasGender"></EditFormRow>
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<EditFormRow Id="birthdate" Label="@Localizer["BirthDateLabel"]" @bind-Value="Obj.AliasBirthDate"></EditFormRow>
|
||||
@@ -244,7 +246,7 @@ else
|
||||
private bool Loading { get; set; } = true;
|
||||
private bool IsPasswordVisible { get; set; } = false;
|
||||
private bool PasskeyMarkedForDeletion { get; set; } = false;
|
||||
private CredentialEdit Obj { get; set; } = new();
|
||||
private ItemEdit Obj { get; set; } = new();
|
||||
private IJSObjectReference? Module;
|
||||
|
||||
// Track last generated values to protect manual entries
|
||||
@@ -347,56 +349,49 @@ else
|
||||
}
|
||||
|
||||
// Load existing Obj, retrieve from service
|
||||
var alias = await CredentialService.LoadEntryAsync(Id.Value);
|
||||
if (alias is null)
|
||||
var item = await ItemService.LoadEntryAsync(Id.Value);
|
||||
if (item is null)
|
||||
{
|
||||
NavigateAwayWithError(Localizer["CredentialNotExistError"]);
|
||||
return;
|
||||
}
|
||||
|
||||
Obj = CredentialEdit.FromEntity(alias);
|
||||
|
||||
// If BirthDate is MinValue, set AliasBirthDate to empty string
|
||||
// TODO: after date field in alias data model is made optional and
|
||||
// all min values have been replaced with null, we can remove this check.
|
||||
if (Obj.Alias.BirthDate == DateTime.MinValue)
|
||||
{
|
||||
Obj.AliasBirthDate = string.Empty;
|
||||
}
|
||||
Obj = ItemEdit.FromEntity(item);
|
||||
|
||||
if (Obj.ServiceUrl is null)
|
||||
{
|
||||
Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
|
||||
Obj.ServiceUrl = ItemService.DefaultServiceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new credential object.
|
||||
/// Creates a new item object.
|
||||
/// </summary>
|
||||
private Credential CreateNewCredentialObject()
|
||||
private Item CreateNewItemObject()
|
||||
{
|
||||
var credential = new Credential();
|
||||
credential.Alias = new Alias();
|
||||
credential.Alias.Email = "@" + CredentialService.GetDefaultEmailDomain();
|
||||
credential.Service = new Service();
|
||||
credential.Passwords = new List<Password> { new Password() };
|
||||
credential.TotpCodes = new List<TotpCode>();
|
||||
var item = new Item
|
||||
{
|
||||
ItemType = "Login",
|
||||
FieldValues = new List<FieldValue>(),
|
||||
Attachments = new List<Attachment>(),
|
||||
TotpCodes = new List<TotpCode>(),
|
||||
Passkeys = new List<Passkey>()
|
||||
};
|
||||
|
||||
return credential;
|
||||
ItemService.SetFieldValue(item, FieldKey.LoginEmail, "@" + ItemService.GetDefaultEmailDomain());
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new credential object.
|
||||
/// Creates a new item object for editing.
|
||||
/// </summary>
|
||||
private void CreateNewCredential()
|
||||
{
|
||||
Obj = CredentialEdit.FromEntity(CreateNewCredentialObject());
|
||||
Obj = ItemEdit.FromEntity(CreateNewItemObject());
|
||||
|
||||
// Always set AliasBirthDate to empty for new credentials
|
||||
// TODO: after date field in alias data model is made optional and
|
||||
// all min values have been replaced with null, we can remove this check.
|
||||
Obj.AliasBirthDate = string.Empty;
|
||||
Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
|
||||
Obj.ServiceUrl = ItemService.DefaultServiceUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -413,7 +408,7 @@ else
|
||||
/// </summary>
|
||||
private void OnFocusUrlInput(FocusEventArgs e)
|
||||
{
|
||||
if (Obj.ServiceUrl != CredentialService.DefaultServiceUrl)
|
||||
if (Obj.ServiceUrl != ItemService.DefaultServiceUrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -421,7 +416,7 @@ else
|
||||
// Use a small delay to ensure the focus is set after the browser's default behavior.
|
||||
Task.Delay(1).ContinueWith(_ =>
|
||||
{
|
||||
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('service-url').setSelectionRange({CredentialService.DefaultServiceUrl.Length}, {CredentialService.DefaultServiceUrl.Length})");
|
||||
JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('service-url').setSelectionRange({ItemService.DefaultServiceUrl.Length}, {ItemService.DefaultServiceUrl.Length})");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -451,10 +446,9 @@ else
|
||||
|
||||
private void ClearAliasFields()
|
||||
{
|
||||
Obj.Alias.FirstName = string.Empty;
|
||||
Obj.Alias.LastName = string.Empty;
|
||||
Obj.Alias.NickName = string.Empty;
|
||||
Obj.Alias.Gender = string.Empty;
|
||||
Obj.AliasFirstName = string.Empty;
|
||||
Obj.AliasLastName = string.Empty;
|
||||
Obj.AliasGender = string.Empty;
|
||||
Obj.AliasBirthDate = string.Empty;
|
||||
|
||||
StateHasChanged();
|
||||
@@ -462,10 +456,9 @@ else
|
||||
|
||||
private bool HasAliasValues()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Obj.Alias.FirstName) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.Alias.LastName) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.Alias.NickName) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.Alias.Gender) ||
|
||||
return !string.IsNullOrWhiteSpace(Obj.AliasFirstName) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.AliasLastName) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.AliasGender) ||
|
||||
!string.IsNullOrWhiteSpace(Obj.AliasBirthDate);
|
||||
}
|
||||
|
||||
@@ -483,24 +476,22 @@ else
|
||||
{
|
||||
// Store current values BEFORE generating, as the service might modify them
|
||||
string currentUsername = Obj.Username ?? string.Empty;
|
||||
string currentPassword = Obj.Password.Value ?? string.Empty;
|
||||
string currentEmail = Obj.Alias.Email ?? string.Empty;
|
||||
string currentPassword = Obj.Password ?? string.Empty;
|
||||
string currentEmail = Obj.Email ?? string.Empty;
|
||||
|
||||
// Generate random identity
|
||||
var generatedCredential = await CredentialService.GenerateRandomIdentityAsync(Obj.ToEntity());
|
||||
var generatedObj = CredentialEdit.FromEntity(generatedCredential);
|
||||
var generatedItem = await ItemService.GenerateRandomIdentityAsync(Obj.ToEntity());
|
||||
var generatedObj = ItemEdit.FromEntity(generatedItem);
|
||||
|
||||
// Restore the original values to prevent service calls above from modifying them
|
||||
Obj.Username = currentUsername;
|
||||
Obj.Password.Value = currentPassword;
|
||||
Obj.Alias.Email = currentEmail;
|
||||
Obj.Password = currentPassword;
|
||||
Obj.Email = currentEmail;
|
||||
|
||||
// Apply generated values, respecting manual entries
|
||||
Obj.Alias.FirstName = generatedObj.Alias.FirstName;
|
||||
Obj.Alias.LastName = generatedObj.Alias.LastName;
|
||||
Obj.Alias.NickName = generatedObj.Alias.NickName;
|
||||
Obj.Alias.Gender = generatedObj.Alias.Gender;
|
||||
Obj.Alias.BirthDate = generatedObj.Alias.BirthDate;
|
||||
Obj.AliasFirstName = generatedObj.AliasFirstName;
|
||||
Obj.AliasLastName = generatedObj.AliasLastName;
|
||||
Obj.AliasGender = generatedObj.AliasGender;
|
||||
Obj.AliasBirthDate = generatedObj.AliasBirthDate;
|
||||
|
||||
// Only overwrite username if it's empty or matches the last generated value
|
||||
@@ -513,16 +504,16 @@ else
|
||||
// Only overwrite password if it's empty or matches the last generated value
|
||||
if (string.IsNullOrWhiteSpace(currentPassword) || currentPassword == LastGeneratedPassword)
|
||||
{
|
||||
Obj.Password.Value = generatedObj.Password.Value;
|
||||
LastGeneratedPassword = generatedObj.Password.Value;
|
||||
Obj.Password = generatedObj.Password;
|
||||
LastGeneratedPassword = generatedObj.Password;
|
||||
IsPasswordVisible = true;
|
||||
}
|
||||
|
||||
// Only overwrite email if it's empty or (for new credentials) matches the last generated value or starts with @ which is the default email pattern
|
||||
if (string.IsNullOrWhiteSpace(currentEmail) || currentEmail == LastGeneratedEmail || currentEmail.StartsWith("@"))
|
||||
{
|
||||
Obj.Alias.Email = generatedObj.Alias.Email;
|
||||
LastGeneratedEmail = generatedObj.Alias.Email;
|
||||
Obj.Email = generatedObj.Email;
|
||||
LastGeneratedEmail = generatedObj.Email;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
@@ -536,18 +527,19 @@ else
|
||||
{
|
||||
// If current object is null, then we create a new random identity.
|
||||
AliasVaultIdentity identity;
|
||||
if (Obj.Alias.FirstName is null && Obj.Alias.LastName is null && Obj.Alias.BirthDate == DateTime.MinValue)
|
||||
if (string.IsNullOrWhiteSpace(Obj.AliasFirstName) && string.IsNullOrWhiteSpace(Obj.AliasLastName) && string.IsNullOrWhiteSpace(Obj.AliasBirthDate))
|
||||
{
|
||||
// Create new Credential object to avoid modifying the original object
|
||||
var randomIdentity = await CredentialService.GenerateRandomIdentityAsync(CreateNewCredentialObject());
|
||||
// Create new Item object to avoid modifying the original object
|
||||
var randomIdentity = await ItemService.GenerateRandomIdentityAsync(CreateNewItemObject());
|
||||
var randomIdentityEdit = ItemEdit.FromEntity(randomIdentity);
|
||||
|
||||
identity = new AliasVaultIdentity
|
||||
{
|
||||
FirstName = randomIdentity.Alias.FirstName ?? string.Empty,
|
||||
LastName = randomIdentity.Alias.LastName ?? string.Empty,
|
||||
BirthDate = randomIdentity.Alias.BirthDate.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
|
||||
Gender = randomIdentity.Alias.Gender,
|
||||
NickName = randomIdentity.Alias.NickName ?? string.Empty,
|
||||
FirstName = randomIdentityEdit.AliasFirstName ?? string.Empty,
|
||||
LastName = randomIdentityEdit.AliasLastName ?? string.Empty,
|
||||
BirthDate = randomIdentityEdit.AliasBirthDate ?? string.Empty,
|
||||
Gender = randomIdentityEdit.AliasGender ?? string.Empty,
|
||||
NickName = randomIdentityEdit.Username ?? string.Empty,
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -555,11 +547,11 @@ else
|
||||
// Assemble identity model with the current values
|
||||
identity = new AliasVaultIdentity
|
||||
{
|
||||
FirstName = Obj.Alias.FirstName ?? string.Empty,
|
||||
LastName = Obj.Alias.LastName ?? string.Empty,
|
||||
BirthDate = Obj.Alias.BirthDate.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
|
||||
Gender = Obj.Alias.Gender,
|
||||
NickName = Obj.Alias.NickName ?? string.Empty,
|
||||
FirstName = Obj.AliasFirstName ?? string.Empty,
|
||||
LastName = Obj.AliasLastName ?? string.Empty,
|
||||
BirthDate = Obj.AliasBirthDate ?? string.Empty,
|
||||
Gender = Obj.AliasGender ?? string.Empty,
|
||||
NickName = Obj.Username ?? string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -606,7 +598,7 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the alias to the database.
|
||||
/// Save the item to the database.
|
||||
/// </summary>
|
||||
private async Task SaveAlias()
|
||||
{
|
||||
@@ -619,7 +611,7 @@ else
|
||||
var context = await DbService.GetDbContextAsync();
|
||||
foreach (var passkey in Obj.Passkeys)
|
||||
{
|
||||
await CredentialService.DeletePasskeyAsync(passkey.Id);
|
||||
await ItemService.DeletePasskeyAsync(passkey.Id);
|
||||
}
|
||||
Obj.Passkeys.Clear();
|
||||
}
|
||||
@@ -628,12 +620,12 @@ else
|
||||
{
|
||||
if (Id is not null)
|
||||
{
|
||||
Id = await CredentialService.UpdateEntryAsync(Obj.ToEntity());
|
||||
Id = await ItemService.UpdateEntryAsync(Obj.ToEntity());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Id = await CredentialService.InsertEntryAsync(Obj.ToEntity());
|
||||
Id = await ItemService.InsertEntryAsync(Obj.ToEntity());
|
||||
}
|
||||
|
||||
GlobalLoadingSpinner.Hide();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@page "/credentials/{id:guid}/delete"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasClientDb
|
||||
|
||||
<LayoutPageTitle>@Localizer["DeleteCredentialPageTitle"]</LayoutPageTitle>
|
||||
|
||||
@@ -26,7 +27,7 @@ else
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ServiceNameLabel"]</label>
|
||||
<div class="text-gray-900 dark:text-white">@Obj?.Service.Name</div>
|
||||
<div class="text-gray-900 dark:text-white">@Obj?.Name</div>
|
||||
</div>
|
||||
<button @onclick="DeleteConfirm" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm inline-flex items-center px-3 py-2.5 text-center mr-2 dark:focus:ring-red-900">
|
||||
@Localizer["YesImSureButton"]
|
||||
@@ -48,7 +49,7 @@ else
|
||||
public Guid Id { get; set; }
|
||||
|
||||
private bool IsLoading { get; set; } = true;
|
||||
private Credential? Obj { get; set; }
|
||||
private Item? Obj { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -66,7 +67,7 @@ else
|
||||
if (firstRender)
|
||||
{
|
||||
// Load existing Obj, retrieve from service
|
||||
Obj = await CredentialService.LoadEntryAsync(Id);
|
||||
Obj = await ItemService.LoadEntryAsync(Id);
|
||||
|
||||
// Hide loading spinner
|
||||
IsLoading = false;
|
||||
@@ -85,7 +86,7 @@ else
|
||||
}
|
||||
|
||||
GlobalLoadingSpinner.Show(Localizer["DeletingCredentialMessage"]);
|
||||
if (await CredentialService.SoftDeleteEntryAsync(Id))
|
||||
if (await ItemService.TrashItemAsync(Id))
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["DeleteSuccessMessage"]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/credentials"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@using AliasVault.RazorComponents.Tables
|
||||
@using AliasVault.Client.Main.Models
|
||||
@using Microsoft.Extensions.Localization
|
||||
@@ -164,9 +164,9 @@ else
|
||||
private bool IsLoading { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the credentials.
|
||||
/// Gets or sets the items.
|
||||
/// </summary>
|
||||
private List<CredentialListEntry> Credentials { get; set; } = new();
|
||||
private List<ItemListEntry> Credentials { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the settings dropdown is shown.
|
||||
@@ -202,9 +202,9 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credentials filtered and sorted according to the current filter and sort order.
|
||||
/// Gets the items filtered and sorted according to the current filter and sort order.
|
||||
/// </summary>
|
||||
private IEnumerable<CredentialListEntry> FilteredAndSortedCredentials
|
||||
private IEnumerable<ItemListEntry> FilteredAndSortedCredentials
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -303,23 +303,23 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and/or refreshes the credentials.
|
||||
/// Loads and/or refreshes the items.
|
||||
/// </summary>
|
||||
private async Task LoadCredentialsAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
// Load the aliases from the webapi via AliasService.
|
||||
var credentialListEntries = await CredentialService.GetListAsync();
|
||||
if (credentialListEntries is null)
|
||||
// Load the items from the database via ItemService.
|
||||
var itemListEntries = await ItemService.GetListAsync();
|
||||
if (itemListEntries is null)
|
||||
{
|
||||
// Error loading aliases.
|
||||
// Error loading items.
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["FailedToLoadCredentialsMessage"], true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (credentialListEntries.Count == 0 && !DbService.Settings.TutorialDone)
|
||||
if (itemListEntries.Count == 0 && !DbService.Settings.TutorialDone)
|
||||
{
|
||||
// Redirect to the welcome page.
|
||||
NavigationManager.NavigateTo("/welcome");
|
||||
@@ -327,7 +327,7 @@ else
|
||||
}
|
||||
|
||||
// Pass unsorted list to the view - sorting will be handled by the table/grid components
|
||||
Credentials = credentialListEntries;
|
||||
Credentials = itemListEntries;
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/credentials/{id:guid}"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@implements IAsyncDisposable
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
|
||||
<LayoutPageTitle>@Localizer["ViewCredentialsPageTitle"]</LayoutPageTitle>
|
||||
|
||||
@@ -33,34 +35,40 @@ else
|
||||
<div class="col-span-1 md:col-span-2 lg:col-span-1">
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="items-center flex space-x-4">
|
||||
<DisplayFavicon FaviconBytes="@Alias.Service.Logo" Padding="true" />
|
||||
<DisplayFavicon FaviconBytes="@Alias.Logo?.FileData" Padding="true" />
|
||||
|
||||
<div>
|
||||
<h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">@Alias.Service.Name</h3>
|
||||
@if (Alias.Service.Url is not null && Alias.Service.Url.Length > 0)
|
||||
<h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">@Alias.Name</h3>
|
||||
@{
|
||||
var url = ItemService.GetFieldValue(Alias, FieldKey.LoginUrl);
|
||||
}
|
||||
@if (url is not null && url.Length > 0)
|
||||
{
|
||||
@if (Alias.Service.Url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || Alias.Service.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
@if (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
<a href="@Alias.Service.Url" target="_blank" class="text-blue-500 break-all dark:text-blue-400">@Alias.Service.Url</a>
|
||||
<a href="@url" target="_blank" class="text-blue-500 break-all dark:text-blue-400">@url</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-gray-700 break-all dark:text-gray-300">@Alias.Service.Url</span>
|
||||
<span class="text-gray-700 break-all dark:text-gray-300">@url</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RecentEmails EmailAddress="@Alias.Alias.Email" />
|
||||
<RecentEmails EmailAddress="@ItemService.GetFieldValue(Alias, FieldKey.LoginEmail)" />
|
||||
|
||||
@if (Alias.TotpCodes.Count > 0)
|
||||
{
|
||||
<TotpViewer TotpCodeList="@Alias.TotpCodes" />
|
||||
}
|
||||
|
||||
@if (Alias.Notes != null && Alias.Notes.Length > 0)
|
||||
@{
|
||||
var notes = ItemService.GetFieldValue(Alias, FieldKey.NotesContent);
|
||||
}
|
||||
@if (notes != null && notes.Length > 0)
|
||||
{
|
||||
<FormattedNote Notes="@Alias.Notes" />
|
||||
<FormattedNote Notes="@notes" />
|
||||
}
|
||||
|
||||
@if (Alias.Attachments.Count > 0)
|
||||
@@ -72,7 +80,10 @@ else
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<h3 class="mb-2 text-xl font-semibold dark:text-white">@Localizer["LoginCredentialsSection"]</h3>
|
||||
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
@if (EmailService.IsAliasVaultSupportedDomain(Alias.Alias.Email ?? string.Empty))
|
||||
@{
|
||||
var email = ItemService.GetFieldValue(Alias, FieldKey.LoginEmail);
|
||||
}
|
||||
@if (EmailService.IsAliasVaultSupportedDomain(email ?? string.Empty))
|
||||
{
|
||||
<span>@Localizer["GeneratedCredentialsDescription"]</span>
|
||||
}
|
||||
@@ -86,11 +97,12 @@ else
|
||||
@if (Alias.Passkeys != null && Alias.Passkeys.Any())
|
||||
{
|
||||
var passkey = Alias.Passkeys.First();
|
||||
var username = ItemService.GetFieldValue(Alias, FieldKey.LoginUsername);
|
||||
@* With passkey: Username, Passkey, Email, Password *@
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Username))
|
||||
@if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPasteFormRow Id="username" Label="@Localizer["UsernameLabel"]" Value="@(Alias.Username)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Id="username" Label="@Localizer["UsernameLabel"]" Value="@username"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
<div class="col-span-6">
|
||||
@@ -126,38 +138,38 @@ else
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.Email))
|
||||
@if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPasteFormRow Id="email" Label="@Localizer["EmailLabel"]" Value="@Alias.Alias.Email"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Id="email" Label="@Localizer["EmailLabel"]" Value="@email"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Passwords.FirstOrDefault()?.Value))
|
||||
@if (!string.IsNullOrWhiteSpace(ItemService.GetFieldValue(Alias, FieldKey.LoginPassword)))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPastePasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" Value="@(Alias.Passwords.FirstOrDefault()?.Value ?? string.Empty)"></CopyPastePasswordFormRow>
|
||||
<CopyPastePasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" Value="@ItemService.GetFieldValue(Alias, FieldKey.LoginPassword)"></CopyPastePasswordFormRow>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Without passkey: Email, Username, Password *@
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.Email))
|
||||
@if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPasteFormRow Id="email" Label="@Localizer["EmailLabel"]" Value="@Alias.Alias.Email"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Id="email" Label="@Localizer["EmailLabel"]" Value="@email"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Username))
|
||||
@if (!string.IsNullOrWhiteSpace(ItemService.GetFieldValue(Alias, FieldKey.LoginUsername)))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPasteFormRow Id="username" Label="@Localizer["UsernameLabel"]" Value="@(Alias.Username)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Id="username" Label="@Localizer["UsernameLabel"]" Value="@ItemService.GetFieldValue(Alias, FieldKey.LoginUsername)"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Passwords.FirstOrDefault()?.Value))
|
||||
@if (!string.IsNullOrWhiteSpace(ItemService.GetFieldValue(Alias, FieldKey.LoginPassword)))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPastePasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" Value="@(Alias.Passwords.FirstOrDefault()?.Value ?? string.Empty)"></CopyPastePasswordFormRow>
|
||||
<CopyPastePasswordFormRow Id="password" Label="@Localizer["PasswordLabel"]" Value="@ItemService.GetFieldValue(Alias, FieldKey.LoginPassword)"></CopyPastePasswordFormRow>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -170,34 +182,40 @@ else
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["AliasSection"]</h3>
|
||||
<form action="#">
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.FirstName) && !string.IsNullOrWhiteSpace(Alias.Alias.LastName))
|
||||
@{
|
||||
var firstName = ItemService.GetFieldValue(Alias, FieldKey.AliasFirstName);
|
||||
var lastName = ItemService.GetFieldValue(Alias, FieldKey.AliasLastName);
|
||||
var birthDateStr = ItemService.GetFieldValue(Alias, FieldKey.AliasBirthdate);
|
||||
var nickname = ItemService.GetFieldValue(Alias, FieldKey.LoginUsername);
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(firstName) && !string.IsNullOrWhiteSpace(lastName))
|
||||
{
|
||||
<div class="col-span-6">
|
||||
<CopyPasteFormRow Label="@Localizer["FullNameLabel"]" Value="@(Alias.Alias.FirstName + " " + Alias.Alias.LastName)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Label="@Localizer["FullNameLabel"]" Value="@(firstName + " " + lastName)"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.FirstName))
|
||||
@if (!string.IsNullOrWhiteSpace(firstName))
|
||||
{
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<CopyPasteFormRow Label="@Localizer["FirstNameLabel"]" Value="@(Alias.Alias.FirstName)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Label="@Localizer["FirstNameLabel"]" Value="@firstName"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.LastName))
|
||||
@if (!string.IsNullOrWhiteSpace(lastName))
|
||||
{
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<CopyPasteFormRow Label="@Localizer["LastNameLabel"]" Value="@(Alias.Alias.LastName)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Label="@Localizer["LastNameLabel"]" Value="@lastName"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (IsValidDate(Alias.Alias.BirthDate))
|
||||
@if (!string.IsNullOrWhiteSpace(birthDateStr))
|
||||
{
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<CopyPasteFormRow Label="@Localizer["BirthdateLabel"]" Value="@(Alias.Alias.BirthDate.ToString("yyyy-MM-dd"))"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Label="@Localizer["BirthdateLabel"]" Value="@birthDateStr"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Alias.Alias.NickName))
|
||||
@if (!string.IsNullOrWhiteSpace(nickname))
|
||||
{
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<CopyPasteFormRow Label="@Localizer["NicknameLabel"]" Value="@(Alias.Alias.NickName)"></CopyPasteFormRow>
|
||||
<CopyPasteFormRow Label="@Localizer["NicknameLabel"]" Value="@nickname"></CopyPasteFormRow>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -217,7 +235,7 @@ else
|
||||
[Parameter]
|
||||
public Guid Id { get; set; }
|
||||
private bool IsLoading { get; set; } = true;
|
||||
private Credential? Alias { get; set; } = new();
|
||||
private Item? Alias { get; set; } = new();
|
||||
private bool HasAlias { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -236,21 +254,20 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the alias has any valid data.
|
||||
/// Checks if the item has any valid alias data.
|
||||
/// </summary>
|
||||
/// <param name="alias">The credential containing alias information.</param>
|
||||
/// <returns>True if the alias has any valid data, false otherwise.</returns>
|
||||
private static bool CheckHasAlias(Credential alias)
|
||||
/// <param name="item">The item containing alias information.</param>
|
||||
/// <returns>True if the item has any valid alias data, false otherwise.</returns>
|
||||
private static bool CheckHasAlias(Item item)
|
||||
{
|
||||
if (alias?.Alias == null)
|
||||
if (item == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !string.IsNullOrWhiteSpace(alias.Alias.FirstName) ||
|
||||
!string.IsNullOrWhiteSpace(alias.Alias.LastName) ||
|
||||
!string.IsNullOrWhiteSpace(alias.Alias.NickName) ||
|
||||
IsValidDate(alias.Alias.BirthDate);
|
||||
return !string.IsNullOrWhiteSpace(ItemService.GetFieldValue(item, FieldKey.AliasFirstName)) ||
|
||||
!string.IsNullOrWhiteSpace(ItemService.GetFieldValue(item, FieldKey.AliasLastName)) ||
|
||||
!string.IsNullOrWhiteSpace(ItemService.GetFieldValue(item, FieldKey.AliasBirthdate));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -268,25 +285,25 @@ else
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the credential.
|
||||
/// Loads the item.
|
||||
/// </summary>
|
||||
private async Task LoadEntryAsync()
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
// Load the aliases from the webapi via AliasService.
|
||||
Alias = await CredentialService.LoadEntryAsync(Id);
|
||||
// Load the item from the database via ItemService.
|
||||
Alias = await ItemService.LoadEntryAsync(Id);
|
||||
|
||||
if (Alias is null)
|
||||
{
|
||||
// Error loading alias.
|
||||
// Error loading item.
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["CredentialNotFoundError"]);
|
||||
NavigationManager.NavigateTo("/credentials", false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the alias has any valid data
|
||||
// Check if the item has any valid alias data
|
||||
HasAlias = CheckHasAlias(Alias);
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
@@ -302,14 +302,19 @@ else
|
||||
if (mailbox?.Mails != null)
|
||||
{
|
||||
var context = await DbService.GetDbContextAsync();
|
||||
var credentialLookup = await context.Credentials
|
||||
.Include(x => x.Service)
|
||||
.Include(x => x.Alias)
|
||||
.Where(x => x.Alias.Email != null)
|
||||
.GroupBy(x => x.Alias.Email!.ToLower())
|
||||
var credentialLookup = await context.Items
|
||||
.Include(x => x.FieldValues)
|
||||
.Where(x => !x.IsDeleted && x.DeletedAt == null)
|
||||
.Where(x => x.FieldValues.Any(fv => fv.FieldKey == AliasClientDb.Models.FieldKey.LoginEmail && fv.Value != null && !fv.IsDeleted))
|
||||
.Select(x => new {
|
||||
x.Id,
|
||||
x.Name,
|
||||
Email = x.FieldValues.FirstOrDefault(fv => fv.FieldKey == AliasClientDb.Models.FieldKey.LoginEmail && !fv.IsDeleted)!.Value!.ToLower()
|
||||
})
|
||||
.GroupBy(x => x.Email)
|
||||
.ToDictionaryAsync(
|
||||
g => g.Key,
|
||||
g => new { Id = g.First().Id, ServiceName = g.First().Service.Name ?? "Unknown" }
|
||||
g => new { Id = g.First().Id, ServiceName = g.First().Name ?? "Unknown" }
|
||||
);
|
||||
|
||||
List<MailboxEmailApiModel> decryptedEmailList;
|
||||
|
||||
@@ -19,11 +19,7 @@
|
||||
|
||||
private static async Task<List<ImportedCredential>> ProcessFile(string fileContents)
|
||||
{
|
||||
var importedCredentials = await Task.Run(() =>
|
||||
{
|
||||
return AliasVault.ImportExport.CredentialCsvService.ImportCredentialsFromCsv(fileContents);
|
||||
});
|
||||
|
||||
var importedCredentials = await AliasVault.ImportExport.ItemCsvService.ImportItemsFromCsv(fileContents);
|
||||
return importedCredentials;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@inject ILogger<ImportServiceCard> Logger
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject DbService DbService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@@ -9,6 +9,8 @@
|
||||
@using AliasVault.ImportExport.Importers
|
||||
@using AliasVault.ImportExport.Models
|
||||
@using AliasVault.Shared.Models.WebApi.Favicon
|
||||
@using AliasClientDb
|
||||
@using AliasClientDb.Models
|
||||
|
||||
<div @onclick="OpenImportModal" class="flex flex-col p-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
|
||||
<div class="flex items-center">
|
||||
@@ -436,14 +438,14 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports the credentials to the database.
|
||||
/// Imports the items to the database.
|
||||
/// </summary>
|
||||
private async Task ImportCredentialsToDatabase()
|
||||
{
|
||||
var credentials = BaseImporter.ConvertToCredential(ImportedCredentials);
|
||||
foreach (var credential in credentials)
|
||||
var items = BaseImporter.ConvertToItem(ImportedCredentials);
|
||||
foreach (var item in items)
|
||||
{
|
||||
await ProcessSingleCredential(credential);
|
||||
await ProcessSingleItem(item);
|
||||
await Task.Delay(2); // Small delay to avoid blocking the UI thread
|
||||
}
|
||||
|
||||
@@ -460,16 +462,30 @@
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a single credential.
|
||||
/// Processes a single item.
|
||||
/// </summary>
|
||||
/// <param name="credential">The credential to process.</param>
|
||||
private async Task ProcessSingleCredential(Credential credential)
|
||||
/// <param name="item">The item to process.</param>
|
||||
private async Task ProcessSingleItem(Item item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(credential.Service.Url) && ExtractedFavicons.TryGetValue(credential.Service.Url, out var favicon))
|
||||
var url = ItemService.GetFieldValue(item, FieldKey.LoginUrl);
|
||||
if (!string.IsNullOrEmpty(url) && ExtractedFavicons.TryGetValue(url, out var favicon))
|
||||
{
|
||||
credential.Service.Logo = favicon;
|
||||
// Store favicon in Logo entity
|
||||
var logo = new Logo
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Source = new Uri(url).Host,
|
||||
FileData = favicon,
|
||||
MimeType = "image/png",
|
||||
FetchedAt = DateTime.UtcNow,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
var context = await DbService.GetDbContextAsync();
|
||||
context.Logos.Add(logo);
|
||||
item.LogoId = logo.Id;
|
||||
}
|
||||
await CredentialService.InsertEntryAsync(credential, false, false);
|
||||
await ItemService.InsertEntryAsync(item, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -533,12 +549,14 @@
|
||||
/// </summary>
|
||||
private async Task DetectAndRemoveDuplicates()
|
||||
{
|
||||
var existingCredentials = await CredentialService.LoadAllAsync();
|
||||
var existingItems = await ItemService.LoadAllAsync();
|
||||
var duplicates = ImportedCredentials.Where(imported =>
|
||||
existingCredentials.Any(existing =>
|
||||
existing.Service.Name != null && existing.Service.Name.Equals(imported.ServiceName, StringComparison.OrdinalIgnoreCase) &&
|
||||
existing.Username != null && existing.Username.Equals(imported.Username, StringComparison.OrdinalIgnoreCase) &&
|
||||
existing.Passwords.Any(p => p.Value != null && p.Value.Equals(imported.Password, StringComparison.OrdinalIgnoreCase))
|
||||
existingItems.Any(existing =>
|
||||
existing.Name != null && existing.Name.Equals(imported.ServiceName, StringComparison.OrdinalIgnoreCase) &&
|
||||
ItemService.GetFieldValue(existing, FieldKey.LoginUsername) != null &&
|
||||
ItemService.GetFieldValue(existing, FieldKey.LoginUsername)!.Equals(imported.Username, StringComparison.OrdinalIgnoreCase) &&
|
||||
ItemService.GetFieldValue(existing, FieldKey.LoginPassword) != null &&
|
||||
ItemService.GetFieldValue(existing, FieldKey.LoginPassword)!.Equals(imported.Password, StringComparison.OrdinalIgnoreCase)
|
||||
)).ToList();
|
||||
|
||||
DuplicateCredentialsCount = duplicates.Count;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/settings/import-export"
|
||||
@inherits MainBase
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject ILogger<ImportExport> Logger
|
||||
@inject ConfirmModalService ConfirmModalService
|
||||
@using Microsoft.Extensions.Localization
|
||||
@@ -123,9 +123,9 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var credentials = await CredentialService.LoadAllAsync();
|
||||
var items = await ItemService.LoadAllAsync();
|
||||
|
||||
var csvBytes = CredentialCsvService.ExportCredentialsToCsv(credentials);
|
||||
var csvBytes = ItemCsvService.ExportItemsToCsv(items);
|
||||
|
||||
// Create a memory stream from the byte array.
|
||||
using (MemoryStream memoryStream = new MemoryStream(csvBytes))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/settings/import-export/reset-vault"
|
||||
@inherits MainBase
|
||||
@inject HttpClient Http
|
||||
@inject CredentialService CredentialService
|
||||
@inject ItemService ItemService
|
||||
@inject ILogger<ResetVault> Logger
|
||||
@using System.Text.Json
|
||||
@using AliasVault.Client.Utilities
|
||||
@@ -171,8 +171,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear local vault data by hard-deleting all credentials
|
||||
await CredentialService.HardDeleteAllCredentialsAsync();
|
||||
// Clear local vault data by hard-deleting all items
|
||||
await ItemService.HardDeleteAllItemsAsync();
|
||||
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["ResetVaultSuccessMessage"]);
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
var context = await DbService.GetDbContextAsync();
|
||||
SoftDeletedCredentialCount = await context.Credentials.CountAsync(c => c.IsDeleted);
|
||||
SoftDeletedCredentialCount = await context.Items.CountAsync(i => i.IsDeleted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ builder.Services.AddTransient<AliasVaultApiHandlerService>();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddScoped<UserRegistrationService>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();
|
||||
builder.Services.AddScoped<CredentialService>();
|
||||
builder.Services.AddScoped<ItemService>();
|
||||
builder.Services.AddScoped<DbService>();
|
||||
builder.Services.AddScoped<GlobalNotificationService>();
|
||||
builder.Services.AddScoped<GlobalLoadingService>();
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="CredentialService.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Services;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AliasClientDb;
|
||||
using AliasVault.Client.Utilities;
|
||||
using AliasVault.Shared.Models.WebApi.Favicon;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Service class for credential operations.
|
||||
/// </summary>
|
||||
public sealed class CredentialService(HttpClient httpClient, DbService dbService, Config config, JsInteropService jsInteropService)
|
||||
{
|
||||
/// <summary>
|
||||
/// The default service URL used as placeholder in forms. When this value is set, the URL field is considered empty
|
||||
/// and a null value is stored in the database.
|
||||
/// </summary>
|
||||
public const string DefaultServiceUrl = "https://";
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random password for a credential using the specified settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">PasswordSettings model.</param>
|
||||
/// <returns>Random password.</returns>
|
||||
public async Task<string> GenerateRandomPasswordAsync(PasswordSettings settings)
|
||||
{
|
||||
// Sanity check: if all settings are false, then default to use lowercase letters only.
|
||||
if (!settings.UseLowercase && !settings.UseUppercase && !settings.UseNumbers && !settings.UseSpecialChars && !settings.UseNonAmbiguousChars)
|
||||
{
|
||||
settings.UseLowercase = true;
|
||||
}
|
||||
|
||||
return await jsInteropService.GenerateRandomPasswordAsync(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random identity for a credential.
|
||||
/// </summary>
|
||||
/// <param name="credential">The credential object to update.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task<Credential> GenerateRandomIdentityAsync(Credential credential)
|
||||
{
|
||||
const int MaxAttempts = 5;
|
||||
var attempts = 0;
|
||||
bool isEmailTaken;
|
||||
|
||||
do
|
||||
{
|
||||
// Convert age range to birthdate options using shared JS utility
|
||||
var birthdateOptions = await jsInteropService.ConvertAgeRangeToBirthdateOptionsAsync(dbService.Settings.DefaultIdentityAgeRange);
|
||||
|
||||
// Get the effective identity language (smart default based on UI language if no explicit override is set)
|
||||
var identityLanguage = await GetEffectiveIdentityLanguageAsync();
|
||||
|
||||
// Generate a random identity using the TypeScript library
|
||||
var identity = await jsInteropService.GenerateRandomIdentityAsync(identityLanguage, dbService.Settings.DefaultIdentityGender, birthdateOptions);
|
||||
|
||||
// Generate random values for the Identity properties
|
||||
credential.Username = identity.NickName;
|
||||
credential.Alias.FirstName = identity.FirstName;
|
||||
credential.Alias.LastName = identity.LastName;
|
||||
credential.Alias.NickName = identity.NickName;
|
||||
credential.Alias.Gender = identity.Gender;
|
||||
credential.Alias.BirthDate = string.IsNullOrEmpty(identity.BirthDate) ? DateTime.MinValue : DateTimeFormatter.Parse(identity.BirthDate);
|
||||
|
||||
// Set the email
|
||||
var emailDomain = GetDefaultEmailDomain();
|
||||
credential.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
|
||||
|
||||
// Check if email is already taken
|
||||
try
|
||||
{
|
||||
var response = await httpClient.PostAsync($"v1/Identity/CheckEmail/{credential.Alias.Email}", null);
|
||||
var result = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
|
||||
isEmailTaken = result?["isTaken"] ?? false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If the API call fails, assume email is not taken to allow operation to continue
|
||||
isEmailTaken = false;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
while (isEmailTaken && attempts < MaxAttempts);
|
||||
|
||||
// Generate password using the TypeScript library
|
||||
var passwordSettings = dbService.Settings.PasswordSettings;
|
||||
credential.Passwords.First().Value = await jsInteropService.GenerateRandomPasswordAsync(passwordSettings);
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default email domain based on settings and available domains.
|
||||
/// </summary>
|
||||
/// <returns>Default email domain.</returns>
|
||||
public string GetDefaultEmailDomain()
|
||||
{
|
||||
var defaultDomain = dbService.Settings.DefaultEmailDomain;
|
||||
|
||||
// Function to check if a domain is valid (not disabled, not hidden, and exists in domain lists)
|
||||
// TODO: "DISABLED.TLD" was a placeholder used < 0.22.0 that has been replaced by an empty string.
|
||||
// That value is still here for legacy purposes, but it can be removed from the codebase in a future release.
|
||||
bool IsValidDomain(string domain) =>
|
||||
!string.IsNullOrEmpty(domain) &&
|
||||
domain != "DISABLED.TLD" &&
|
||||
!config.HiddenPrivateEmailDomains.Contains(domain) &&
|
||||
(config.PublicEmailDomains.Contains(domain) || config.PrivateEmailDomains.Contains(domain));
|
||||
|
||||
// Get the first valid domain from private or public domains (excluding hidden ones)
|
||||
string GetFirstValidDomain() =>
|
||||
config.PrivateEmailDomains.Find(IsValidDomain) ??
|
||||
config.PublicEmailDomains.FirstOrDefault() ??
|
||||
"example.com";
|
||||
|
||||
// Use the default domain if it's valid (not hidden), otherwise get the first valid domain
|
||||
string domainToUse = IsValidDomain(defaultDomain) ? defaultDomain : GetFirstValidDomain();
|
||||
|
||||
return domainToUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert new entry into database.
|
||||
/// </summary>
|
||||
/// <param name="loginObject">Login object to insert.</param>
|
||||
/// <param name="saveToDb">Whether to commit changes to database. Defaults to true, but can be set to false if entries are added in bulk by caller.</param>
|
||||
/// <param name="extractFavicon">Whether to extract the favicon from the service URL. Defaults to true.</param>
|
||||
/// <returns>Guid of inserted entry.</returns>
|
||||
public async Task<Guid> InsertEntryAsync(Credential loginObject, bool saveToDb = true, bool extractFavicon = true)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Try to extract favicon from service URL
|
||||
if (extractFavicon)
|
||||
{
|
||||
await ExtractFaviconAsync(loginObject);
|
||||
}
|
||||
|
||||
// If the email starts with an @ it is most likely still the placeholder which hasn't been filled.
|
||||
// So we remove it.
|
||||
if (loginObject.Alias.Email is not null && loginObject.Alias.Email.StartsWith('@'))
|
||||
{
|
||||
loginObject.Alias.Email = null;
|
||||
}
|
||||
|
||||
// If the URL equals the placeholder, we set it to null.
|
||||
if (loginObject.Service.Url == DefaultServiceUrl)
|
||||
{
|
||||
loginObject.Service.Url = null;
|
||||
}
|
||||
|
||||
var currentDateTime = DateTime.UtcNow;
|
||||
var login = new Credential
|
||||
{
|
||||
CreatedAt = currentDateTime,
|
||||
UpdatedAt = currentDateTime,
|
||||
Notes = loginObject.Notes,
|
||||
Username = loginObject.Username,
|
||||
Alias = new Alias()
|
||||
{
|
||||
NickName = loginObject.Alias.NickName,
|
||||
FirstName = loginObject.Alias.FirstName,
|
||||
LastName = loginObject.Alias.LastName,
|
||||
BirthDate = loginObject.Alias.BirthDate,
|
||||
Gender = loginObject.Alias.Gender,
|
||||
Email = loginObject.Alias.Email,
|
||||
CreatedAt = currentDateTime,
|
||||
UpdatedAt = currentDateTime,
|
||||
},
|
||||
Service = new Service()
|
||||
{
|
||||
Name = loginObject.Service.Name,
|
||||
Url = loginObject.Service.Url,
|
||||
Logo = loginObject.Service.Logo,
|
||||
CreatedAt = currentDateTime,
|
||||
UpdatedAt = currentDateTime,
|
||||
},
|
||||
};
|
||||
|
||||
login.Passwords.Add(new Password()
|
||||
{
|
||||
Value = loginObject.Passwords.First().Value,
|
||||
});
|
||||
|
||||
foreach (var attachment in loginObject.Attachments)
|
||||
{
|
||||
login.Attachments.Add(attachment);
|
||||
}
|
||||
|
||||
// Add TOTP codes
|
||||
foreach (var totpCode in loginObject.TotpCodes)
|
||||
{
|
||||
login.TotpCodes.Add(totpCode);
|
||||
}
|
||||
|
||||
context.Credentials.Add(login);
|
||||
|
||||
// Add password.
|
||||
login.Passwords.Add(loginObject.Passwords.First());
|
||||
|
||||
// Save the database to the server if saveToDb is true.
|
||||
if (saveToDb && !await dbService.SaveDatabaseAsync())
|
||||
{
|
||||
// If saving database to server failed, return empty guid to indicate error.
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return login.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an existing entry to database.
|
||||
/// </summary>
|
||||
/// <param name="loginObject">Login object to update.</param>
|
||||
/// <returns>Guid of updated entry.</returns>
|
||||
public async Task<Guid> UpdateEntryAsync(Credential loginObject)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Try to extract favicon from service URL
|
||||
await ExtractFaviconAsync(loginObject);
|
||||
|
||||
// Get the existing entry.
|
||||
var login = await LoadEntryAsync(loginObject.Id);
|
||||
if (login is null)
|
||||
{
|
||||
throw new InvalidOperationException("Login object not found.");
|
||||
}
|
||||
|
||||
if (loginObject.Alias.Email is not null && loginObject.Alias.Email.StartsWith('@'))
|
||||
{
|
||||
loginObject.Alias.Email = null;
|
||||
}
|
||||
|
||||
// If the URL equals the placeholder, we set it to null.
|
||||
if (loginObject.Service.Url == DefaultServiceUrl)
|
||||
{
|
||||
loginObject.Service.Url = null;
|
||||
}
|
||||
|
||||
// Update all fields and collections with current timestamp.
|
||||
var updateDateTime = DateTime.UtcNow;
|
||||
UpdateBasicCredentialInfo(login, loginObject, updateDateTime);
|
||||
UpdateAttachments(context, login, loginObject);
|
||||
UpdateTotpCodes(context, login, loginObject);
|
||||
|
||||
if (!await dbService.SaveDatabaseAsync())
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return login.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load existing entry from database.
|
||||
/// </summary>
|
||||
/// <param name="loginId">Id of login to load.</param>
|
||||
/// <returns>Alias object.</returns>
|
||||
public async Task<Credential?> LoadEntryAsync(Guid loginId)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var loginObject = await context.Credentials
|
||||
.Include(x => x.Passwords)
|
||||
.Include(x => x.Alias)
|
||||
.Include(x => x.Service)
|
||||
.Include(x => x.Attachments)
|
||||
.Include(x => x.TotpCodes)
|
||||
.Include(x => x.Passkeys)
|
||||
.AsSplitQuery()
|
||||
.Where(x => x.Id == loginId)
|
||||
.Where(x => !x.IsDeleted)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (loginObject != null)
|
||||
{
|
||||
// Filter out deleted items from collections after loading
|
||||
loginObject.Passwords = loginObject.Passwords.Where(p => !p.IsDeleted).ToList();
|
||||
loginObject.Attachments = loginObject.Attachments.Where(a => !a.IsDeleted).ToList();
|
||||
loginObject.TotpCodes = loginObject.TotpCodes.Where(t => !t.IsDeleted).ToList();
|
||||
loginObject.Passkeys = loginObject.Passkeys.Where(p => !p.IsDeleted).ToList();
|
||||
}
|
||||
|
||||
return loginObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all entries from database.
|
||||
/// </summary>
|
||||
/// <returns>Alias object.</returns>
|
||||
public async Task<List<Credential>> LoadAllAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var loginObject = await context.Credentials
|
||||
.Include(x => x.Passwords.Where(p => !p.IsDeleted))
|
||||
.Include(x => x.Alias)
|
||||
.Include(x => x.Service)
|
||||
.Include(x => x.Attachments.Where(a => !a.IsDeleted))
|
||||
.Include(x => x.TotpCodes.Where(t => !t.IsDeleted))
|
||||
.Include(x => x.Passkeys.Where(p => !p.IsDeleted))
|
||||
.AsSplitQuery()
|
||||
.Where(x => !x.IsDeleted)
|
||||
.ToListAsync();
|
||||
|
||||
return loginObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list with all login entries.
|
||||
/// </summary>
|
||||
/// <returns>List of CredentialListEntry objects.</returns>
|
||||
public async Task<List<CredentialListEntry>?> GetListAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Retrieve all aliases from client DB.
|
||||
var credentials = await context.Credentials
|
||||
.Include(x => x.Alias)
|
||||
.Include(x => x.Service)
|
||||
.Include(x => x.Passkeys.Where(p => !p.IsDeleted))
|
||||
.Include(x => x.Passwords.Where(p => !p.IsDeleted))
|
||||
.Include(x => x.Attachments.Where(a => !a.IsDeleted))
|
||||
.AsSplitQuery()
|
||||
.Where(x => !x.IsDeleted)
|
||||
.ToListAsync();
|
||||
|
||||
// Map to CredentialListEntry with proper boolean logic
|
||||
return credentials.Select(x => new CredentialListEntry
|
||||
{
|
||||
Id = x.Id,
|
||||
Logo = x.Service.Logo,
|
||||
Service = x.Service.Name,
|
||||
Username = x.Username,
|
||||
Email = x.Alias.Email,
|
||||
CreatedAt = x.CreatedAt,
|
||||
HasPasskey = x.Passkeys != null && x.Passkeys.Any(),
|
||||
HasAlias = !string.IsNullOrWhiteSpace(x.Alias.FirstName) ||
|
||||
!string.IsNullOrWhiteSpace(x.Alias.LastName) ||
|
||||
!string.IsNullOrWhiteSpace(x.Alias.NickName) ||
|
||||
!string.IsNullOrWhiteSpace(x.Alias.Gender) ||
|
||||
x.Alias.BirthDate.Year > 1,
|
||||
HasUsernameOrPassword = !string.IsNullOrWhiteSpace(x.Username) ||
|
||||
(x.Passwords != null && x.Passwords.Any(p => !string.IsNullOrWhiteSpace(p.Value))),
|
||||
HasAttachment = x.Attachments != null && x.Attachments.Any(),
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft deletes an existing entry from database. NOTE: all user actions should be handled via this soft deletion.
|
||||
/// Permanently deleting entries is handled by periodic database cleanup job. The soft-delete mechanism
|
||||
/// is required in order to synchronize the deletion of entries across multiple client vault versions.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of alias to delete.</param>
|
||||
/// <returns>Bool which indicates if deletion and saving database was successful.</returns>
|
||||
public async Task<bool> SoftDeleteEntryAsync(Guid id)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var login = await context.Credentials
|
||||
.Include(x => x.Passkeys)
|
||||
.Where(x => x.Id == id)
|
||||
.FirstAsync();
|
||||
|
||||
var deleteDateTime = DateTime.UtcNow;
|
||||
|
||||
login.IsDeleted = true;
|
||||
login.UpdatedAt = deleteDateTime;
|
||||
|
||||
// Mark associated alias and service as deleted
|
||||
var alias = await context.Aliases
|
||||
.Where(x => x.Id == login.Alias.Id)
|
||||
.FirstAsync();
|
||||
alias.IsDeleted = true;
|
||||
alias.UpdatedAt = deleteDateTime;
|
||||
|
||||
var service = await context.Services
|
||||
.Where(x => x.Id == login.Service.Id)
|
||||
.FirstAsync();
|
||||
service.IsDeleted = true;
|
||||
service.UpdatedAt = deleteDateTime;
|
||||
|
||||
// Mark associated passkeys as deleted
|
||||
foreach (var passkey in login.Passkeys)
|
||||
{
|
||||
passkey.IsDeleted = true;
|
||||
passkey.UpdatedAt = deleteDateTime;
|
||||
}
|
||||
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hard delete all credentials from the database. This permanently removes all credential records
|
||||
/// (including soft-deleted ones) from the database for a complete vault reset.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public async Task<bool> HardDeleteAllCredentialsAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Hard delete all attachments, aliases, services and credentials.
|
||||
context.Attachments.RemoveRange(context.Attachments);
|
||||
context.Aliases.RemoveRange(context.Aliases);
|
||||
context.Services.RemoveRange(context.Services);
|
||||
context.Credentials.RemoveRange(context.Credentials);
|
||||
|
||||
// Save changes locally
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Save the database to server
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a passkey by marking it as deleted.
|
||||
/// </summary>
|
||||
/// <param name="passkeyId">The ID of the passkey to delete.</param>
|
||||
/// <returns>A value indicating whether the deletion was successful.</returns>
|
||||
public async Task<bool> DeletePasskeyAsync(Guid passkeyId)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
var passkey = await context.Passkeys.FirstOrDefaultAsync(p => p.Id == passkeyId);
|
||||
|
||||
if (passkey != null)
|
||||
{
|
||||
var deleteDateTime = DateTime.UtcNow;
|
||||
passkey.IsDeleted = true;
|
||||
passkey.UpdatedAt = deleteDateTime;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Save to server
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the basic credential information.
|
||||
/// </summary>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
/// <param name="updateDateTime">The datetime to use for UpdatedAt fields.</param>
|
||||
private static void UpdateBasicCredentialInfo(Credential login, Credential loginObject, DateTime updateDateTime)
|
||||
{
|
||||
login.UpdatedAt = updateDateTime;
|
||||
login.Notes = loginObject.Notes;
|
||||
login.Username = loginObject.Username;
|
||||
|
||||
login.Alias.NickName = loginObject.Alias.NickName;
|
||||
login.Alias.FirstName = loginObject.Alias.FirstName;
|
||||
login.Alias.LastName = loginObject.Alias.LastName;
|
||||
login.Alias.BirthDate = loginObject.Alias.BirthDate;
|
||||
login.Alias.Gender = loginObject.Alias.Gender;
|
||||
login.Alias.Email = loginObject.Alias.Email;
|
||||
login.Alias.UpdatedAt = updateDateTime;
|
||||
|
||||
login.Passwords = loginObject.Passwords;
|
||||
if (login.Passwords.Count > 0)
|
||||
{
|
||||
login.Passwords.First().UpdatedAt = updateDateTime;
|
||||
}
|
||||
|
||||
login.Service.Name = loginObject.Service.Name;
|
||||
login.Service.Url = loginObject.Service.Url;
|
||||
login.Service.Logo = loginObject.Service.Logo;
|
||||
login.Service.UpdatedAt = updateDateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the attachments.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
private static void UpdateAttachments(DbContext context, Credential login, Credential loginObject)
|
||||
{
|
||||
var attachmentsToRemove = login.Attachments
|
||||
.Where(existingAttachment => !loginObject.Attachments.Any(a => a.Id == existingAttachment.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var attachmentToRemove in attachmentsToRemove)
|
||||
{
|
||||
login.Attachments.Remove(attachmentToRemove);
|
||||
context.Entry(attachmentToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
foreach (var attachment in loginObject.Attachments)
|
||||
{
|
||||
if (attachment.Id != Guid.Empty)
|
||||
{
|
||||
var existingAttachment = login.Attachments.FirstOrDefault(a => a.Id == attachment.Id);
|
||||
if (existingAttachment != null)
|
||||
{
|
||||
context.Entry(existingAttachment).CurrentValues.SetValues(attachment);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
login.Attachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the TOTP codes.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="login">The login object to update.</param>
|
||||
/// <param name="loginObject">The login object to update from.</param>
|
||||
private static void UpdateTotpCodes(DbContext context, Credential login, Credential loginObject)
|
||||
{
|
||||
var totpCodesToRemove = login.TotpCodes
|
||||
.Where(existingTotp => !loginObject.TotpCodes.Any(t => t.Id == existingTotp.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var totpToRemove in totpCodesToRemove)
|
||||
{
|
||||
login.TotpCodes.Remove(totpToRemove);
|
||||
context.Entry(totpToRemove).State = EntityState.Deleted;
|
||||
}
|
||||
|
||||
foreach (var totpCode in loginObject.TotpCodes)
|
||||
{
|
||||
if (totpCode.Id != Guid.Empty)
|
||||
{
|
||||
var existingTotpCode = login.TotpCodes.FirstOrDefault(t => t.Id == totpCode.Id);
|
||||
if (existingTotpCode != null)
|
||||
{
|
||||
context.Entry(existingTotpCode).CurrentValues.SetValues(totpCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
login.TotpCodes.Add(totpCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract favicon from service URL if available in object. If successful the passed object itself will be updated with the bytes.
|
||||
/// </summary>
|
||||
/// <param name="credentialObject">The Credential object to extract the favicon for.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ExtractFaviconAsync(Credential credentialObject)
|
||||
{
|
||||
// Try to extract favicon from service URL
|
||||
var url = credentialObject.Service.Url;
|
||||
if (url != null && !string.IsNullOrEmpty(url))
|
||||
{
|
||||
// Request favicon from service URL via WebApi
|
||||
try
|
||||
{
|
||||
var apiReturn =
|
||||
await httpClient.GetFromJsonAsync<FaviconExtractModel>($"v1/Favicon/Extract?url={url}");
|
||||
if (apiReturn?.Image is not null)
|
||||
{
|
||||
credentialObject.Service.Logo = apiReturn.Image;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore favicon extraction errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective identity generator language to use.
|
||||
/// If user has explicitly set a language preference, use that.
|
||||
/// Otherwise, intelligently match the UI language to an available identity generator language.
|
||||
/// Falls back to "en" if no match is found.
|
||||
/// </summary>
|
||||
/// <returns>The identity generator language code to use.</returns>
|
||||
private async Task<string> GetEffectiveIdentityLanguageAsync()
|
||||
{
|
||||
var explicitLanguage = dbService.Settings.DefaultIdentityLanguage;
|
||||
|
||||
// If user has explicitly set a language preference, use it
|
||||
if (!string.IsNullOrWhiteSpace(explicitLanguage))
|
||||
{
|
||||
return explicitLanguage;
|
||||
}
|
||||
|
||||
// Otherwise, try to match UI language to an identity generator language
|
||||
var uiLanguage = dbService.Settings.AppLanguage;
|
||||
var mappedLanguage = await jsInteropService.MapUiLanguageToIdentityLanguageAsync(uiLanguage);
|
||||
|
||||
// Return the mapped language, or fall back to "en" if no match found
|
||||
return mappedLanguage ?? "en";
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace AliasVault.Client.Services.Database;
|
||||
using System.Data;
|
||||
using System.Net.Http.Json;
|
||||
using AliasClientDb;
|
||||
using AliasClientDb.Models;
|
||||
using AliasVault.Client.Services;
|
||||
using AliasVault.Client.Services.Auth;
|
||||
using AliasVault.Client.Services.JsInterop.Models;
|
||||
@@ -477,7 +478,7 @@ public sealed class DbService : IDisposable
|
||||
var username = _authService.GetUsername();
|
||||
var databaseVersion = await GetCurrentDatabaseVersionAsync();
|
||||
var encryptionKey = await GetOrCreateEncryptionKeyAsync();
|
||||
var credentialsCount = await _dbContext.Credentials.Where(x => !x.IsDeleted).CountAsync();
|
||||
var credentialsCount = await _dbContext.Items.Where(x => !x.IsDeleted && x.DeletedAt == null).CountAsync();
|
||||
var emailAddresses = await GetEmailClaimListAsync();
|
||||
var currentDateTime = DateTime.UtcNow;
|
||||
return new Vault
|
||||
@@ -525,17 +526,19 @@ public sealed class DbService : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of private email addresses that are used in aliases by this vault.
|
||||
/// Get a list of private email addresses that are used in items by this vault.
|
||||
/// </summary>
|
||||
/// <returns>List of email addresses.</returns>
|
||||
public async Task<List<string>> GetEmailClaimListAsync()
|
||||
{
|
||||
// Send list of email addresses that are used in aliases by this vault, so they can be
|
||||
// Send list of email addresses that are used in items by this vault, so they can be
|
||||
// claimed on the server.
|
||||
var emailAddresses = await _dbContext.Aliases
|
||||
.Where(a => a.Email != null)
|
||||
.Where(a => !a.IsDeleted)
|
||||
.Select(a => a.Email)
|
||||
var emailAddresses = await _dbContext.FieldValues
|
||||
.Where(fv => fv.FieldKey == FieldKey.LoginEmail)
|
||||
.Where(fv => fv.Value != null)
|
||||
.Where(fv => !fv.IsDeleted)
|
||||
.Where(fv => !fv.Item.IsDeleted && fv.Item.DeletedAt == null)
|
||||
.Select(fv => fv.Value)
|
||||
.Distinct()
|
||||
.Select(email => email!)
|
||||
.ToListAsync();
|
||||
@@ -856,9 +859,14 @@ public sealed class DbService : IDisposable
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-7);
|
||||
var deleteCount = 0;
|
||||
|
||||
// Hard delete soft-deleted Credentials older than 7 days
|
||||
deleteCount += await _dbContext.Credentials
|
||||
.Where(c => c.IsDeleted && c.UpdatedAt <= cutoffDate)
|
||||
// Hard delete soft-deleted Items older than 7 days
|
||||
deleteCount += await _dbContext.Items
|
||||
.Where(i => i.IsDeleted && i.UpdatedAt <= cutoffDate)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
// Hard delete soft-deleted FieldValues older than 7 days
|
||||
deleteCount += await _dbContext.FieldValues
|
||||
.Where(fv => fv.IsDeleted && fv.UpdatedAt <= cutoffDate)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
// Hard delete soft-deleted Passkeys older than 7 days
|
||||
@@ -871,6 +879,11 @@ public sealed class DbService : IDisposable
|
||||
.Where(a => a.IsDeleted && a.UpdatedAt <= cutoffDate)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
// Hard delete soft-deleted TotpCodes older than 7 days
|
||||
deleteCount += await _dbContext.TotpCodes
|
||||
.Where(t => t.IsDeleted && t.UpdatedAt <= cutoffDate)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
if (deleteCount > 0)
|
||||
{
|
||||
var success = await SaveDatabaseAsync();
|
||||
|
||||
781
apps/server/AliasVault.Client/Services/ItemService.cs
Normal file
781
apps/server/AliasVault.Client/Services/ItemService.cs
Normal file
@@ -0,0 +1,781 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ItemService.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.Client.Services;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AliasClientDb;
|
||||
using AliasClientDb.Models;
|
||||
using AliasVault.Client.Main.Models;
|
||||
using AliasVault.Client.Utilities;
|
||||
using AliasVault.Shared.Models.WebApi.Favicon;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
/// <summary>
|
||||
/// Service class for Item operations.
|
||||
/// </summary>
|
||||
public sealed class ItemService(HttpClient httpClient, DbService dbService, Config config, JsInteropService jsInteropService)
|
||||
{
|
||||
/// <summary>
|
||||
/// The default service URL used as placeholder in forms. When this value is set, the URL field is considered empty
|
||||
/// and a null value is stored in the database.
|
||||
/// </summary>
|
||||
public const string DefaultServiceUrl = "https://";
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random password for an item using the specified settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">PasswordSettings model.</param>
|
||||
/// <returns>Random password.</returns>
|
||||
public async Task<string> GenerateRandomPasswordAsync(PasswordSettings settings)
|
||||
{
|
||||
// Sanity check: if all settings are false, then default to use lowercase letters only.
|
||||
if (!settings.UseLowercase && !settings.UseUppercase && !settings.UseNumbers && !settings.UseSpecialChars && !settings.UseNonAmbiguousChars)
|
||||
{
|
||||
settings.UseLowercase = true;
|
||||
}
|
||||
|
||||
return await jsInteropService.GenerateRandomPasswordAsync(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random identity for an item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to update with random identity.</param>
|
||||
/// <returns>Task with the updated item.</returns>
|
||||
public async Task<Item> GenerateRandomIdentityAsync(Item item)
|
||||
{
|
||||
const int MaxAttempts = 5;
|
||||
var attempts = 0;
|
||||
bool isEmailTaken;
|
||||
|
||||
do
|
||||
{
|
||||
// Convert age range to birthdate options using shared JS utility
|
||||
var birthdateOptions = await jsInteropService.ConvertAgeRangeToBirthdateOptionsAsync(dbService.Settings.DefaultIdentityAgeRange);
|
||||
|
||||
// Get the effective identity language (smart default based on UI language if no explicit override is set)
|
||||
var identityLanguage = await GetEffectiveIdentityLanguageAsync();
|
||||
|
||||
// Generate a random identity using the TypeScript library
|
||||
var identity = await jsInteropService.GenerateRandomIdentityAsync(identityLanguage, dbService.Settings.DefaultIdentityGender, birthdateOptions);
|
||||
|
||||
// Set username field
|
||||
SetFieldValue(item, FieldKey.LoginUsername, identity.NickName);
|
||||
|
||||
// Set alias fields
|
||||
SetFieldValue(item, FieldKey.AliasFirstName, identity.FirstName);
|
||||
SetFieldValue(item, FieldKey.AliasLastName, identity.LastName);
|
||||
SetFieldValue(item, FieldKey.AliasGender, identity.Gender);
|
||||
|
||||
if (!string.IsNullOrEmpty(identity.BirthDate))
|
||||
{
|
||||
SetFieldValue(item, FieldKey.AliasBirthdate, identity.BirthDate);
|
||||
}
|
||||
|
||||
// Set the email
|
||||
var emailDomain = GetDefaultEmailDomain();
|
||||
var email = $"{identity.EmailPrefix}@{emailDomain}";
|
||||
SetFieldValue(item, FieldKey.LoginEmail, email);
|
||||
|
||||
// Check if email is already taken
|
||||
try
|
||||
{
|
||||
var response = await httpClient.PostAsync($"v1/Identity/CheckEmail/{email}", null);
|
||||
var result = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
|
||||
isEmailTaken = result?["isTaken"] ?? false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If the API call fails, assume email is not taken to allow operation to continue
|
||||
isEmailTaken = false;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
while (isEmailTaken && attempts < MaxAttempts);
|
||||
|
||||
// Generate password using the TypeScript library
|
||||
var passwordSettings = dbService.Settings.PasswordSettings;
|
||||
var password = await jsInteropService.GenerateRandomPasswordAsync(passwordSettings);
|
||||
SetFieldValue(item, FieldKey.LoginPassword, password);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default email domain based on settings and available domains.
|
||||
/// </summary>
|
||||
/// <returns>Default email domain.</returns>
|
||||
public string GetDefaultEmailDomain()
|
||||
{
|
||||
var defaultDomain = dbService.Settings.DefaultEmailDomain;
|
||||
|
||||
// Function to check if a domain is valid (not disabled, not hidden, and exists in domain lists)
|
||||
bool IsValidDomain(string domain) =>
|
||||
!string.IsNullOrEmpty(domain) &&
|
||||
domain != "DISABLED.TLD" &&
|
||||
!config.HiddenPrivateEmailDomains.Contains(domain) &&
|
||||
(config.PublicEmailDomains.Contains(domain) || config.PrivateEmailDomains.Contains(domain));
|
||||
|
||||
// Get the first valid domain from private or public domains (excluding hidden ones)
|
||||
string GetFirstValidDomain() =>
|
||||
config.PrivateEmailDomains.Find(IsValidDomain) ??
|
||||
config.PublicEmailDomains.FirstOrDefault() ??
|
||||
"example.com";
|
||||
|
||||
// Use the default domain if it's valid (not hidden), otherwise get the first valid domain
|
||||
string domainToUse = IsValidDomain(defaultDomain) ? defaultDomain : GetFirstValidDomain();
|
||||
|
||||
return domainToUse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert new item into database.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to insert.</param>
|
||||
/// <param name="saveToDb">Whether to commit changes to database. Defaults to true.</param>
|
||||
/// <param name="extractFavicon">Whether to extract the favicon from the service URL. Defaults to true.</param>
|
||||
/// <returns>Guid of inserted entry.</returns>
|
||||
public async Task<Guid> InsertEntryAsync(Item item, bool saveToDb = true, bool extractFavicon = true)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Try to extract favicon from service URL
|
||||
if (extractFavicon)
|
||||
{
|
||||
await ExtractFaviconAsync(item);
|
||||
}
|
||||
|
||||
// Clean up email if it starts with @ (placeholder not filled)
|
||||
var email = GetFieldValue(item, FieldKey.LoginEmail);
|
||||
if (email != null && email.StartsWith('@'))
|
||||
{
|
||||
RemoveFieldValue(item, FieldKey.LoginEmail);
|
||||
}
|
||||
|
||||
// If the URL equals the placeholder, remove it
|
||||
var url = GetFieldValue(item, FieldKey.LoginUrl);
|
||||
if (url == DefaultServiceUrl)
|
||||
{
|
||||
RemoveFieldValue(item, FieldKey.LoginUrl);
|
||||
}
|
||||
|
||||
var currentDateTime = DateTime.UtcNow;
|
||||
item.Id = Guid.NewGuid();
|
||||
item.CreatedAt = currentDateTime;
|
||||
item.UpdatedAt = currentDateTime;
|
||||
|
||||
// Set timestamps on all field values
|
||||
foreach (var fv in item.FieldValues)
|
||||
{
|
||||
fv.Id = Guid.NewGuid();
|
||||
fv.ItemId = item.Id;
|
||||
fv.CreatedAt = currentDateTime;
|
||||
fv.UpdatedAt = currentDateTime;
|
||||
}
|
||||
|
||||
// Set timestamps on attachments
|
||||
foreach (var attachment in item.Attachments)
|
||||
{
|
||||
attachment.ItemId = item.Id;
|
||||
attachment.CreatedAt = currentDateTime;
|
||||
attachment.UpdatedAt = currentDateTime;
|
||||
}
|
||||
|
||||
// Set timestamps on TOTP codes
|
||||
foreach (var totpCode in item.TotpCodes)
|
||||
{
|
||||
totpCode.ItemId = item.Id;
|
||||
totpCode.CreatedAt = currentDateTime;
|
||||
totpCode.UpdatedAt = currentDateTime;
|
||||
}
|
||||
|
||||
context.Items.Add(item);
|
||||
|
||||
// Save the database to the server if saveToDb is true.
|
||||
if (saveToDb && !await dbService.SaveDatabaseAsync())
|
||||
{
|
||||
// If saving database to server failed, return empty guid to indicate error.
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return item.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update an existing item in database.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to update.</param>
|
||||
/// <returns>Guid of updated entry.</returns>
|
||||
public async Task<Guid> UpdateEntryAsync(Item item)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Try to extract favicon from service URL
|
||||
await ExtractFaviconAsync(item);
|
||||
|
||||
// Get the existing entry.
|
||||
var existingItem = await LoadEntryAsync(item.Id);
|
||||
if (existingItem is null)
|
||||
{
|
||||
throw new InvalidOperationException("Item not found.");
|
||||
}
|
||||
|
||||
// Clean up email if it starts with @ (placeholder not filled)
|
||||
var email = GetFieldValue(item, FieldKey.LoginEmail);
|
||||
if (email != null && email.StartsWith('@'))
|
||||
{
|
||||
RemoveFieldValue(item, FieldKey.LoginEmail);
|
||||
}
|
||||
|
||||
// If the URL equals the placeholder, remove it
|
||||
var url = GetFieldValue(item, FieldKey.LoginUrl);
|
||||
if (url == DefaultServiceUrl)
|
||||
{
|
||||
RemoveFieldValue(item, FieldKey.LoginUrl);
|
||||
}
|
||||
|
||||
var updateDateTime = DateTime.UtcNow;
|
||||
|
||||
// Update basic item info
|
||||
existingItem.Name = item.Name;
|
||||
existingItem.ItemType = item.ItemType;
|
||||
existingItem.LogoId = item.LogoId;
|
||||
existingItem.UpdatedAt = updateDateTime;
|
||||
|
||||
// Update field values
|
||||
UpdateFieldValues(context, existingItem, item, updateDateTime);
|
||||
|
||||
// Update attachments
|
||||
UpdateAttachments(context, existingItem, item, updateDateTime);
|
||||
|
||||
// Update TOTP codes
|
||||
UpdateTotpCodes(context, existingItem, item, updateDateTime);
|
||||
|
||||
if (!await dbService.SaveDatabaseAsync())
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return existingItem.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load existing item from database.
|
||||
/// </summary>
|
||||
/// <param name="itemId">Id of item to load.</param>
|
||||
/// <returns>Item object or null if not found.</returns>
|
||||
public async Task<Item?> LoadEntryAsync(Guid itemId)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var item = await context.Items
|
||||
.Include(x => x.FieldValues.Where(fv => !fv.IsDeleted))
|
||||
.Include(x => x.Logo)
|
||||
.Include(x => x.Attachments.Where(a => !a.IsDeleted))
|
||||
.Include(x => x.TotpCodes.Where(t => !t.IsDeleted))
|
||||
.Include(x => x.Passkeys.Where(p => !p.IsDeleted))
|
||||
.AsSplitQuery()
|
||||
.Where(x => x.Id == itemId)
|
||||
.Where(x => !x.IsDeleted)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all items from database.
|
||||
/// </summary>
|
||||
/// <returns>List of all items.</returns>
|
||||
public async Task<List<Item>> LoadAllAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var items = await context.Items
|
||||
.Include(x => x.FieldValues.Where(fv => !fv.IsDeleted))
|
||||
.Include(x => x.Logo)
|
||||
.Include(x => x.Attachments.Where(a => !a.IsDeleted))
|
||||
.Include(x => x.TotpCodes.Where(t => !t.IsDeleted))
|
||||
.Include(x => x.Passkeys.Where(p => !p.IsDeleted))
|
||||
.AsSplitQuery()
|
||||
.Where(x => !x.IsDeleted)
|
||||
.Where(x => x.DeletedAt == null) // Exclude items in trash
|
||||
.ToListAsync();
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list with all item entries for display.
|
||||
/// </summary>
|
||||
/// <returns>List of ItemListEntry objects.</returns>
|
||||
public async Task<List<ItemListEntry>?> GetListAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Retrieve all items from client DB.
|
||||
var items = await context.Items
|
||||
.Include(x => x.FieldValues.Where(fv => !fv.IsDeleted))
|
||||
.Include(x => x.Logo)
|
||||
.Include(x => x.Passkeys.Where(p => !p.IsDeleted))
|
||||
.Include(x => x.Attachments.Where(a => !a.IsDeleted))
|
||||
.AsSplitQuery()
|
||||
.Where(x => !x.IsDeleted)
|
||||
.Where(x => x.DeletedAt == null) // Exclude items in trash
|
||||
.ToListAsync();
|
||||
|
||||
// Map to ItemListEntry with proper boolean logic
|
||||
return items.Select(x => new ItemListEntry
|
||||
{
|
||||
Id = x.Id,
|
||||
Logo = x.Logo?.FileData,
|
||||
Service = x.Name,
|
||||
Username = GetFieldValue(x, FieldKey.LoginUsername),
|
||||
Email = GetFieldValue(x, FieldKey.LoginEmail),
|
||||
CreatedAt = x.CreatedAt,
|
||||
HasPasskey = x.Passkeys != null && x.Passkeys.Any(),
|
||||
HasAlias = !string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.AliasFirstName)) ||
|
||||
!string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.AliasLastName)) ||
|
||||
!string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.AliasGender)) ||
|
||||
!string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.AliasBirthdate)),
|
||||
HasUsernameOrPassword = !string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.LoginUsername)) ||
|
||||
!string.IsNullOrWhiteSpace(GetFieldValue(x, FieldKey.LoginPassword)),
|
||||
HasAttachment = x.Attachments != null && x.Attachments.Any(),
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft deletes an existing item from database by moving it to trash.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of item to delete.</param>
|
||||
/// <returns>Bool which indicates if deletion and saving database was successful.</returns>
|
||||
public async Task<bool> TrashItemAsync(Guid id)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var item = await context.Items
|
||||
.Include(x => x.Passkeys)
|
||||
.Where(x => x.Id == id)
|
||||
.FirstAsync();
|
||||
|
||||
var deleteDateTime = DateTime.UtcNow;
|
||||
|
||||
// Move to trash (soft delete)
|
||||
item.DeletedAt = deleteDateTime;
|
||||
item.UpdatedAt = deleteDateTime;
|
||||
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes an item (sets IsDeleted = true).
|
||||
/// </summary>
|
||||
/// <param name="id">Id of item to permanently delete.</param>
|
||||
/// <returns>Bool which indicates if deletion was successful.</returns>
|
||||
public async Task<bool> PermanentlyDeleteItemAsync(Guid id)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
var item = await context.Items
|
||||
.Include(x => x.FieldValues)
|
||||
.Include(x => x.Passkeys)
|
||||
.Include(x => x.Attachments)
|
||||
.Include(x => x.TotpCodes)
|
||||
.Where(x => x.Id == id)
|
||||
.FirstAsync();
|
||||
|
||||
var deleteDateTime = DateTime.UtcNow;
|
||||
|
||||
// Mark item and all related entities as deleted
|
||||
item.IsDeleted = true;
|
||||
item.UpdatedAt = deleteDateTime;
|
||||
|
||||
foreach (var fv in item.FieldValues)
|
||||
{
|
||||
fv.IsDeleted = true;
|
||||
fv.UpdatedAt = deleteDateTime;
|
||||
}
|
||||
|
||||
foreach (var passkey in item.Passkeys)
|
||||
{
|
||||
passkey.IsDeleted = true;
|
||||
passkey.UpdatedAt = deleteDateTime;
|
||||
}
|
||||
|
||||
foreach (var attachment in item.Attachments)
|
||||
{
|
||||
attachment.IsDeleted = true;
|
||||
attachment.UpdatedAt = deleteDateTime;
|
||||
}
|
||||
|
||||
foreach (var totp in item.TotpCodes)
|
||||
{
|
||||
totp.IsDeleted = true;
|
||||
totp.UpdatedAt = deleteDateTime;
|
||||
}
|
||||
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hard delete all items from the database. This permanently removes all item records
|
||||
/// (including soft-deleted ones) from the database for a complete vault reset.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public async Task<bool> HardDeleteAllItemsAsync()
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Hard delete all related entities and items.
|
||||
context.Attachments.RemoveRange(context.Attachments);
|
||||
context.FieldValues.RemoveRange(context.FieldValues);
|
||||
context.TotpCodes.RemoveRange(context.TotpCodes);
|
||||
context.Passkeys.RemoveRange(context.Passkeys);
|
||||
context.Items.RemoveRange(context.Items);
|
||||
|
||||
// Save changes locally
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Save the database to server
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a passkey by marking it as deleted.
|
||||
/// </summary>
|
||||
/// <param name="passkeyId">The ID of the passkey to delete.</param>
|
||||
/// <returns>A value indicating whether the deletion was successful.</returns>
|
||||
public async Task<bool> DeletePasskeyAsync(Guid passkeyId)
|
||||
{
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
var passkey = await context.Passkeys.FirstOrDefaultAsync(p => p.Id == passkeyId);
|
||||
|
||||
if (passkey != null)
|
||||
{
|
||||
var deleteDateTime = DateTime.UtcNow;
|
||||
passkey.IsDeleted = true;
|
||||
passkey.UpdatedAt = deleteDateTime;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
// Save to server
|
||||
return await dbService.SaveDatabaseAsync();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a field value from an item by field key.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to get the field from.</param>
|
||||
/// <param name="fieldKey">The field key.</param>
|
||||
/// <returns>The field value or null.</returns>
|
||||
#pragma warning disable SA1204 // Static members should appear before non-static members
|
||||
public static string? GetFieldValue(Item item, string fieldKey)
|
||||
#pragma warning restore SA1204
|
||||
{
|
||||
return item.FieldValues
|
||||
.FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted)
|
||||
?.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all field values for a multi-value field key.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to get the fields from.</param>
|
||||
/// <param name="fieldKey">The field key.</param>
|
||||
/// <returns>List of field values.</returns>
|
||||
public static List<string> GetFieldValues(Item item, string fieldKey)
|
||||
{
|
||||
return item.FieldValues
|
||||
.Where(fv => fv.FieldKey == fieldKey && !fv.IsDeleted)
|
||||
.OrderBy(fv => fv.Weight)
|
||||
.Select(fv => fv.Value ?? string.Empty)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or updates a field value on an item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to update.</param>
|
||||
/// <param name="fieldKey">The field key.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
public static void SetFieldValue(Item item, string fieldKey, string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
RemoveFieldValue(item, fieldKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var existingField = item.FieldValues.FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted);
|
||||
if (existingField != null)
|
||||
{
|
||||
existingField.Value = value;
|
||||
existingField.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.FieldValues.Add(new FieldValue
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ItemId = item.Id,
|
||||
FieldKey = fieldKey,
|
||||
Value = value,
|
||||
Weight = 0,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a field value from an item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to update.</param>
|
||||
/// <param name="fieldKey">The field key to remove.</param>
|
||||
public static void RemoveFieldValue(Item item, string fieldKey)
|
||||
{
|
||||
var existingField = item.FieldValues.FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted);
|
||||
if (existingField != null)
|
||||
{
|
||||
existingField.IsDeleted = true;
|
||||
existingField.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the field values for an item.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="existingItem">The existing item in the database.</param>
|
||||
/// <param name="newItem">The new item with updated field values.</param>
|
||||
/// <param name="updateDateTime">The timestamp for updates.</param>
|
||||
private static void UpdateFieldValues(DbContext context, Item existingItem, Item newItem, DateTime updateDateTime)
|
||||
{
|
||||
// Get existing field values
|
||||
var existingFields = existingItem.FieldValues.Where(fv => !fv.IsDeleted).ToList();
|
||||
|
||||
// Track which fields we've processed
|
||||
var processedFieldKeys = new HashSet<string>();
|
||||
|
||||
// Process new field values
|
||||
foreach (var newField in newItem.FieldValues.Where(fv => !fv.IsDeleted))
|
||||
{
|
||||
var fieldKey = newField.FieldKey;
|
||||
if (fieldKey == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
processedFieldKeys.Add(fieldKey);
|
||||
|
||||
var existingField = existingFields.FirstOrDefault(fv => fv.FieldKey == fieldKey);
|
||||
if (existingField != null)
|
||||
{
|
||||
// Update existing field
|
||||
if (existingField.Value != newField.Value)
|
||||
{
|
||||
existingField.Value = newField.Value;
|
||||
existingField.UpdatedAt = updateDateTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new field
|
||||
existingItem.FieldValues.Add(new FieldValue
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ItemId = existingItem.Id,
|
||||
FieldKey = fieldKey,
|
||||
Value = newField.Value,
|
||||
Weight = newField.Weight,
|
||||
CreatedAt = updateDateTime,
|
||||
UpdatedAt = updateDateTime,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Soft-delete removed fields
|
||||
foreach (var existingField in existingFields)
|
||||
{
|
||||
if (existingField.FieldKey != null && !processedFieldKeys.Contains(existingField.FieldKey))
|
||||
{
|
||||
existingField.IsDeleted = true;
|
||||
existingField.UpdatedAt = updateDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the attachments for an item.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="existingItem">The existing item in the database.</param>
|
||||
/// <param name="newItem">The new item with updated attachments.</param>
|
||||
/// <param name="updateDateTime">The timestamp for updates.</param>
|
||||
private static void UpdateAttachments(DbContext context, Item existingItem, Item newItem, DateTime updateDateTime)
|
||||
{
|
||||
var attachmentsToRemove = existingItem.Attachments
|
||||
.Where(existingAttachment => !newItem.Attachments.Any(a => a.Id == existingAttachment.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var attachmentToRemove in attachmentsToRemove)
|
||||
{
|
||||
attachmentToRemove.IsDeleted = true;
|
||||
attachmentToRemove.UpdatedAt = updateDateTime;
|
||||
}
|
||||
|
||||
foreach (var attachment in newItem.Attachments)
|
||||
{
|
||||
if (attachment.Id != Guid.Empty)
|
||||
{
|
||||
var existingAttachment = existingItem.Attachments.FirstOrDefault(a => a.Id == attachment.Id);
|
||||
if (existingAttachment != null)
|
||||
{
|
||||
context.Entry(existingAttachment).CurrentValues.SetValues(attachment);
|
||||
existingAttachment.UpdatedAt = updateDateTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.Id = Guid.NewGuid();
|
||||
attachment.ItemId = existingItem.Id;
|
||||
attachment.CreatedAt = updateDateTime;
|
||||
attachment.UpdatedAt = updateDateTime;
|
||||
existingItem.Attachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the TOTP codes for an item.
|
||||
/// </summary>
|
||||
/// <param name="context">The database context.</param>
|
||||
/// <param name="existingItem">The existing item in the database.</param>
|
||||
/// <param name="newItem">The new item with updated TOTP codes.</param>
|
||||
/// <param name="updateDateTime">The timestamp for updates.</param>
|
||||
private static void UpdateTotpCodes(DbContext context, Item existingItem, Item newItem, DateTime updateDateTime)
|
||||
{
|
||||
var totpCodesToRemove = existingItem.TotpCodes
|
||||
.Where(existingTotp => !newItem.TotpCodes.Any(t => t.Id == existingTotp.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var totpToRemove in totpCodesToRemove)
|
||||
{
|
||||
totpToRemove.IsDeleted = true;
|
||||
totpToRemove.UpdatedAt = updateDateTime;
|
||||
}
|
||||
|
||||
foreach (var totpCode in newItem.TotpCodes)
|
||||
{
|
||||
if (totpCode.Id != Guid.Empty)
|
||||
{
|
||||
var existingTotpCode = existingItem.TotpCodes.FirstOrDefault(t => t.Id == totpCode.Id);
|
||||
if (existingTotpCode != null)
|
||||
{
|
||||
context.Entry(existingTotpCode).CurrentValues.SetValues(totpCode);
|
||||
existingTotpCode.UpdatedAt = updateDateTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
totpCode.Id = Guid.NewGuid();
|
||||
totpCode.ItemId = existingItem.Id;
|
||||
totpCode.CreatedAt = updateDateTime;
|
||||
totpCode.UpdatedAt = updateDateTime;
|
||||
existingItem.TotpCodes.Add(totpCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract favicon from service URL if available. If successful, links the item to the logo.
|
||||
/// </summary>
|
||||
/// <param name="item">The Item to extract the favicon for.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ExtractFaviconAsync(Item item)
|
||||
{
|
||||
// Try to extract favicon from service URL
|
||||
var url = GetFieldValue(item, FieldKey.LoginUrl);
|
||||
if (url != null && !string.IsNullOrEmpty(url) && url != DefaultServiceUrl)
|
||||
{
|
||||
// Request favicon from service URL via WebApi
|
||||
try
|
||||
{
|
||||
var apiReturn = await httpClient.GetFromJsonAsync<FaviconExtractModel>($"v1/Favicon/Extract?url={url}");
|
||||
if (apiReturn?.Image is not null)
|
||||
{
|
||||
// For now, we store the favicon directly on the item's logo
|
||||
// In the future, we should use the Logo deduplication table
|
||||
var context = await dbService.GetDbContextAsync();
|
||||
|
||||
// Try to find existing logo by source
|
||||
var domain = new Uri(url).Host;
|
||||
var existingLogo = await context.Logos.FirstOrDefaultAsync(l => l.Source == domain);
|
||||
|
||||
if (existingLogo != null)
|
||||
{
|
||||
item.LogoId = existingLogo.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new logo
|
||||
var newLogo = new Logo
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Source = domain,
|
||||
FileData = apiReturn.Image,
|
||||
MimeType = "image/png",
|
||||
FetchedAt = DateTime.UtcNow,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
};
|
||||
context.Logos.Add(newLogo);
|
||||
item.LogoId = newLogo.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore favicon extraction errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective identity generator language to use.
|
||||
/// If user has explicitly set a language preference, use that.
|
||||
/// Otherwise, intelligently match the UI language to an available identity generator language.
|
||||
/// Falls back to "en" if no match is found.
|
||||
/// </summary>
|
||||
/// <returns>The identity generator language code to use.</returns>
|
||||
private async Task<string> GetEffectiveIdentityLanguageAsync()
|
||||
{
|
||||
var explicitLanguage = dbService.Settings.DefaultIdentityLanguage;
|
||||
|
||||
// If user has explicitly set a language preference, use it
|
||||
if (!string.IsNullOrWhiteSpace(explicitLanguage))
|
||||
{
|
||||
return explicitLanguage;
|
||||
}
|
||||
|
||||
// Otherwise, try to match UI language to an identity generator language
|
||||
var uiLanguage = dbService.Settings.AppLanguage;
|
||||
var mappedLanguage = await jsInteropService.MapUiLanguageToIdentityLanguageAsync(uiLanguage);
|
||||
|
||||
// Return the mapped language, or fall back to "en" if no match found
|
||||
return mappedLanguage ?? "en";
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -882,7 +882,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -1011,7 +1015,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -1028,7 +1036,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1044,7 +1056,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1060,7 +1076,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1082,7 +1102,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1099,7 +1123,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1116,7 +1144,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1133,7 +1165,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1150,11 +1186,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2039,7 +2079,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2168,7 +2212,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2185,7 +2233,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2201,7 +2253,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2217,7 +2273,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2239,7 +2299,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2256,7 +2320,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2273,7 +2341,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2290,7 +2362,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2307,11 +2383,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -850,7 +850,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -979,7 +983,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -996,7 +1004,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1012,7 +1024,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1028,7 +1044,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1050,7 +1070,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1067,7 +1091,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1084,7 +1112,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1101,7 +1133,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1118,11 +1154,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2007,7 +2047,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2136,7 +2180,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2153,7 +2201,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2169,7 +2221,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2185,7 +2241,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2207,7 +2267,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2224,7 +2288,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2241,7 +2309,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2258,7 +2330,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2275,11 +2351,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -387,10 +387,15 @@ namespace AliasClientDb.Migrations
|
||||
// Extract normalized domain from URL (e.g., 'https://www.github.com/path' -> 'github.com')
|
||||
// This matches the browser extension's FaviconService.extractSourceFromUrl() logic
|
||||
// Uses LOWER() for case-insensitive www. removal
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -519,10 +524,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate login.url field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -537,10 +547,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate login.username field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -554,10 +569,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate login.notes field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -571,10 +591,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate login.password field (system field using FieldKey) - only most recent password
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -595,10 +620,15 @@ namespace AliasClientDb.Migrations
|
||||
|
||||
// Migrate login.email field (system field using FieldKey)
|
||||
// Email is now a login field, applicable to both Login and Alias types
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -613,10 +643,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate alias.first_name field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -631,10 +666,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate alias.last_name field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -649,10 +689,15 @@ namespace AliasClientDb.Migrations
|
||||
");
|
||||
|
||||
// Migrate alias.gender field (system field using FieldKey)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -669,10 +714,15 @@ namespace AliasClientDb.Migrations
|
||||
// Migrate alias.birthdate field (system field using FieldKey)
|
||||
// Exclude DateTime.MinValue (year 0001) which represents empty/unset dates
|
||||
// Strip time portion from old format (yyyy-MM-dd HH:mm:ss or yyyy-MM-ddTHH:mm:ss) to new format (yyyy-MM-dd)
|
||||
// Note: GUID format must be uppercase with dashes (8-4-4-4-12) to match C# Guid.Parse expectations
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
namespace AliasClientDb.Models;
|
||||
@@ -20,11 +20,6 @@ public static class FieldKey
|
||||
/// </summary>
|
||||
public const string LoginPassword = "login.password";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string LoginNotes = "login.notes";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Email
|
||||
/// </summary>
|
||||
@@ -35,11 +30,6 @@ public static class FieldKey
|
||||
/// </summary>
|
||||
public const string LoginUrl = "login.url";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string LoginRecoveryCodes = "login.recovery_codes";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
@@ -70,66 +60,6 @@ public static class FieldKey
|
||||
/// </summary>
|
||||
public const string CardPin = "card.pin";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityTitle = "identity.title";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityFirstName = "identity.first_name";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityMiddleName = "identity.middle_name";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityLastName = "identity.last_name";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Email
|
||||
/// </summary>
|
||||
public const string IdentityEmail = "identity.email";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityPhoneNumbers = "identity.phone_numbers";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityAddressLine1 = "identity.address_line1";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityAddressLine2 = "identity.address_line2";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityCity = "identity.city";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityState = "identity.state";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityPostalCode = "identity.postal_code";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
public const string IdentityCountry = "identity.country";
|
||||
|
||||
/// <summary>
|
||||
/// Type: Text
|
||||
/// </summary>
|
||||
@@ -844,7 +844,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -973,7 +977,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -990,7 +998,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1006,7 +1018,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1022,7 +1038,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1044,7 +1064,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1061,7 +1085,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1078,7 +1106,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1095,7 +1127,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1112,11 +1148,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -173,7 +173,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -302,7 +306,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -319,7 +327,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -335,7 +347,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -351,7 +367,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -373,7 +393,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -390,7 +414,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -407,7 +435,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -424,7 +456,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -441,11 +477,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="CredentialCsvService.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.ImportExport;
|
||||
|
||||
using AliasClientDb;
|
||||
using AliasVault.ImportExport.Models;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// Exports and imports Credential objects to and from CSV files.
|
||||
/// </summary>
|
||||
public static class CredentialCsvService
|
||||
{
|
||||
private const string CsvVersionIdentifier = "1.5.0";
|
||||
|
||||
/// <summary>
|
||||
/// Export list of credentials to CSV file.
|
||||
/// </summary>
|
||||
/// <param name="credentials">List of credentials to export.</param>
|
||||
/// <returns>CSV file as byte array.</returns>
|
||||
public static byte[] ExportCredentialsToCsv(List<Credential> credentials)
|
||||
{
|
||||
var records = new List<CredentialCsvRecord>();
|
||||
|
||||
foreach (var credential in credentials)
|
||||
{
|
||||
var record = new CredentialCsvRecord
|
||||
{
|
||||
Version = CsvVersionIdentifier,
|
||||
Username = credential.Username ?? string.Empty,
|
||||
Notes = credential.Notes ?? string.Empty,
|
||||
CreatedAt = credential.CreatedAt,
|
||||
UpdatedAt = credential.UpdatedAt,
|
||||
AliasGender = credential.Alias?.Gender ?? string.Empty,
|
||||
AliasFirstName = credential.Alias?.FirstName ?? string.Empty,
|
||||
AliasLastName = credential.Alias?.LastName ?? string.Empty,
|
||||
AliasNickName = credential.Alias?.NickName ?? string.Empty,
|
||||
AliasBirthDate = credential.Alias?.BirthDate,
|
||||
AliasEmail = credential.Alias?.Email ?? string.Empty,
|
||||
ServiceName = credential.Service?.Name ?? string.Empty,
|
||||
ServiceUrl = credential.Service?.Url ?? string.Empty,
|
||||
CurrentPassword = credential.Passwords.OrderByDescending(p => p.CreatedAt).FirstOrDefault()?.Value ?? string.Empty,
|
||||
TwoFactorSecret = credential.TotpCodes.FirstOrDefault()?.SecretKey ?? string.Empty
|
||||
};
|
||||
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var writer = new StreamWriter(memoryStream);
|
||||
using var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
csv.WriteRecords(records);
|
||||
writer.Flush();
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports Credential objects from a CSV file.
|
||||
/// </summary>
|
||||
/// <param name="fileContent">The content of the CSV file.</param>
|
||||
/// <returns>The imported list of ImportedCredential objects.</returns>
|
||||
public static async Task<List<ImportedCredential>> ImportCredentialsFromCsv(string fileContent)
|
||||
{
|
||||
using var reader = new StringReader(fileContent);
|
||||
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
var records = new List<CredentialCsvRecord>();
|
||||
await foreach (var record in csv.GetRecordsAsync<CredentialCsvRecord>())
|
||||
{
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
if (records.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No records found in the CSV file.");
|
||||
}
|
||||
|
||||
if (records[0].Version != CsvVersionIdentifier)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid CSV file version.");
|
||||
}
|
||||
|
||||
var credentials = new List<ImportedCredential>();
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
var credential = new ImportedCredential
|
||||
{
|
||||
ServiceName = record.ServiceName,
|
||||
ServiceUrl = record.ServiceUrl,
|
||||
Username = record.Username,
|
||||
Password = record.CurrentPassword,
|
||||
Email = record.AliasEmail,
|
||||
Notes = record.Notes,
|
||||
Alias = new ImportedAlias
|
||||
{
|
||||
Gender = record.AliasGender,
|
||||
FirstName = record.AliasFirstName,
|
||||
LastName = record.AliasLastName,
|
||||
NickName = record.AliasNickName,
|
||||
BirthDate = record.AliasBirthDate,
|
||||
CreatedAt = record.CreatedAt,
|
||||
UpdatedAt = record.UpdatedAt
|
||||
},
|
||||
TwoFactorSecret = record.TwoFactorSecret,
|
||||
CreatedAt = record.CreatedAt,
|
||||
UpdatedAt = record.UpdatedAt,
|
||||
};
|
||||
|
||||
credentials.Add(credential);
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV record for Credential objects.
|
||||
/// </summary>
|
||||
public class CredentialCsvRecord
|
||||
{
|
||||
public string Version { get; set; } = "1.5.0";
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Notes { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.MinValue;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.MinValue;
|
||||
public string AliasGender { get; set; } = string.Empty;
|
||||
public string AliasFirstName { get; set; } = string.Empty;
|
||||
public string AliasLastName { get; set; } = string.Empty;
|
||||
public string AliasNickName { get; set; } = string.Empty;
|
||||
public DateTime? AliasBirthDate { get; set; } = null;
|
||||
public string AliasEmail { get; set; } = string.Empty;
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
public string ServiceUrl { get; set; } = string.Empty;
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
public string TwoFactorSecret { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace AliasVault.ImportExport.Importers;
|
||||
|
||||
using AliasClientDb;
|
||||
using AliasClientDb.Models;
|
||||
using AliasVault.ImportExport.Models;
|
||||
using AliasVault.TotpGenerator;
|
||||
using CsvHelper;
|
||||
@@ -138,68 +139,111 @@ public static class BaseImporter
|
||||
return decoded;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a list of imported credentials to a list of AliasVault credentials.
|
||||
/// Converts a list of imported credentials to a list of AliasVault Items.
|
||||
/// </summary>
|
||||
/// <param name="importedCredentials">The list of imported credentials.</param>
|
||||
/// <returns>The list of AliasVault credentials.</returns>
|
||||
public static List<Credential> ConvertToCredential(List<ImportedCredential> importedCredentials)
|
||||
/// <returns>The list of AliasVault Items.</returns>
|
||||
public static List<Item> ConvertToItem(List<ImportedCredential> importedCredentials)
|
||||
{
|
||||
var credentials = new List<Credential>();
|
||||
var items = new List<Item>();
|
||||
|
||||
// Convert imported credentials to AliasVault relational DB format.
|
||||
// Convert imported credentials to AliasVault field-based Item format.
|
||||
foreach (var importedCredential in importedCredentials)
|
||||
{
|
||||
var credential = new Credential
|
||||
var currentDateTime = DateTime.UtcNow;
|
||||
var createdAt = importedCredential.CreatedAt ?? currentDateTime;
|
||||
var updatedAt = importedCredential.UpdatedAt ?? currentDateTime;
|
||||
|
||||
// Determine if this is an Alias item type (has alias identity data)
|
||||
var hasAliasData = importedCredential.Alias != null &&
|
||||
(!string.IsNullOrEmpty(importedCredential.Alias.FirstName) ||
|
||||
!string.IsNullOrEmpty(importedCredential.Alias.LastName) ||
|
||||
!string.IsNullOrEmpty(importedCredential.Alias.Gender) ||
|
||||
importedCredential.Alias.BirthDate.HasValue);
|
||||
|
||||
var item = new Item
|
||||
{
|
||||
Service = new Service { Name = importedCredential.ServiceName, Url = importedCredential.ServiceUrl },
|
||||
Username = importedCredential.Username,
|
||||
Passwords = [new() { Value = importedCredential.Password }],
|
||||
Notes = importedCredential.Notes,
|
||||
CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow,
|
||||
UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow,
|
||||
Alias = new Alias
|
||||
{
|
||||
FirstName = importedCredential.Alias?.FirstName,
|
||||
LastName = importedCredential.Alias?.LastName,
|
||||
Gender = importedCredential.Alias?.Gender,
|
||||
// TODO: birth date should be made nullable in client DB as it's not always available.
|
||||
BirthDate = importedCredential.Alias?.BirthDate ?? DateTime.MinValue,
|
||||
Email = importedCredential.Email,
|
||||
NickName = importedCredential.Alias?.NickName,
|
||||
CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow,
|
||||
UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow,
|
||||
}
|
||||
Id = Guid.NewGuid(),
|
||||
Name = importedCredential.ServiceName ?? string.Empty,
|
||||
ItemType = hasAliasData ? "Alias" : "Login",
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = updatedAt,
|
||||
};
|
||||
|
||||
// Add field values for non-empty fields
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.LoginUrl, importedCredential.ServiceUrl, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.LoginUsername, importedCredential.Username, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.LoginPassword, importedCredential.Password, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.LoginEmail, importedCredential.Email, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.NotesContent, importedCredential.Notes, createdAt, updatedAt);
|
||||
|
||||
// Add alias fields if present
|
||||
if (importedCredential.Alias != null)
|
||||
{
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.AliasFirstName, importedCredential.Alias.FirstName, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.AliasLastName, importedCredential.Alias.LastName, createdAt, updatedAt);
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.AliasGender, importedCredential.Alias.Gender, createdAt, updatedAt);
|
||||
|
||||
if (importedCredential.Alias.BirthDate.HasValue && importedCredential.Alias.BirthDate.Value != DateTime.MinValue)
|
||||
{
|
||||
AddFieldValueIfNotEmpty(item, FieldKey.AliasBirthdate, importedCredential.Alias.BirthDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), createdAt, updatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// Add TOTP codes if present
|
||||
if (!string.IsNullOrEmpty(importedCredential.TwoFactorSecret))
|
||||
{
|
||||
// Sanitize the secret key by converting from potential URI to secret key and name.
|
||||
try
|
||||
{
|
||||
var (secretKey, name) = TotpHelper.SanitizeSecretKey(importedCredential.TwoFactorSecret);
|
||||
|
||||
credential.TotpCodes = new List<TotpCode>
|
||||
item.TotpCodes.Add(new TotpCode
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = name ?? "Authenticator",
|
||||
SecretKey = secretKey,
|
||||
CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow,
|
||||
UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow,
|
||||
}
|
||||
};
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name ?? "Authenticator",
|
||||
SecretKey = secretKey,
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = updatedAt,
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 2FA extraction failed, log the error and continue with the next credential
|
||||
// 2FA extraction failed, log the error and continue with the next item
|
||||
// so the import doesn't fail due to failed 2FA extraction.
|
||||
Console.WriteLine($"Error importing TOTP code: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
credentials.Add(credential);
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return credentials;
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a field value to an item if the value is not empty.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add the field value to.</param>
|
||||
/// <param name="fieldKey">The field key.</param>
|
||||
/// <param name="value">The field value.</param>
|
||||
/// <param name="createdAt">The created timestamp.</param>
|
||||
/// <param name="updatedAt">The updated timestamp.</param>
|
||||
private static void AddFieldValueIfNotEmpty(Item item, string fieldKey, string? value, DateTime createdAt, DateTime updatedAt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item.FieldValues.Add(new FieldValue
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ItemId = item.Id,
|
||||
FieldKey = fieldKey,
|
||||
Value = value,
|
||||
Weight = 0,
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = updatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
240
apps/server/Utilities/AliasVault.ImportExport/ItemCsvService.cs
Normal file
240
apps/server/Utilities/AliasVault.ImportExport/ItemCsvService.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ItemCsvService.cs" company="aliasvault">
|
||||
// Copyright (c) aliasvault. All rights reserved.
|
||||
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.ImportExport;
|
||||
|
||||
using AliasClientDb;
|
||||
using AliasClientDb.Models;
|
||||
using AliasVault.ImportExport.Models;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// Exports and imports Item objects to and from CSV files.
|
||||
/// </summary>
|
||||
public static class ItemCsvService
|
||||
{
|
||||
private const string CsvVersionIdentifier = "1.7.0";
|
||||
|
||||
/// <summary>
|
||||
/// Export list of items to CSV file.
|
||||
/// </summary>
|
||||
/// <param name="items">List of items to export.</param>
|
||||
/// <returns>CSV file as byte array.</returns>
|
||||
public static byte[] ExportItemsToCsv(List<Item> items)
|
||||
{
|
||||
var records = new List<ItemCsvRecord>();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var record = new ItemCsvRecord
|
||||
{
|
||||
Version = CsvVersionIdentifier,
|
||||
ServiceName = item.Name ?? string.Empty,
|
||||
ServiceUrl = GetFieldValue(item, FieldKey.LoginUrl),
|
||||
Username = GetFieldValue(item, FieldKey.LoginUsername),
|
||||
CurrentPassword = GetFieldValue(item, FieldKey.LoginPassword),
|
||||
AliasEmail = GetFieldValue(item, FieldKey.LoginEmail),
|
||||
Notes = GetFieldValue(item, FieldKey.NotesContent),
|
||||
AliasGender = GetFieldValue(item, FieldKey.AliasGender),
|
||||
AliasFirstName = GetFieldValue(item, FieldKey.AliasFirstName),
|
||||
AliasLastName = GetFieldValue(item, FieldKey.AliasLastName),
|
||||
AliasNickName = string.Empty, // NickName is no longer stored as a separate field
|
||||
AliasBirthDate = ParseBirthDate(GetFieldValue(item, FieldKey.AliasBirthdate)),
|
||||
CreatedAt = item.CreatedAt,
|
||||
UpdatedAt = item.UpdatedAt,
|
||||
TwoFactorSecret = item.TotpCodes.FirstOrDefault(t => !t.IsDeleted)?.SecretKey ?? string.Empty,
|
||||
};
|
||||
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var writer = new StreamWriter(memoryStream);
|
||||
using var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
csv.WriteRecords(records);
|
||||
writer.Flush();
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports Item objects from a CSV file.
|
||||
/// </summary>
|
||||
/// <param name="fileContent">The content of the CSV file.</param>
|
||||
/// <returns>The imported list of ImportedCredential objects.</returns>
|
||||
public static async Task<List<ImportedCredential>> ImportItemsFromCsv(string fileContent)
|
||||
{
|
||||
using var reader = new StringReader(fileContent);
|
||||
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
var records = new List<ItemCsvRecord>();
|
||||
await foreach (var record in csv.GetRecordsAsync<ItemCsvRecord>())
|
||||
{
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
if (records.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No records found in the CSV file.");
|
||||
}
|
||||
|
||||
// Support both 1.5.0 (old format) and 1.7.0 (new format)
|
||||
var version = records[0].Version;
|
||||
if (version != CsvVersionIdentifier && version != "1.5.0")
|
||||
{
|
||||
throw new InvalidOperationException($"Unsupported CSV file version: {version}. Expected 1.5.0 or 1.7.0.");
|
||||
}
|
||||
|
||||
var credentials = new List<ImportedCredential>();
|
||||
|
||||
foreach (var record in records)
|
||||
{
|
||||
var credential = new ImportedCredential
|
||||
{
|
||||
ServiceName = record.ServiceName,
|
||||
ServiceUrl = record.ServiceUrl,
|
||||
Username = record.Username,
|
||||
Password = record.CurrentPassword,
|
||||
Email = record.AliasEmail,
|
||||
Notes = record.Notes,
|
||||
Alias = new ImportedAlias
|
||||
{
|
||||
Gender = record.AliasGender,
|
||||
FirstName = record.AliasFirstName,
|
||||
LastName = record.AliasLastName,
|
||||
NickName = record.AliasNickName,
|
||||
BirthDate = record.AliasBirthDate,
|
||||
CreatedAt = record.CreatedAt,
|
||||
UpdatedAt = record.UpdatedAt,
|
||||
},
|
||||
TwoFactorSecret = record.TwoFactorSecret,
|
||||
CreatedAt = record.CreatedAt,
|
||||
UpdatedAt = record.UpdatedAt,
|
||||
};
|
||||
|
||||
credentials.Add(credential);
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a field value from an item by field key.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to get the field value from.</param>
|
||||
/// <param name="fieldKey">The field key to look up.</param>
|
||||
/// <returns>The field value, or empty string if not found.</returns>
|
||||
private static string GetFieldValue(Item item, string fieldKey)
|
||||
{
|
||||
return item.FieldValues
|
||||
.FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted)
|
||||
?.Value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a birth date string to a DateTime.
|
||||
/// </summary>
|
||||
/// <param name="birthDateStr">The birth date string in yyyy-MM-dd format.</param>
|
||||
/// <returns>The parsed DateTime, or null if the string is empty or invalid.</returns>
|
||||
private static DateTime? ParseBirthDate(string birthDateStr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(birthDateStr))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(birthDateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
|
||||
{
|
||||
return date;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV record for Item objects.
|
||||
/// </summary>
|
||||
public class ItemCsvRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the CSV format version.
|
||||
/// </summary>
|
||||
public string Version { get; set; } = "1.7.0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the notes.
|
||||
/// </summary>
|
||||
public string Notes { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the created timestamp.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the updated timestamp.
|
||||
/// </summary>
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias gender.
|
||||
/// </summary>
|
||||
public string AliasGender { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias first name.
|
||||
/// </summary>
|
||||
public string AliasFirstName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias last name.
|
||||
/// </summary>
|
||||
public string AliasLastName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias nickname (legacy field, no longer used).
|
||||
/// </summary>
|
||||
public string AliasNickName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias birth date.
|
||||
/// </summary>
|
||||
public DateTime? AliasBirthDate { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias email.
|
||||
/// </summary>
|
||||
public string AliasEmail { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service name.
|
||||
/// </summary>
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service URL.
|
||||
/// </summary>
|
||||
public string ServiceUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current password.
|
||||
/// </summary>
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the two-factor secret.
|
||||
/// </summary>
|
||||
public string TwoFactorSecret { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ const path = require('path');
|
||||
// Paths
|
||||
const REPO_ROOT = path.join(__dirname, '../../..');
|
||||
const TS_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/FieldKey.ts');
|
||||
const CS_OUTPUT = path.join(REPO_ROOT, 'apps/server/AliasVault.Client/Main/Models/Vault/FieldKey.cs');
|
||||
const CS_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/FieldKey.cs');
|
||||
const SWIFT_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels/FieldKey.swift');
|
||||
const KOTLIN_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models/FieldKey.kt');
|
||||
|
||||
@@ -54,7 +54,7 @@ function parseTypeScriptFieldKeys(tsContent) {
|
||||
*/
|
||||
function generateCSharp(fieldKeys) {
|
||||
const header = `// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
namespace AliasClientDb.Models;
|
||||
@@ -87,7 +87,7 @@ public static class FieldKey
|
||||
*/
|
||||
function generateSwift(fieldKeys) {
|
||||
const header = `// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
import Foundation
|
||||
@@ -117,7 +117,7 @@ public struct FieldKey {`;
|
||||
*/
|
||||
function generateKotlin(fieldKeys) {
|
||||
const header = `// <auto-generated />
|
||||
// This file is auto-generated from shared/models/src/vault/FieldKey.ts
|
||||
// This file is auto-generated from core/models/src/vault/FieldKey.ts
|
||||
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
|
||||
|
||||
package net.aliasvault.app.vaultstore.models
|
||||
|
||||
@@ -851,7 +851,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -980,7 +984,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -997,7 +1005,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -1013,7 +1025,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -1029,7 +1045,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -1051,7 +1071,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -1068,7 +1092,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -1085,7 +1113,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -1102,7 +1134,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -1119,11 +1155,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
@@ -2012,7 +2052,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO Logos (Id, Source, FileData, MimeType, FetchedAt, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
-- Extract and normalize hostname: remove protocol, path, lowercase, and www. prefix
|
||||
REPLACE(
|
||||
LOWER(
|
||||
@@ -2141,7 +2185,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.url' AS FieldKey,
|
||||
@@ -2158,7 +2206,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.username' AS FieldKey,
|
||||
@@ -2174,7 +2226,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.notes' AS FieldKey,
|
||||
@@ -2190,7 +2246,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
p.CredentialId AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.password' AS FieldKey,
|
||||
@@ -2212,7 +2272,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'login.email' AS FieldKey,
|
||||
@@ -2229,7 +2293,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.first_name' AS FieldKey,
|
||||
@@ -2246,7 +2314,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.last_name' AS FieldKey,
|
||||
@@ -2263,7 +2335,11 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.gender' AS FieldKey,
|
||||
@@ -2280,11 +2356,15 @@ CREATE INDEX "IX_Tags_Name" ON "Tags" ("Name");
|
||||
|
||||
INSERT INTO FieldValues (Id, ItemId, FieldDefinitionId, FieldKey, Value, Weight, CreatedAt, UpdatedAt, IsDeleted)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))) AS Id,
|
||||
UPPER(SUBSTR(hex(randomblob(4)), 1, 8) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(2)), 1, 4) || '-' ||
|
||||
SUBSTR(hex(randomblob(6)), 1, 12)) AS Id,
|
||||
c.Id AS ItemId,
|
||||
NULL AS FieldDefinitionId,
|
||||
'alias.birthdate' AS FieldKey,
|
||||
a.BirthDate AS Value,
|
||||
SUBSTR(a.BirthDate, 1, 10) AS Value,
|
||||
0 AS Weight,
|
||||
a.UpdatedAt AS CreatedAt,
|
||||
a.UpdatedAt AS UpdatedAt,
|
||||
|
||||
Reference in New Issue
Block a user