Implement core modifyTimestamp functionality with database migration and backend support

Co-authored-by: nitnelave <796633+nitnelave@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-26 20:41:32 +00:00
parent df49d827d0
commit a90695a6ce
20 changed files with 2365 additions and 5 deletions
@@ -268,10 +268,12 @@ impl SqlBackendHandler {
.display_name
.as_ref()
.map(|s| s.as_str().to_lowercase());
let now = chrono::Utc::now().naive_utc();
let update_group = model::groups::ActiveModel {
group_id: Set(request.group_id),
display_name: request.display_name.map(Set).unwrap_or_default(),
lowercase_display_name: lower_display_name.map(Set).unwrap_or_default(),
modified_date: Set(now),
..Default::default()
};
update_group.update(transaction).await?;
@@ -27,6 +27,8 @@ pub enum Users {
TotpSecret,
MfaType,
Uuid,
ModifiedDate,
PasswordModifiedDate,
}
#[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
@@ -37,6 +39,7 @@ pub(crate) enum Groups {
LowercaseDisplayName,
CreationDate,
Uuid,
ModifiedDate,
}
#[derive(DeriveIden, Clone, Copy)]
@@ -1112,6 +1115,142 @@ async fn migrate_to_v10(transaction: DatabaseTransaction) -> Result<DatabaseTran
Ok(transaction)
}
async fn migrate_to_v11(transaction: DatabaseTransaction) -> Result<DatabaseTransaction, DbErr> {
let builder = transaction.get_database_backend();
// Add modified_date to users table
transaction
.execute(
builder.build(
Table::alter()
.table(Users::Table)
.add_column(
ColumnDef::new(Users::ModifiedDate)
.date_time()
.not_null()
.default("CURRENT_TIMESTAMP"),
),
),
)
.await?;
// Add password_modified_date to users table
transaction
.execute(
builder.build(
Table::alter()
.table(Users::Table)
.add_column(
ColumnDef::new(Users::PasswordModifiedDate)
.date_time()
.not_null()
.default("CURRENT_TIMESTAMP"),
),
),
)
.await?;
// Add modified_date to groups table
transaction
.execute(
builder.build(
Table::alter()
.table(Groups::Table)
.add_column(
ColumnDef::new(Groups::ModifiedDate)
.date_time()
.not_null()
.default("CURRENT_TIMESTAMP"),
),
),
)
.await?;
// Initialize existing users with modified_date = creation_date
transaction
.execute(
builder.build(
Query::update()
.table(Users::Table)
.value(Users::ModifiedDate, Expr::col(Users::CreationDate))
.value(Users::PasswordModifiedDate, Expr::col(Users::CreationDate)),
),
)
.await?;
// Initialize existing groups with modified_date = creation_date
transaction
.execute(
builder.build(
Query::update()
.table(Groups::Table)
.value(Groups::ModifiedDate, Expr::col(Groups::CreationDate)),
),
)
.await?;
// Add the new timestamp attributes to the user attribute schema as hardcoded read-only attributes
transaction
.execute(
builder.build(
Query::insert()
.into_table(UserAttributeSchema::Table)
.columns([
UserAttributeSchema::UserAttributeSchemaName,
UserAttributeSchema::UserAttributeSchemaType,
UserAttributeSchema::UserAttributeSchemaIsList,
UserAttributeSchema::UserAttributeSchemaIsUserVisible,
UserAttributeSchema::UserAttributeSchemaIsUserEditable,
UserAttributeSchema::UserAttributeSchemaIsHardcoded,
])
.values_panic([
"modified_date".into(),
AttributeType::DateTime.into(),
false.into(),
true.into(),
false.into(),
true.into(),
])
.values_panic([
"password_modified_date".into(),
AttributeType::DateTime.into(),
false.into(),
true.into(),
false.into(),
true.into(),
]),
),
)
.await?;
// Add the new timestamp attribute to the group attribute schema as hardcoded read-only attribute
transaction
.execute(
builder.build(
Query::insert()
.into_table(GroupAttributeSchema::Table)
.columns([
GroupAttributeSchema::GroupAttributeSchemaName,
GroupAttributeSchema::GroupAttributeSchemaType,
GroupAttributeSchema::GroupAttributeSchemaIsList,
GroupAttributeSchema::GroupAttributeSchemaIsGroupVisible,
GroupAttributeSchema::GroupAttributeSchemaIsGroupEditable,
GroupAttributeSchema::GroupAttributeSchemaIsHardcoded,
])
.values_panic([
"modified_date".into(),
AttributeType::DateTime.into(),
false.into(),
true.into(),
false.into(),
true.into(),
]),
),
)
.await?;
Ok(transaction)
}
// This is needed to make an array of async functions.
macro_rules! to_sync {
($l:ident) => {
@@ -1142,6 +1281,7 @@ pub(crate) async fn migrate_from_version(
to_sync!(migrate_to_v8),
to_sync!(migrate_to_v9),
to_sync!(migrate_to_v10),
to_sync!(migrate_to_v11),
];
assert_eq!(migrations.len(), (LAST_SCHEMA_VERSION.0 - 1) as usize);
for migration in 2..=last_version.0 {
@@ -197,9 +197,11 @@ impl OpaqueHandler for SqlOpaqueHandler {
let password_file =
opaque::server::registration::get_password_file(request.registration_upload);
// Set the user password to the new password.
let now = chrono::Utc::now().naive_utc();
let user_update = model::users::ActiveModel {
user_id: ActiveValue::Set(username.clone()),
password_hash: ActiveValue::Set(Some(password_file.serialize())),
password_modified_date: ActiveValue::Set(now),
..Default::default()
};
user_update.update(&self.sql_pool).await?;
+1 -1
View File
@@ -9,7 +9,7 @@ pub type DbConnection = sea_orm::DatabaseConnection;
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, DeriveValueType)]
pub struct SchemaVersion(pub i16);
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(10);
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(11);
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)]
pub struct PrivateKeyHash(pub [u8; 32]);
@@ -190,11 +190,13 @@ impl SqlBackendHandler {
request: UpdateUserRequest,
) -> Result<()> {
let lower_email = request.email.as_ref().map(|s| s.as_str().to_lowercase());
let now = chrono::Utc::now().naive_utc();
let update_user = model::users::ActiveModel {
user_id: ActiveValue::Set(request.user_id.clone()),
email: request.email.map(ActiveValue::Set).unwrap_or_default(),
lowercase_email: lower_email.map(ActiveValue::Set).unwrap_or_default(),
display_name: to_value(&request.display_name),
modified_date: ActiveValue::Set(now),
..Default::default()
};
let mut update_user_attributes = Vec::new();