Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions backend/migrations/20250717010813_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ CREATE TABLE IF NOT EXISTS credentials (
is_active BOOLEAN NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_deleted BOOLEAN NOT NULL DEFAULT 0,
deleted_at DATETIME DEFAULT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
);
Expand All @@ -101,7 +99,7 @@ CREATE INDEX idx_credentials_user_id ON credentials(user_id);
CREATE INDEX idx_credentials_account_id ON credentials(account_id);
CREATE INDEX idx_credentials_node_type ON credentials(node_type);

CREATE UNIQUE INDEX idx_credentials_user_unique ON credentials(user_id) WHERE is_deleted = 0;
CREATE UNIQUE INDEX idx_credentials_user_unique ON credentials(user_id);

CREATE TRIGGER credentials_updated_at
AFTER UPDATE ON credentials
Expand Down
2 changes: 1 addition & 1 deletion backend/src/auth/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub async fn revoke_node_credentials(
}
};

// Soft delete the credential
// Delete the credential
if let Err(_e) = credential_repo.delete_credential(&credential.id).await {
let error_response =
ApiResponse::<()>::error("Failed to revoke credentials", "database_error", None);
Expand Down
2 changes: 0 additions & 2 deletions backend/src/database/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,6 @@ pub struct Credential {
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_deleted: bool,
pub deleted_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
Expand Down
75 changes: 49 additions & 26 deletions backend/src/repositories/credential_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ impl<'a> CredentialRepository<'a> {
ca_cert as "ca_cert?",
is_active as "is_active!",
created_at as "created_at!: DateTime<Utc>",
updated_at as "updated_at!: DateTime<Utc>",
is_deleted as "is_deleted!",
deleted_at as "deleted_at?: DateTime<Utc>"
updated_at as "updated_at!: DateTime<Utc>"
"#,
credential.id,
credential.user_id,
Expand Down Expand Up @@ -89,16 +87,44 @@ impl<'a> CredentialRepository<'a> {
/// * `id` - Credential ID (UUID format)
///
/// # Returns
/// `Some(Credential)` if found and not deleted, `None` otherwise
///
/// # Security
/// `Some(Credential)` if found, `None` otherwise
pub async fn get_credential_by_id(&self, id: &str) -> Result<Option<Credential>> {
Comment thread
Camillarhi marked this conversation as resolved.
Outdated
let credential = sqlx::query_as!(
Credential,
r#"
SELECT
id as "id!",
user_id as "user_id!",
account_id as "account_id!",
node_id as "node_id!",
node_alias as "node_alias!",
macaroon as "macaroon!",
tls_cert as "tls_cert!",
address as "address!",
node_type as "node_type?",
client_cert as "client_cert?",
client_key as "client_key?",
ca_cert as "ca_cert?",
is_active as "is_active!",
created_at as "created_at!: DateTime<Utc>",
updated_at as "updated_at!: DateTime<Utc>"
FROM credentials WHERE id = ?
"#,
id
)
.fetch_optional(self.pool)
.await?;

Ok(credential)
}

/// Retrieves credentials associated with a specific user.
///
/// # Arguments
/// * `user_id` - User ID (UUID format)
///
/// # Returns
/// `Some(Credential)` if found and not deleted, `None` otherwise
/// `Some(Credential)` if found, `None` otherwise
pub async fn get_credential_by_user_id(&self, user_id: &str) -> Result<Option<Credential>> {
let credential = sqlx::query_as!(
Credential,
Expand All @@ -118,10 +144,8 @@ impl<'a> CredentialRepository<'a> {
ca_cert as "ca_cert?",
is_active as "is_active!",
created_at as "created_at!: DateTime<Utc>",
updated_at as "updated_at!: DateTime<Utc>",
is_deleted as "is_deleted!",
deleted_at as "deleted_at?: DateTime<Utc>"
FROM credentials WHERE user_id = ? AND is_deleted = 0
updated_at as "updated_at!: DateTime<Utc>"
FROM credentials WHERE user_id = ?
"#,
user_id
)
Expand All @@ -137,8 +161,11 @@ impl<'a> CredentialRepository<'a> {
/// * `account_id` - Account ID (UUID format)
///
/// # Returns
/// `Some(Credential)` if found and not deleted, `None` otherwise
pub async fn get_credential_by_account_id(&self, account_id: &str) -> Result<Option<Credential>> {
/// `Some(Credential)` if found, `None` otherwise
pub async fn get_credential_by_account_id(
&self,
account_id: &str,
) -> Result<Option<Credential>> {
let credential = sqlx::query_as!(
Credential,
r#"
Expand All @@ -157,10 +184,8 @@ impl<'a> CredentialRepository<'a> {
ca_cert as "ca_cert?",
is_active as "is_active!",
created_at as "created_at!: DateTime<Utc>",
updated_at as "updated_at!: DateTime<Utc>",
is_deleted as "is_deleted!",
deleted_at as "deleted_at?: DateTime<Utc>"
FROM credentials WHERE account_id = ? AND is_deleted = 0
updated_at as "updated_at!: DateTime<Utc>"
FROM credentials WHERE account_id = ?
"#,
account_id
)
Expand All @@ -170,24 +195,22 @@ impl<'a> CredentialRepository<'a> {
Ok(credential)
}

/// Marks a credential as deleted (soft deletion).
/// Deletes a credential permanently from the database.
///
/// # Arguments
/// * `id` - Credential ID to deactivate
/// * `id` - Credential ID to delete
///
/// # Effects
/// - Sets `is_deleted` flag to true
/// - Records deletion timestamp
/// - Credential remains in database but won't appear in normal queries
/// - Permanently removes credential from database
/// - Cannot be recovered after deletion
///
/// # Security
/// - Prevents credential from being used while preserving audit trail
/// - Ensures credential cannot be used after deletion
pub async fn delete_credential(&self, id: &str) -> Result<()> {
sqlx::query!(
r#"
UPDATE credentials
SET is_deleted = 1, deleted_at = CURRENT_TIMESTAMP
WHERE id = ? AND is_deleted = 0
DELETE FROM credentials
WHERE id = ?
"#,
id
)
Expand Down
6 changes: 0 additions & 6 deletions docs/build-nix.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,6 @@ Run database migrations:
sqlx migrate run --source backend/migrations
```

Generate offline SQLx data:

```bash
cargo sqlx prepare --workspace
```

#### Step 5: Build and Run

Using the provided Makefile (recommended):
Expand Down
11 changes: 0 additions & 11 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.