Skip to content

Commit 86e81d0

Browse files
authored
feat: add auth feature endpoints and cqrs dispatch (#22)
1 parent 95ff904 commit 86e81d0

137 files changed

Lines changed: 5167 additions & 320 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/mcp/postgres-mcp.ps1

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
$ErrorActionPreference = "Stop"
2+
3+
function Get-PostgresUrlFromDocker {
4+
$docker = Get-Command docker -ErrorAction SilentlyContinue
5+
if ($null -eq $docker) {
6+
return $null
7+
}
8+
9+
$envLines = & docker inspect smartmoviecatalog-postgres-1 --format '{{range .Config.Env}}{{println .}}{{end}}' 2>$null
10+
if ($LASTEXITCODE -ne 0 -or $null -eq $envLines) {
11+
return $null
12+
}
13+
14+
$containerEnv = @{}
15+
foreach ($line in $envLines) {
16+
$parts = $line.Split("=", 2)
17+
if ($parts.Length -eq 2) {
18+
$containerEnv[$parts[0]] = $parts[1]
19+
}
20+
}
21+
22+
$user = $containerEnv["POSTGRES_USER"]
23+
$password = $containerEnv["POSTGRES_PASSWORD"]
24+
$database = $containerEnv["POSTGRES_DB"]
25+
26+
if ([string]::IsNullOrWhiteSpace($user) -or
27+
[string]::IsNullOrWhiteSpace($password) -or
28+
[string]::IsNullOrWhiteSpace($database)) {
29+
return $null
30+
}
31+
32+
$escapedUser = [uri]::EscapeDataString($user)
33+
$escapedPassword = [uri]::EscapeDataString($password)
34+
$escapedDatabase = [uri]::EscapeDataString($database)
35+
36+
return "postgresql://${escapedUser}:${escapedPassword}@localhost:5432/${escapedDatabase}"
37+
}
38+
39+
$connectionString = $env:SMARTMOVIECATALOG_POSTGRES_URL
40+
if ([string]::IsNullOrWhiteSpace($connectionString)) {
41+
$connectionString = Get-PostgresUrlFromDocker
42+
}
43+
44+
if ([string]::IsNullOrWhiteSpace($connectionString)) {
45+
[Console]::Error.WriteLine("SMARTMOVIECATALOG_POSTGRES_URL is not set and smartmoviecatalog-postgres-1 could not provide PostgreSQL settings.")
46+
exit 1
47+
}
48+
49+
& npx -y @modelcontextprotocol/server-postgres $connectionString
50+
exit $LASTEXITCODE

.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,16 @@ POSTGRES_DB=smart_movie_catalog
99
POSTGRES_USER=smartmovie
1010
POSTGRES_PASSWORD=smartmovie_dev_password
1111
POSTGRES_PORT=5432
12+
13+
DATABASE_URL = postgresql://smartmovie:smartmovie_dev_password@localhost:5432/smart_movie_catalog?schema=public
14+
15+
JWT_ISSUER=SmartMovieCatalog
16+
JWT_AUDIENCE=SmartMovieCatalog.Api
17+
JWT_SIGNING_KEY=replace-with-local-development-signing-key-at-least-32-characters
18+
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=60
19+
20+
# Optional admin seed. Leave blank to disable.
21+
# Do not commit real admin credentials.
22+
ADMIN_SEED_EMAIL=
23+
ADMIN_SEED_PASSWORD=
24+
ADMIN_SEED_NAME=

.github/workflows/sonar.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ jobs:
5858
/d:sonar.qualitygate.timeout=300 \
5959
/d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" \
6060
/d:sonar.javascript.lcov.reportPaths="frontend/coverage/smartmoviecatalog.angular/lcov.info" \
61-
/d:sonar.exclusions="**/bin/**,**/obj/**,**/node_modules/**,**/dist/**,**/.angular/**"
61+
/d:sonar.exclusions="**/bin/**,**/obj/**,**/node_modules/**,**/dist/**,**/.angular/**" \
62+
/d:sonar.coverage.exclusions="**/Migrations/**,**/*ModelSnapshot.cs,**/Program.cs,**/DependencyInjection.cs,**/DatabaseBootstrapper.cs,**/HealthCheckRunner.cs,**/Persistence/Configurations/**"
6263
6364
- name: Restore backend
6465
run: dotnet restore SmartMovieCatalog.slnx

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ bin/
33
obj/
44
dist/
55
out/
6+
.artifacts/
7+
.dotnet-cli-home/
8+
restore*.log
69

710
# .NET
811
*.user
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# GitHub Issue Context
2+
3+
## Source
4+
5+
- Repository: marciomyst/SmartMovieCatalog
6+
- Issue: #21
7+
- URL: https://github.com/marciomyst/SmartMovieCatalog/issues/21
8+
- State: OPEN
9+
- Created: 05/03/2026 00:59:57
10+
- Updated: 05/03/2026 01:00:54
11+
- Milestone: M1 — Core Movie Catalog
12+
13+
## Title
14+
15+
Backend authentication with JWT and current user context
16+
17+
## Labels
18+
19+
- type:feature
20+
- priority:high
21+
- area:api
22+
- area:backend
23+
- area:database
24+
- area:architecture
25+
- area:security
26+
- needs:adr
27+
28+
## Assignees
29+
30+
- marciomyst
31+
32+
33+
34+
## Issue Body
35+
36+
## Descricao
37+
Implementar autenticacao backend no SmartMovieCatalog com `POST /api/auth/authenticate` e `GET /api/auth/me`, adaptando o desenho analisado em `MercaSafra` para a arquitetura Clean Architecture deste repositorio.
38+
39+
O escopo e somente backend API. Nao incluir frontend Angular nesta issue.
40+
41+
## Escopo Tecnico
42+
- Aceitar a estrategia de autenticacao em `docs/adr/0002-authentication-strategy.md`: autenticacao local com email/senha e JWT bearer.
43+
- Aceitar a estrategia de persistencia em `docs/adr/0003-database-strategy.md`: EF Core com PostgreSQL usando `ConnectionStrings:DefaultConnection`.
44+
- Definir/atualizar contrato de erro em `docs/adr/0004-api-error-contract.md`.
45+
- Adicionar entidade/aggregate de usuario no dominio, abstracoes de aplicacao e implementacoes de infraestrutura.
46+
- Configurar EF Core, migrations iniciais, hashing de senha, geracao JWT e leitura do usuario autenticado.
47+
- Configurar `AddAuthentication().AddJwtBearer(...)` e `UseAuthentication()` antes de `UseAuthorization()`.
48+
49+
## API Contracts
50+
### `POST /api/auth/authenticate`
51+
Request:
52+
```json
53+
{ "email": "user@example.com", "password": "Password123!" }
54+
```
55+
56+
Responses:
57+
- `200 OK`: `{ userId, email, accessToken, accessTokenExpiresAtUtc }`
58+
- `400 Bad Request`: entrada invalida
59+
- `401 Unauthorized`: credenciais invalidas, usuario inexistente ou inativo
60+
61+
### `GET /api/auth/me`
62+
Requer bearer token.
63+
64+
Responses:
65+
- `200 OK`: `{ userId, email, name, roles, mustChangePasswordOnFirstLogin }`
66+
- `401 Unauthorized`: token ausente/invalido ou usuario inexistente/inativo
67+
68+
## Fora De Escopo
69+
- Frontend login/session UI.
70+
- Refresh token.
71+
- Registro de usuario.
72+
- Recuperacao de senha.
73+
- Provedor externo de identidade.
74+
- Organizacoes/tenancy.
75+
- Autorizacao granular alem de roles basicas no token.
76+
77+
## Documentacao
78+
Atualizar:
79+
- `docs/adr/0002-authentication-strategy.md`
80+
- `docs/adr/0003-database-strategy.md`
81+
- `docs/adr/0004-api-error-contract.md`
82+
- `docs/API.md`
83+
- `docs/SECURITY.md`
84+
- `README.md`
85+
- `backend/src/SmartMovieCatalog.Api/SmartMovieCatalog.Api.http`
86+
87+
## Testes
88+
Application tests:
89+
- autenticacao valida retorna token;
90+
- senha invalida retorna falha nao autenticada;
91+
- usuario inexistente/inativo nao autentica;
92+
- contexto atual exige usuario autenticado;
93+
- contexto atual rejeita usuario removido/inativo.
94+
95+
API tests:
96+
- `POST /api/auth/authenticate` cobre `200`, `400`, `401`;
97+
- `GET /api/auth/me` cobre `200` com token e `401` sem token.
98+
99+
Verificacao final:
100+
- `dotnet build SmartMovieCatalog.slnx`
101+
- executar testes backend adicionados.
102+
103+
## Assumptions
104+
- PostgreSQL e o provider escolhido porque ja existe em `docker-compose.yml`.
105+
- Segredos JWT ficam em environment variables ou user-secrets, nunca em arquivos versionados.
106+
- O scaffold `WeatherForecast` pode ser removido quando os endpoints reais forem introduzidos.
107+
108+
## Comments
109+
110+
_No comments_
111+
112+
## Instructions for Spec Kit
113+
114+
Use this GitHub issue as the primary source of truth.
115+
116+
Convert the issue into a Spec Kit feature specification before creating the implementation plan.
117+
118+
Preserve:
119+
120+
- business goal;
121+
- user stories;
122+
- acceptance criteria;
123+
- technical constraints;
124+
- non-goals;
125+
- dependencies;
126+
- open questions.
127+
128+
If information is missing, add it under a clearly marked **Clarifications Needed** section instead of inventing requirements.
129+
130+
If the issue conflicts with existing project documentation, explicitly call out the conflict.
131+
132+
Prefer a small, incremental implementation plan aligned with the repository's existing architecture, folder structure, language, framework, and conventions.

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"**/.husky": true,
1111
"**/.agents": true,
1212
"**/.github": true,
13+
"**/.artifacts": true,
14+
"**/.dotnet-cli-home": true,
1315
},
1416

