From 10b3b9f0af5767085b565c464a0d58144f7c06a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 16:41:51 +0000
Subject: [PATCH 1/5] Initial plan
From ab220e724fc1c63ffe86eee6dfe5be548a73129c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 16:49:30 +0000
Subject: [PATCH 2/5] Add CreatedAt and UpdatedAt properties to 1Password
VaultItem
Co-authored-by: detunized <245393+detunized@users.noreply.github.com>
---
CHANGELOG.md | 3 +++
src/OnePassword/Response.cs | 6 +++++
src/OnePassword/VaultItem.cs | 2 ++
src/PasswordManagerAccess.csproj | 2 +-
test/OnePassword/ClientTest.cs | 2 ++
test/OnePassword/ResponseTest.cs | 38 ++++++++++++++++++++++++++++++++
6 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b9735460..41b41c2de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 28.2.0
+ - 1Password: added `VaultItem.CreatedAt` and `VaultItem.UpdatedAt` properties
+
## 28.1.0
- 1Password: fixed incorrect item and vault IDs detection in Client.GetItem
- 1Password: added `NoItem.Inaccessible` for inaccessible vaults and items
diff --git a/src/OnePassword/Response.cs b/src/OnePassword/Response.cs
index 0b92ae505..e5d5f0677 100644
--- a/src/OnePassword/Response.cs
+++ b/src/OnePassword/Response.cs
@@ -313,6 +313,12 @@ internal class VaultItem
[JsonProperty("trashed", Required = Required.Always)]
public readonly string Deleted;
+ [JsonProperty("createdAt")]
+ public readonly string CreatedAt;
+
+ [JsonProperty("updatedAt")]
+ public readonly string UpdatedAt;
+
[JsonProperty("itemVersion", Required = Required.Always)]
public readonly int Version;
diff --git a/src/OnePassword/VaultItem.cs b/src/OnePassword/VaultItem.cs
index c0765227d..c41eea56b 100644
--- a/src/OnePassword/VaultItem.cs
+++ b/src/OnePassword/VaultItem.cs
@@ -17,6 +17,8 @@ public class VaultItem
public string Name => Overview.Title ?? "";
public string Description => Overview.AdditionalInfo ?? "";
public string Note => Details.Note ?? "";
+ public string CreatedAt => _itemInfo.CreatedAt ?? "";
+ public string UpdatedAt => _itemInfo.UpdatedAt ?? "";
public Field[] Fields => _fields ??= ParseFields();
diff --git a/src/PasswordManagerAccess.csproj b/src/PasswordManagerAccess.csproj
index b4010205e..95a7f2b5d 100644
--- a/src/PasswordManagerAccess.csproj
+++ b/src/PasswordManagerAccess.csproj
@@ -8,7 +8,7 @@
detunized.net
MIT
- 28.1.0
+ 28.2.0
diff --git a/test/OnePassword/ClientTest.cs b/test/OnePassword/ClientTest.cs
index 050b80de0..a1cb77c63 100644
--- a/test/OnePassword/ClientTest.cs
+++ b/test/OnePassword/ClientTest.cs
@@ -54,6 +54,8 @@ public void GetItem_returns_account()
// Assert
result.TryPickT0(out var account, out _).ShouldBeTrue();
account.Id.ShouldBe("wm3uxq4xsmb4mghxw6o3s7zrem");
+ account.CreatedAt.ShouldBe("2016-08-04T13:15:10Z");
+ account.UpdatedAt.ShouldBe("2016-08-04T13:16:07Z");
}
[Fact]
diff --git a/test/OnePassword/ResponseTest.cs b/test/OnePassword/ResponseTest.cs
index c7208f9d2..068533c6c 100644
--- a/test/OnePassword/ResponseTest.cs
+++ b/test/OnePassword/ResponseTest.cs
@@ -18,5 +18,43 @@ public void VaultItemDetails_parses_with_all_types_of_fields()
Assert.Equal(6, details.Sections[0].Fields.Length);
Assert.Equal(6, details.Sections[1].Fields.Length);
}
+
+ [Fact]
+ public void VaultItem_parses_createdAt_and_updatedAt()
+ {
+ var json = GetFixture("get-vault-item-response");
+ var response = JsonConvert.DeserializeObject(json);
+ Assert.Equal("2016-08-04T13:15:10Z", response.Item.CreatedAt);
+ Assert.Equal("2016-08-04T13:16:07Z", response.Item.UpdatedAt);
+ }
+
+ [Fact]
+ public void VaultItem_handles_missing_createdAt_and_updatedAt()
+ {
+ var json = @"{
+ ""uuid"": ""test-id"",
+ ""templateUuid"": ""001"",
+ ""trashed"": ""N"",
+ ""itemVersion"": 1,
+ ""encryptedBy"": ""test-key"",
+ ""encOverview"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ },
+ ""encDetails"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ }
+ }";
+ var item = JsonConvert.DeserializeObject(json);
+ Assert.Null(item.CreatedAt);
+ Assert.Null(item.UpdatedAt);
+ }
}
}
From d39fde71649a853e9421fea324c0015dadead2e6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 17:19:21 +0000
Subject: [PATCH 3/5] Change CreatedAt and UpdatedAt to DateTime? with safe
parsing
Co-authored-by: detunized <245393+detunized@users.noreply.github.com>
---
src/OnePassword/Response.cs | 41 ++++++++++++++++++++++++++++++--
src/OnePassword/VaultItem.cs | 5 ++--
test/OnePassword/ClientTest.cs | 4 ++--
test/OnePassword/ResponseTest.cs | 36 ++++++++++++++++++++++++++--
4 files changed, 78 insertions(+), 8 deletions(-)
diff --git a/src/OnePassword/Response.cs b/src/OnePassword/Response.cs
index e5d5f0677..60aaa83cf 100644
--- a/src/OnePassword/Response.cs
+++ b/src/OnePassword/Response.cs
@@ -313,11 +313,13 @@ internal class VaultItem
[JsonProperty("trashed", Required = Required.Always)]
public readonly string Deleted;
+ [JsonConverter(typeof(SafeDateTimeConverter))]
[JsonProperty("createdAt")]
- public readonly string CreatedAt;
+ public readonly DateTime? CreatedAt;
+ [JsonConverter(typeof(SafeDateTimeConverter))]
[JsonProperty("updatedAt")]
- public readonly string UpdatedAt;
+ public readonly DateTime? UpdatedAt;
[JsonProperty("itemVersion", Required = Required.Always)]
public readonly int Version;
@@ -498,4 +500,39 @@ internal class ServiceAccountToken
[JsonProperty("muk")]
public readonly AesKey MasterUnlockKey;
}
+
+ // Safely parse DateTime values without throwing exceptions on invalid input
+ internal class SafeDateTimeConverter : JsonConverter
+ {
+ public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Null)
+ return null;
+
+ if (reader.TokenType == JsonToken.Date)
+ {
+ // Newtonsoft.Json already parsed it as a DateTime
+ return reader.Value as DateTime?;
+ }
+
+ if (reader.TokenType == JsonToken.String)
+ {
+ var value = reader.Value as string;
+ if (string.IsNullOrWhiteSpace(value))
+ return null;
+
+ // Try parsing with standard formats
+ if (DateTime.TryParse(value, System.Globalization.CultureInfo.InvariantCulture,
+ System.Globalization.DateTimeStyles.RoundtripKind, out var result))
+ return result;
+
+ // Invalid date format - return null instead of throwing
+ return null;
+ }
+
+ return null;
+ }
+
+ public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer) => throw new NotImplementedException();
+ }
}
diff --git a/src/OnePassword/VaultItem.cs b/src/OnePassword/VaultItem.cs
index c41eea56b..2238b50b6 100644
--- a/src/OnePassword/VaultItem.cs
+++ b/src/OnePassword/VaultItem.cs
@@ -3,6 +3,7 @@
#nullable enable
+using System;
using System.Collections.Generic;
using R = PasswordManagerAccess.OnePassword.Response;
@@ -17,8 +18,8 @@ public class VaultItem
public string Name => Overview.Title ?? "";
public string Description => Overview.AdditionalInfo ?? "";
public string Note => Details.Note ?? "";
- public string CreatedAt => _itemInfo.CreatedAt ?? "";
- public string UpdatedAt => _itemInfo.UpdatedAt ?? "";
+ public DateTime? CreatedAt => _itemInfo.CreatedAt;
+ public DateTime? UpdatedAt => _itemInfo.UpdatedAt;
public Field[] Fields => _fields ??= ParseFields();
diff --git a/test/OnePassword/ClientTest.cs b/test/OnePassword/ClientTest.cs
index a1cb77c63..c57503f39 100644
--- a/test/OnePassword/ClientTest.cs
+++ b/test/OnePassword/ClientTest.cs
@@ -54,8 +54,8 @@ public void GetItem_returns_account()
// Assert
result.TryPickT0(out var account, out _).ShouldBeTrue();
account.Id.ShouldBe("wm3uxq4xsmb4mghxw6o3s7zrem");
- account.CreatedAt.ShouldBe("2016-08-04T13:15:10Z");
- account.UpdatedAt.ShouldBe("2016-08-04T13:16:07Z");
+ account.CreatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc));
+ account.UpdatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 16, 7, DateTimeKind.Utc));
}
[Fact]
diff --git a/test/OnePassword/ResponseTest.cs b/test/OnePassword/ResponseTest.cs
index 068533c6c..0603d5824 100644
--- a/test/OnePassword/ResponseTest.cs
+++ b/test/OnePassword/ResponseTest.cs
@@ -1,6 +1,7 @@
// Copyright (C) Dmitry Yakimenko (detunized@gmail.com).
// Licensed under the terms of the MIT license. See LICENCE for details.
+using System;
using Newtonsoft.Json;
using Xunit;
using R = PasswordManagerAccess.OnePassword.Response;
@@ -24,8 +25,8 @@ public void VaultItem_parses_createdAt_and_updatedAt()
{
var json = GetFixture("get-vault-item-response");
var response = JsonConvert.DeserializeObject(json);
- Assert.Equal("2016-08-04T13:15:10Z", response.Item.CreatedAt);
- Assert.Equal("2016-08-04T13:16:07Z", response.Item.UpdatedAt);
+ Assert.Equal(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc), response.Item.CreatedAt);
+ Assert.Equal(new DateTime(2016, 8, 4, 13, 16, 7, DateTimeKind.Utc), response.Item.UpdatedAt);
}
[Fact]
@@ -56,5 +57,36 @@ public void VaultItem_handles_missing_createdAt_and_updatedAt()
Assert.Null(item.CreatedAt);
Assert.Null(item.UpdatedAt);
}
+
+ [Fact]
+ public void VaultItem_handles_invalid_createdAt_and_updatedAt()
+ {
+ var json = @"{
+ ""uuid"": ""test-id"",
+ ""templateUuid"": ""001"",
+ ""trashed"": ""N"",
+ ""createdAt"": ""not-a-date"",
+ ""updatedAt"": ""also-not-a-date"",
+ ""itemVersion"": 1,
+ ""encryptedBy"": ""test-key"",
+ ""encOverview"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ },
+ ""encDetails"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ }
+ }";
+ var item = JsonConvert.DeserializeObject(json);
+ Assert.Null(item.CreatedAt);
+ Assert.Null(item.UpdatedAt);
+ }
}
}
From a704ffc12b8f87e15101bc2583590fd5bf3902ac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 17:37:15 +0000
Subject: [PATCH 4/5] Simplify date handling: use non-nullable DateTime with
default value, parse in VaultItem
Co-authored-by: detunized <245393+detunized@users.noreply.github.com>
---
src/OnePassword/Response.cs | 41 ++------------------------------
src/OnePassword/VaultItem.cs | 17 +++++++++++--
test/OnePassword/ResponseTest.cs | 37 ++++++++++++++++++++++++----
3 files changed, 50 insertions(+), 45 deletions(-)
diff --git a/src/OnePassword/Response.cs b/src/OnePassword/Response.cs
index 60aaa83cf..e5d5f0677 100644
--- a/src/OnePassword/Response.cs
+++ b/src/OnePassword/Response.cs
@@ -313,13 +313,11 @@ internal class VaultItem
[JsonProperty("trashed", Required = Required.Always)]
public readonly string Deleted;
- [JsonConverter(typeof(SafeDateTimeConverter))]
[JsonProperty("createdAt")]
- public readonly DateTime? CreatedAt;
+ public readonly string CreatedAt;
- [JsonConverter(typeof(SafeDateTimeConverter))]
[JsonProperty("updatedAt")]
- public readonly DateTime? UpdatedAt;
+ public readonly string UpdatedAt;
[JsonProperty("itemVersion", Required = Required.Always)]
public readonly int Version;
@@ -500,39 +498,4 @@ internal class ServiceAccountToken
[JsonProperty("muk")]
public readonly AesKey MasterUnlockKey;
}
-
- // Safely parse DateTime values without throwing exceptions on invalid input
- internal class SafeDateTimeConverter : JsonConverter
- {
- public override DateTime? ReadJson(JsonReader reader, Type objectType, DateTime? existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- if (reader.TokenType == JsonToken.Null)
- return null;
-
- if (reader.TokenType == JsonToken.Date)
- {
- // Newtonsoft.Json already parsed it as a DateTime
- return reader.Value as DateTime?;
- }
-
- if (reader.TokenType == JsonToken.String)
- {
- var value = reader.Value as string;
- if (string.IsNullOrWhiteSpace(value))
- return null;
-
- // Try parsing with standard formats
- if (DateTime.TryParse(value, System.Globalization.CultureInfo.InvariantCulture,
- System.Globalization.DateTimeStyles.RoundtripKind, out var result))
- return result;
-
- // Invalid date format - return null instead of throwing
- return null;
- }
-
- return null;
- }
-
- public override void WriteJson(JsonWriter writer, DateTime? value, JsonSerializer serializer) => throw new NotImplementedException();
- }
}
diff --git a/src/OnePassword/VaultItem.cs b/src/OnePassword/VaultItem.cs
index 2238b50b6..3def36664 100644
--- a/src/OnePassword/VaultItem.cs
+++ b/src/OnePassword/VaultItem.cs
@@ -18,8 +18,8 @@ public class VaultItem
public string Name => Overview.Title ?? "";
public string Description => Overview.AdditionalInfo ?? "";
public string Note => Details.Note ?? "";
- public DateTime? CreatedAt => _itemInfo.CreatedAt;
- public DateTime? UpdatedAt => _itemInfo.UpdatedAt;
+ public DateTime CreatedAt => ParseDateTime(_itemInfo.CreatedAt);
+ public DateTime UpdatedAt => ParseDateTime(_itemInfo.UpdatedAt);
public Field[] Fields => _fields ??= ParseFields();
@@ -75,6 +75,19 @@ internal string FindField(string name)
return "";
}
+ internal static DateTime ParseDateTime(string dateString)
+ {
+ if (string.IsNullOrWhiteSpace(dateString))
+ return default;
+
+ if (DateTime.TryParse(dateString, System.Globalization.CultureInfo.InvariantCulture,
+ System.Globalization.DateTimeStyles.RoundtripKind, out var result))
+ return result;
+
+ // Return default if parsing fails
+ return default;
+ }
+
//
// Private
//
diff --git a/test/OnePassword/ResponseTest.cs b/test/OnePassword/ResponseTest.cs
index 0603d5824..372dcf919 100644
--- a/test/OnePassword/ResponseTest.cs
+++ b/test/OnePassword/ResponseTest.cs
@@ -3,6 +3,7 @@
using System;
using Newtonsoft.Json;
+using PasswordManagerAccess.OnePassword;
using Xunit;
using R = PasswordManagerAccess.OnePassword.Response;
@@ -25,8 +26,8 @@ public void VaultItem_parses_createdAt_and_updatedAt()
{
var json = GetFixture("get-vault-item-response");
var response = JsonConvert.DeserializeObject(json);
- Assert.Equal(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc), response.Item.CreatedAt);
- Assert.Equal(new DateTime(2016, 8, 4, 13, 16, 7, DateTimeKind.Utc), response.Item.UpdatedAt);
+ Assert.Equal("2016-08-04T13:15:10Z", response.Item.CreatedAt);
+ Assert.Equal("2016-08-04T13:16:07Z", response.Item.UpdatedAt);
}
[Fact]
@@ -85,8 +86,36 @@ public void VaultItem_handles_invalid_createdAt_and_updatedAt()
}
}";
var item = JsonConvert.DeserializeObject(json);
- Assert.Null(item.CreatedAt);
- Assert.Null(item.UpdatedAt);
+ Assert.Equal("not-a-date", item.CreatedAt);
+ Assert.Equal("also-not-a-date", item.UpdatedAt);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_parses_valid_date()
+ {
+ var result = VaultItem.ParseDateTime("2016-08-04T13:15:10Z");
+ Assert.Equal(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_null()
+ {
+ var result = VaultItem.ParseDateTime(null);
+ Assert.Equal(default(DateTime), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_empty()
+ {
+ var result = VaultItem.ParseDateTime("");
+ Assert.Equal(default(DateTime), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_invalid()
+ {
+ var result = VaultItem.ParseDateTime("not-a-date");
+ Assert.Equal(default(DateTime), result);
}
}
}
From d57374722c55b8095ab03d7c592814824c7f0089 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 18:08:14 +0000
Subject: [PATCH 5/5] Add backing fields to cache parsed CreatedAt and
UpdatedAt dates
Co-authored-by: detunized <245393+detunized@users.noreply.github.com>
---
src/OnePassword/VaultItem.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/OnePassword/VaultItem.cs b/src/OnePassword/VaultItem.cs
index 3def36664..54904704c 100644
--- a/src/OnePassword/VaultItem.cs
+++ b/src/OnePassword/VaultItem.cs
@@ -18,8 +18,8 @@ public class VaultItem
public string Name => Overview.Title ?? "";
public string Description => Overview.AdditionalInfo ?? "";
public string Note => Details.Note ?? "";
- public DateTime CreatedAt => ParseDateTime(_itemInfo.CreatedAt);
- public DateTime UpdatedAt => ParseDateTime(_itemInfo.UpdatedAt);
+ public DateTime CreatedAt => _createdAt ??= ParseDateTime(_itemInfo.CreatedAt);
+ public DateTime UpdatedAt => _updatedAt ??= ParseDateTime(_itemInfo.UpdatedAt);
public Field[] Fields => _fields ??= ParseFields();
@@ -97,4 +97,6 @@ internal static DateTime ParseDateTime(string dateString)
private R.VaultItemOverview? _overview;
private R.VaultItemDetails? _details;
private Field[]? _fields;
+ private DateTime? _createdAt;
+ private DateTime? _updatedAt;
}