Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
af96798
docs: create unified authentication system specification
phgoncalves Nov 6, 2025
a3a448f
docs: complete authentication planning and task generation
phgoncalves Nov 6, 2025
4a93f6d
feat: setup authentication module infrastructure (Phase 1)
phgoncalves Nov 6, 2025
d4e772c
feat: implement authentication foundation with JWT and entities
phgoncalves Nov 6, 2025
ec10818
test: add unit tests for authentication service (TDD Red phase)
phgoncalves Nov 6, 2025
8e99991
test: add integration tests for authentication endpoints (TDD Red phase)
phgoncalves Nov 6, 2025
81a18cc
feat: implement User Story 1 - JWT authentication (login/refresh/logout)
phgoncalves Nov 6, 2025
216751d
docs: synchronize tasks.md with actual implementation progress
phgoncalves Nov 6, 2025
47c3fe4
feat: implement rate limiting for authentication endpoints (T009, T04…
phgoncalves Nov 6, 2025
7254b26
feat: add structured logging to authentication operations (T047)
phgoncalves Nov 6, 2025
3bd539d
feat: implement API key management service and tests (T049-T065, part…
phgoncalves Nov 8, 2025
fe4014f
feat: complete User Story 2 - API key management endpoints and authen…
phgoncalves Nov 8, 2025
f8b4186
feat: implement User Story 3 - audit logging infrastructure (T073-T08…
phgoncalves Nov 8, 2025
7beffc5
feat: add authentication API tests to api-tests.http
phgoncalves Nov 8, 2025
2adfbcf
Merge remote-tracking branch 'origin/develop' into 004-unified-auth
phgoncalves Nov 8, 2025
2f74aea
feat: add dynamic variables to authentication API tests
phgoncalves Nov 8, 2025
70e0973
chore: save solution file state before restructuring
phgoncalves Nov 8, 2025
d303411
fix: restructure solution file and add missing test projects
phgoncalves Nov 8, 2025
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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Auto-generated from all feature plans. Last updated: 2025-10-28

## Active Technologies
- .NET 9 with C# 13 + ASP.NET Core 9 (Minimal APIs), Entity Framework Core 9, BCrypt.Net, FluentValidation, Serilog, NEEDS CLARIFICATION (JWT library selection) (004-unified-auth)

### Backend Framework
- .NET 9 with C# 13
Expand Down Expand Up @@ -49,9 +50,9 @@ dotnet build; dotnet test
.NET 9 with C# 13: Follow standard .NET conventions, use EditorConfig settings

## Recent Changes
- 004-unified-auth: Added .NET 9 with C# 13 + ASP.NET Core 9 (Minimal APIs), Entity Framework Core 9, BCrypt.Net, FluentValidation, Serilog, NEEDS CLARIFICATION (JWT library selection)
- 003-github-actions-ci: Added .NET 9 with C# 13 + GitHub Actions YAML (latest), .NET 9 SDK, GitHub Actions runners (ubuntu-latest)
- 003-github-actions-ci: Added GitHub Actions YAML (latest), .NET 9 SDK + GitHub Actions runners (ubuntu-latest), .NET 9 SDK, existing test suite
- 002-cors-configuration: Added .NET 9 with C# 13 + ASP.NET Core 9 (Minimal APIs), Serilog (logging already configured)

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->
84 changes: 70 additions & 14 deletions CftApi.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,21 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CftApi.Contracts", "src\Con
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Users.Tests", "tests\Modules\Users.Tests\Users.Tests.csproj", "{6FAAA64E-D1BC-4C99-972B-54D4EF6E7696}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CftApi.Tests", "src\CftApi.Tests\CftApi.Tests.csproj", "{54E80562-55B8-4D00-9695-38D9AB5BE188}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{B84A1BDD-2941-2BDC-15F2-488482FDCA47}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CftApi.Modules.Authentication", "src\Modules\Authentication\CftApi.Modules.Authentication.csproj", "{0CE48025-6FC4-47D7-9119-1FF3100353BC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{D7DC9B74-6BC4-2470-2038-1E57C2DCB73B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication.Tests", "tests\Modules\Authentication.Tests\Authentication.Tests.csproj", "{5A044EF2-43A1-435F-9C4A-31DAB47911D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integration.Tests", "tests\Integration\Integration.Tests.csproj", "{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unit.Tests", "tests\Unit\Unit.Tests.csproj", "{7311E646-2B65-4233-BAB6-F8C9D267158B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -87,18 +101,54 @@ Global
{6FAAA64E-D1BC-4C99-972B-54D4EF6E7696}.Release|x64.Build.0 = Release|Any CPU
{6FAAA64E-D1BC-4C99-972B-54D4EF6E7696}.Release|x86.ActiveCfg = Release|Any CPU
{6FAAA64E-D1BC-4C99-972B-54D4EF6E7696}.Release|x86.Build.0 = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|x64.ActiveCfg = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|x64.Build.0 = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|x86.ActiveCfg = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Debug|x86.Build.0 = Debug|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|Any CPU.Build.0 = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|x64.ActiveCfg = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|x64.Build.0 = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|x86.ActiveCfg = Release|Any CPU
{54E80562-55B8-4D00-9695-38D9AB5BE188}.Release|x86.Build.0 = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|x64.ActiveCfg = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|x64.Build.0 = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|x86.ActiveCfg = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Debug|x86.Build.0 = Debug|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|Any CPU.Build.0 = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|x64.ActiveCfg = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|x64.Build.0 = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|x86.ActiveCfg = Release|Any CPU
{0CE48025-6FC4-47D7-9119-1FF3100353BC}.Release|x86.Build.0 = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|x64.ActiveCfg = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|x64.Build.0 = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|x86.ActiveCfg = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Debug|x86.Build.0 = Debug|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|Any CPU.Build.0 = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|x64.ActiveCfg = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|x64.Build.0 = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|x86.ActiveCfg = Release|Any CPU
{5A044EF2-43A1-435F-9C4A-31DAB47911D2}.Release|x86.Build.0 = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|x64.Build.0 = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Debug|x86.Build.0 = Debug|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|Any CPU.Build.0 = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|x64.ActiveCfg = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|x64.Build.0 = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|x86.ActiveCfg = Release|Any CPU
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983}.Release|x86.Build.0 = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|x64.ActiveCfg = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|x64.Build.0 = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|x86.ActiveCfg = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Debug|x86.Build.0 = Debug|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|Any CPU.Build.0 = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|x64.ActiveCfg = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|x64.Build.0 = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|x86.ActiveCfg = Release|Any CPU
{7311E646-2B65-4233-BAB6-F8C9D267158B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -109,6 +159,12 @@ Global
{FA635F08-4B94-4EA9-BE69-D828681D418A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{2482FD37-5484-4DAF-9E1E-467FC689EB23} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{6FAAA64E-D1BC-4C99-972B-54D4EF6E7696} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{54E80562-55B8-4D00-9695-38D9AB5BE188} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{EC447DCF-ABFA-6E24-52A5-D7FD48A5C558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{B84A1BDD-2941-2BDC-15F2-488482FDCA47} = {EC447DCF-ABFA-6E24-52A5-D7FD48A5C558}
{0CE48025-6FC4-47D7-9119-1FF3100353BC} = {B84A1BDD-2941-2BDC-15F2-488482FDCA47}
{D7DC9B74-6BC4-2470-2038-1E57C2DCB73B} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{5A044EF2-43A1-435F-9C4A-31DAB47911D2} = {D7DC9B74-6BC4-2470-2038-1E57C2DCB73B}
{A3237DE0-6C7F-4E50-AAE4-9E1C98F6A983} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{7311E646-2B65-4233-BAB6-F8C9D267158B} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
204 changes: 202 additions & 2 deletions api-tests.http
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
### Use these requests to test the API from within Rider
### Click the green ▢️ icon next to each request to execute

### Variables
@baseUrl = http://localhost:5000
@apiUrl = {{baseUrl}}/api/v1
@authUrl = {{baseUrl}}/v1

### Health Check
GET http://localhost:5000/health
GET {{baseUrl}}/health
Accept: application/json

###

### 1. Successful Registration
POST http://localhost:5000/api/v1/register
POST {{apiUrl}}/register
Content-Type: application/json

{
Expand Down Expand Up @@ -186,4 +191,199 @@ Content-Type: application/json
"password": "Senha@123"
}

###

### ===============================================
### Authentication API Tests (JWT + API Keys)
### ===============================================

### 16. Login with valid credentials (saves accessToken and refreshToken)
# @name login
POST {{authUrl}}/auth/login
Content-Type: application/json

{
"email": "joao.silva@example.com",
"password": "Senha@123"
}

> {%
client.global.set("accessToken", response.body.accessToken);
client.global.set("refreshToken", response.body.refreshToken);
client.log("Access token saved: " + response.body.accessToken.substring(0, 20) + "...");
%}

###

### 17. Login with invalid credentials (should return 401)
POST http://localhost:5000/v1/auth/login
Content-Type: application/json

{
"email": "joao.silva@example.com",
"password": "WrongPassword123"
}

###

### 18. Login with missing email (should return 400)
POST http://localhost:5000/v1/auth/login
Content-Type: application/json

{
"password": "Senha@123"
}

###

### 19. Login rate limit test (should return 429 after 5 attempts)
POST http://localhost:5000/v1/auth/login
Content-Type: application/json

{
"email": "test@example.com",
"password": "Senha@123"
}

###

### 20. Refresh access token using refresh token (uses saved refreshToken)
POST {{authUrl}}/auth/refresh
Content-Type: application/json

{
"refreshToken": "{{refreshToken}}"
}

> {%
client.global.set("accessToken", response.body.accessToken);
client.global.set("refreshToken", response.body.refreshToken);
client.log("Tokens refreshed and saved");
%}

###

### 21. Refresh with invalid token (should return 401)
POST http://localhost:5000/v1/auth/refresh
Content-Type: application/json

{
"refreshToken": "rt_invalid_token_abc123"
}

###

### 22. Logout (revoke tokens) - uses saved accessToken
POST {{authUrl}}/auth/logout
Authorization: Bearer {{accessToken}}

###

### 23. Logout without authentication (should return 401)
POST http://localhost:5000/v1/auth/logout

###

### 24. Get audit log for authenticated user - uses saved accessToken
GET {{authUrl}}/audit?limit=100&offset=0
Authorization: Bearer {{accessToken}}

###

### 25. Get audit log with pagination - uses saved accessToken
GET {{authUrl}}/audit?limit=50&offset=50
Authorization: Bearer {{accessToken}}

###

### 26. Get audit log without authentication (should return 401)
GET http://localhost:5000/v1/audit

###

### ===============================================
### API Key Management Tests
### ===============================================

### 27. List all API keys for current user - uses saved accessToken
GET {{authUrl}}/api-keys
Authorization: Bearer {{accessToken}}

###

### 28. Create new API key with name - uses saved accessToken (saves apiKey)
POST {{authUrl}}/api-keys
Authorization: Bearer {{accessToken}}
Content-Type: application/json

{
"name": "Production Server"
}

> {%
client.global.set("apiKey", response.body.key);
client.global.set("apiKeyId", response.body.id);
client.log("API Key created and saved: " + response.body.key.substring(0, 20) + "...");
client.log("API Key ID saved: " + response.body.id);
%}

###

### 29. Create new API key without name - uses saved accessToken
POST {{authUrl}}/api-keys
Authorization: Bearer {{accessToken}}
Content-Type: application/json

{
"name": null
}

###

### 30. Create API key with name too long (should return 400) - uses saved accessToken
POST {{authUrl}}/api-keys
Authorization: Bearer {{accessToken}}
Content-Type: application/json

{
"name": "This is a very long name that exceeds the maximum allowed length of 100 characters for API key names and should be rejected"
}

###

### 31. Create API key when max limit reached (should return 400) - uses saved accessToken
# Run this after creating 5 API keys
POST {{authUrl}}/api-keys
Authorization: Bearer {{accessToken}}
Content-Type: application/json

{
"name": "Sixth API Key"
}

###

### 32. Delete (revoke) an API key - uses saved accessToken and apiKeyId
DELETE {{authUrl}}/api-keys/{{apiKeyId}}
Authorization: Bearer {{accessToken}}

###

### 33. Delete non-existent API key (should return 404) - uses saved accessToken
DELETE {{authUrl}}/api-keys/00000000-0000-0000-0000-000000000000
Authorization: Bearer {{accessToken}}

###

### 34. Authenticate using API key - uses saved apiKey
GET {{authUrl}}/audit
Authorization: Bearer {{apiKey}}

###

### 35. Authenticate with revoked API key (should return 401)
# Note: Replace <REVOKED_API_KEY> with a revoked API key
GET {{authUrl}}/audit
Authorization: Bearer <REVOKED_API_KEY>

###
Loading
Loading