1517
"search.exclude": {

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ Do not read, modify, or base analysis on generated/vendor output unless explicit
127127
- `test:`
128128

129129
<!-- SPECKIT START -->
130+
Current Spec Kit plan: `specs/021-backend-authentication-with-jwt-and-current-user-context/plan.md`.
131+
130132
Before using Spec Kit skills, read `.specify/memory/constitution.md`.
131133
If the spec, plan, or implementation touches backend, API, contracts, domain,
132134
application, infrastructure, security, persistence, or server runtime behavior,

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Project>
2+
<PropertyGroup>
3+
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
4+
</PropertyGroup>
5+
</Project>

Directory.Packages.props

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
4+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7" />
5+
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.7" />
6+
<PackageVersion Include="Microsoft.AspNetCore.SpaProxy" Version="10.0.7" />
7+
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
8+
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
9+
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.7" />
10+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
11+
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
12+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
13+
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.7" />
14+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
15+
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
16+
<PackageVersion Include="xunit" Version="2.9.3" />
17+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
18+
<PackageVersion Include="FluentValidation" Version="12.1.1" />
19+
<PackageVersion Include="WolverineFx" Version="5.32.0" />
20+
</ItemGroup>
21+
</Project>

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,12 @@ This starts:
8686
- ASP.NET Core API on `http://localhost:5048`.
8787
- PostgreSQL on `localhost:5432`.
8888

89-
The API container receives `ConnectionStrings__DefaultConnection` through environment variables. The current scaffold does not use persistence yet.
89+
The API container receives `ConnectionStrings__DefaultConnection` and JWT settings through environment variables. Set `JWT_SIGNING_KEY` in `.env` or your shell before starting the API; do not commit real signing keys or database credentials.
9090

91-
The current runnable vertical slice is still the scaffold `/weatherforecast` endpoint consumed by the Angular app.
91+
The backend auth endpoints are:
92+
93+
- `POST /api/auth/authenticate`
94+
- `GET /api/auth/me`
9295

9396
Health check:
9497

@@ -119,9 +122,39 @@ docker compose exec postgres pg_isready -U smartmovie -d smart_movie_catalog
119122
docker compose exec postgres psql -U smartmovie -d smart_movie_catalog -c "select version();"
120123
```
121124

125+
Apply EF Core migrations after configuring the local connection string:
126+
127+
```bash
128+
dotnet ef database update --project backend/src/SmartMovieCatalog.Infrastructure --startup-project backend/src/SmartMovieCatalog.Api
129+
```
130+
131+
On API startup, non-test environments apply EF Core migrations and can seed an optional admin user by supplying non-versioned configuration:
132+
133+
- `ADMIN_SEED_EMAIL`
134+
- `ADMIN_SEED_PASSWORD`
135+
- `ADMIN_SEED_NAME`
136+
137+
These `.env` keys are mapped by the local scripts and Docker Compose to `AdminSeedUser:*` configuration. Leave `ADMIN_SEED_EMAIL` and `ADMIN_SEED_PASSWORD` empty to disable the seed. Never commit real admin credentials.
138+
139+
Example local admin seed:
140+
141+
```env
142+
ADMIN_SEED_EMAIL=admin@example.com
143+
ADMIN_SEED_PASSWORD=Password123!
144+
ADMIN_SEED_NAME=Admin
145+
```
146+
147+
The seed runs only during API startup. After changing `.env`, restart the API process or recreate the Docker Compose API container:
148+
149+
```powershell
150+
.\scripts\run-local.ps1
151+
docker compose up -d --force-recreate api
152+
```
153+
122154
## Development Notes
123155
- Backend entry point: `backend/src/SmartMovieCatalog.Api/Program.cs`.
124-
- Backend API scaffold: `backend/src/SmartMovieCatalog.Api/Controllers`.
156+
- Backend HTTP endpoints: Minimal API feature slices under `backend/src/SmartMovieCatalog.Api/Features`.
157+
- Auth endpoint mapping: `backend/src/SmartMovieCatalog.Api/Features/Auth`.
125158
- Frontend application: `frontend/src/app`.
126159
- Frontend visual system: `frontend/DESIGN.md`.
127160
- Root design policy: `DESIGN.md`.

0 commit comments

Comments
 (0)