feat: multi-usuário com autenticação JWT

- Tabela `profiles` + coluna `profile_id` em todas as entidades
  (categories, transactions, recurring_expenses, accounts, player_profile,
   xp_events, player_quests, player_achievements, player_cosmetics)
- Dados existentes migrados para profile_id = 1 (Manoel)
- CLI `./api create-user --name <n> --password <p>` cria perfil com
  seed de categorias e player_profile; faz upsert de senha se já existir
- Auth substituída: cookie+APP_PASSWORD → JWT Bearer 24h (HS256)
- Middleware RequireAuth injeta profile_id no context de todas as rotas
- Todos os repositórios filtram por profile_id do context
- Endpoints: POST /api/auth/login, GET /api/auth/me,
  POST /api/auth/change-password, POST /api/logout
- Frontend: auth store usa localStorage (fc_token/fc_profile),
  api.ts envia Authorization header, LoginView usa campo name
- SettingsView reescrita com troca de senha e logout
- docker-compose.yml: remove APP_USERNAME/APP_PASSWORD, adiciona JWT_SECRET

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
2026-05-27 20:04:44 -03:00
co-authored by Claude Sonnet 4.6
parent 50637bd590
commit acbce4edc0
49 changed files with 2662 additions and 382 deletions
+13 -2
View File
@@ -40,6 +40,12 @@ func main() {
log.Fatalf("migration: %v", err)
}
// CLI subcommand: create-user
if len(os.Args) > 1 && os.Args[1] == "create-user" {
authmw.RunCreateUser(pool)
return
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
@@ -83,12 +89,17 @@ func main() {
r.Get("/health", handler.Health)
r.Post("/api/login", authmw.LoginHandler)
// Public auth endpoints
r.Post("/api/auth/login", authmw.LoginHandler(pool))
r.Post("/api/logout", authmw.LogoutHandler)
r.Get("/api/auth/me", authmw.MeHandler)
// Protected routes
r.Route("/api", func(r chi.Router) {
r.Use(authmw.RequireAuth)
r.Get("/auth/me", authmw.MeHandler)
r.Post("/auth/change-password", authmw.ChangePasswordHandler(pool))
r.Get("/categories", categoryHandler.List)
r.Post("/categories", categoryHandler.Create)
r.Put("/categories/{id}", categoryHandler.Update)