Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ PASSWORD_REQUIRE_NUMBERS=true
PASSWORD_REQUIRE_UPPERCASE=true
PASSWORD_HISTORY_COUNT=5
PASSWORD_EXPIRY_DAYS=90
PASSWORD_EXPIRY_WARNING_DAYS=7

# Authentication Security
JWT_BLACKLIST_ENABLED=true
Expand Down
12 changes: 6 additions & 6 deletions junit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
</testcase>
<testcase classname="PropertiesService helper methods should map property status correctly" name="PropertiesService helper methods should map property status correctly" time="0.019">
</testcase>
<testcase classname="PropertiesService helper methods should validate status transitions correctly" name="PropertiesService helper methods should validate status transitions correctly" time="0.007">
<testcase classname="PropertiesService helper methods should validate status transitions correctly" name="PropertiesService helper methods should validate status transitions correctly" time="0.003">
</testcase>
</testsuite>
<testsuite name="SensitiveEndpointRateLimitGuard" errors="0" failures="0" skipped="0" timestamp="2026-03-23T20:16:40" time="6.996" tests="12">
Expand Down Expand Up @@ -265,7 +265,7 @@
</testcase>
<testcase classname="Auth DTOs LoginDto (Combined) should pass with wallet address and signature" name="Auth DTOs LoginDto (Combined) should pass with wallet address and signature" time="0.002">
</testcase>
<testcase classname="Auth DTOs LoginDto (Combined) should fail with neither email nor wallet" name="Auth DTOs LoginDto (Combined) should fail with neither email nor wallet" time="0.002">
<testcase classname="Auth DTOs LoginDto (Combined) should fail with neither email nor wallet" name="Auth DTOs LoginDto (Combined) should fail with neither email nor wallet" time="0">
</testcase>
<testcase classname="Auth DTOs RefreshTokenDto should pass with valid JWT" name="Auth DTOs RefreshTokenDto should pass with valid JWT" time="0.004">
</testcase>
Expand Down Expand Up @@ -337,13 +337,13 @@
<testsuite name="Pagination Service - Performance Tests" errors="0" failures="0" skipped="0" timestamp="2026-03-23T20:17:03" time="0.775" tests="10">
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should efficiently calculate pagination for 1 million items" name="Pagination Service - Performance Tests Performance with Large Datasets should efficiently calculate pagination for 1 million items" time="0.004">
</testcase>
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should efficiently format response for large dataset" name="Pagination Service - Performance Tests Performance with Large Datasets should efficiently format response for large dataset" time="0.002">
<testcase classname="PaginationService calculatePagination should calculate correct offset for page 2" name="PaginationService calculatePagination should calculate correct offset for page 2" time="0.003">
</testcase>
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should handle rapid consecutive pagination queries" name="Pagination Service - Performance Tests Performance with Large Datasets should handle rapid consecutive pagination queries" time="0.003">
</testcase>
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should efficiently generate Prisma options for many queries" name="Pagination Service - Performance Tests Performance with Large Datasets should efficiently generate Prisma options for many queries" time="0.008">
</testcase>
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should calculate metadata efficiently for extreme pagination values" name="Pagination Service - Performance Tests Performance with Large Datasets should calculate metadata efficiently for extreme pagination values" time="0.001">
<testcase classname="PaginationService calculatePagination should enforce minimum limit" name="PaginationService calculatePagination should enforce minimum limit" time="0.014">
</testcase>
<testcase classname="Pagination Service - Performance Tests Performance with Large Datasets should memory-efficiently handle large format operations" name="Pagination Service - Performance Tests Performance with Large Datasets should memory-efficiently handle large format operations" time="0.003">
</testcase>
Expand Down Expand Up @@ -401,7 +401,7 @@
</testcase>
<testcase classname="Custom Validators IsEthereumAddress should pass with lowercase Ethereum address" name="Custom Validators IsEthereumAddress should pass with lowercase Ethereum address" time="0.001">
</testcase>
<testcase classname="Custom Validators IsEthereumAddress should pass with uppercase Ethereum address" name="Custom Validators IsEthereumAddress should pass with uppercase Ethereum address" time="0.001">
<testcase classname="Custom Validators IsEthereumAddress should pass with uppercase Ethereum address" name="Custom Validators IsEthereumAddress should pass with uppercase Ethereum address" time="0.002">
</testcase>
<testcase classname="Custom Validators IsEthereumAddress should fail without 0x prefix" name="Custom Validators IsEthereumAddress should fail without 0x prefix" time="0.004">
</testcase>
Expand All @@ -411,7 +411,7 @@
</testcase>
<testcase classname="Custom Validators IsEthereumAddress should fail with invalid characters" name="Custom Validators IsEthereumAddress should fail with invalid characters" time="0.001">
</testcase>
<testcase classname="Custom Validators IsEthereumAddress should fail with empty string" name="Custom Validators IsEthereumAddress should fail with empty string" time="0.001">
<testcase classname="Custom Validators IsEthereumAddress should fail with empty string" name="Custom Validators IsEthereumAddress should fail with empty string" time="0">
</testcase>
<testcase classname="Custom Validators IsStrongPassword should pass with strong password" name="Custom Validators IsStrongPassword should pass with strong password" time="0.004">
</testcase>
Expand Down
63 changes: 52 additions & 11 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ model User {
// Activity
activities UserActivity[]

// Password history for rotation policy
passwordHistory PasswordHistory[]

properties Property[]
receivedTransactions Transaction[] @relation("UserTransactions")
userRole Role? @relation(fields: [roleId], references: [id], onDelete: SetNull)
Expand Down Expand Up @@ -373,20 +376,58 @@ model Document {
}

model ApiKey {
id String @id @default(cuid())
name String
key String @unique
keyPrefix String @map("key_prefix")
scopes String[]
requestCount BigInt @default(0) @map("request_count")
lastUsedAt DateTime? @map("last_used_at")
isActive Boolean @default(true) @map("is_active")
rateLimit Int? @map("rate_limit")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
id String @id @default(cuid())
name String
key String @unique
keyPrefix String @map("key_prefix")
keyVersion Int @default(1) @map("key_version")
scopes String[]
requestCount BigInt @default(0) @map("request_count")
lastUsedAt DateTime? @map("last_used_at")
isActive Boolean @default(true) @map("is_active")
rateLimit Int? @map("rate_limit")
lastRotatedAt DateTime? @map("last_rotated_at")
rotationDueAt DateTime? @map("rotation_due_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

usageLogs ApiKeyUsageLog[]

@@index([keyPrefix])
@@index([isActive])
@@index([createdAt])
@@index([rotationDueAt])
@@map("api_keys")
}

model ApiKeyUsageLog {
id String @id @default(cuid())
apiKeyId String @map("api_key_id")
endpoint String
method String
statusCode Int @map("status_code")
responseTime Int @map("response_time")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")

apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)

@@index([apiKeyId])
@@index([endpoint])
@@index([createdAt])
@@map("api_key_usage_logs")
}

model PasswordHistory {
id String @id @default(cuid())
userId String @map("user_id")
passwordHash String @map("password_hash")
createdAt DateTime @default(now()) @map("created_at")

user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId])
@@index([createdAt])
@@map("password_history")
}
Loading
Loading