Files
kanban_cpp/backend/auth.go
T
Egutierrez a76ec74338 feat: initial scaffold kanban_cpp v0.1.0
C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar,
Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry
functions http_request, kpi_card, sparkline, agent_runs_timeline,
dod_evidence_panel. Backend Go on :8403 (independent operations.db from
kanban_web).
2026-05-18 18:46:09 +02:00

157 lines
4.2 KiB
Go

package main
import (
"errors"
"net/http"
"time"
"fn-registry/functions/infra"
)
const (
cookieName = "kanban_session"
sessionTTL = 7 * 24 * time.Hour
)
type ctxKey string
const userCtxKey ctxKey = "kanban_user_id"
func setSessionCookie(w http.ResponseWriter, token string, expiresAt int64) {
infra.SessionCookieSet(w, cookieName, token, expiresAt)
}
func clearSessionCookie(w http.ResponseWriter) {
infra.SessionCookieClear(w, cookieName)
}
func tokenFromRequest(r *http.Request) string {
return infra.SessionTokenExtract(r, cookieName)
}
// POST /api/auth/register {username, password, display_name?}
func handleRegister(db *DB, flags *FeatureFlags) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !flags.Enabled("registration-enabled") {
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusForbidden, Code: "registration_disabled", Message: "user registration is disabled on this instance"})
return
}
var body struct {
Username string `json:"username"`
Password string `json:"password"`
DisplayName string `json:"display_name"`
}
if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
badRequest(w, err.Error())
return
}
u, err := db.CreateUser(body.Username, body.Password, body.DisplayName)
if err != nil {
if errors.Is(err, errUserAlreadyExists) {
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusConflict, Code: "user_exists", Message: err.Error()})
return
}
badRequest(w, err.Error())
return
}
infra.HTTPJSONResponse(w, http.StatusCreated, u)
}
}
// POST /api/auth/login {username, password}
func handleLogin(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var body struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
badRequest(w, err.Error())
return
}
u, err := db.Authenticate(body.Username, body.Password)
if err != nil {
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "invalid_credentials", Message: "invalid username or password"})
return
}
sess, err := infra.SessionCreate(db.conn, u.ID, sessionTTL, map[string]any{"username": u.Username})
if err != nil {
serverError(w, err)
return
}
setSessionCookie(w, sess.Token, sess.ExpiresAt)
infra.HTTPJSONResponse(w, http.StatusOK, u)
}
}
// POST /api/auth/logout
func handleLogout(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := tokenFromRequest(r)
if token != "" {
_ = db.DeleteSessionByToken(token)
}
clearSessionCookie(w)
w.WriteHeader(http.StatusNoContent)
}
}
// GET /api/me
func handleMe(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, ok := infra.UserIDFromContext(r.Context(), userCtxKey)
if !ok {
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "no session"})
return
}
u, err := db.GetUserByID(uid)
if err != nil {
serverError(w, err)
return
}
infra.HTTPJSONResponse(w, http.StatusOK, u)
}
}
// PATCH /api/me { color? }
func handlePatchMe(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, ok := infra.UserIDFromContext(r.Context(), userCtxKey)
if !ok {
infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "no session"})
return
}
var body struct {
Color *string `json:"color"`
}
if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
badRequest(w, err.Error())
return
}
if body.Color != nil {
if err := db.UpdateUserColor(uid, *body.Color); err != nil {
serverError(w, err)
return
}
}
u, err := db.GetUserByID(uid)
if err != nil {
serverError(w, err)
return
}
infra.HTTPJSONResponse(w, http.StatusOK, u)
}
}
// GET /api/users
func handleListUsers(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
users, err := db.ListUsers()
if err != nil {
serverError(w, err)
return
}
infra.HTTPJSONResponse(w, http.StatusOK, users)
}
}