Fix auth for 1.0.0: add logout endpoint, fix JWT claims consistency, add session revocation

This commit is contained in:
Leon Bösche
2026-01-09 19:53:09 +01:00
parent 2ab0786e30
commit 9daccbae82
3 changed files with 96 additions and 3 deletions

View File

@@ -132,6 +132,15 @@ func (db *DB) GetSession(ctx context.Context, sessionID uuid.UUID) (*Session, er
return &session, nil
}
func (db *DB) RevokeSession(ctx context.Context, sessionID uuid.UUID) error {
_, err := db.ExecContext(ctx, `
UPDATE sessions
SET revoked_at = NOW()
WHERE id = $1 AND revoked_at IS NULL
`, sessionID)
return err
}
func (db *DB) GetUserOrganizations(ctx context.Context, userID uuid.UUID) ([]Organization, error) {
rows, err := db.QueryContext(ctx, `
SELECT o.id, o.name, o.slug, o.created_at

View File

@@ -48,6 +48,9 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
r.Post("/refresh", func(w http.ResponseWriter, req *http.Request) {
refreshHandler(w, req, jwtManager, db)
})
r.Post("/logout", func(w http.ResponseWriter, req *http.Request) {
logoutHandler(w, req, jwtManager, db, auditLogger)
})
// Passkey routes
r.Post("/signup", func(w http.ResponseWriter, req *http.Request) {
signupHandler(w, req, db, auditLogger)
@@ -200,6 +203,41 @@ func refreshHandler(w http.ResponseWriter, r *http.Request, jwtManager *jwt.Mana
w.Write([]byte(`{"token": "` + newToken + `"}`))
}
func logoutHandler(w http.ResponseWriter, r *http.Request, jwtManager *jwt.Manager, db *database.DB, auditLogger *audit.Logger) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
errors.WriteError(w, errors.CodeUnauthenticated, "Unauthorized", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, session, err := jwtManager.ValidateWithSession(r.Context(), tokenString, db)
if err != nil {
// Token invalid or session already revoked/expired — still return success
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "ok"}`))
return
}
userID, _ := uuid.Parse(claims.UserID)
// Revoke session
if err := db.RevokeSession(r.Context(), session.ID); err != nil {
errors.LogError(r, err, "Failed to revoke session")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
return
}
auditLogger.Log(r.Context(), audit.Entry{
UserID: &userID,
Action: "logout",
Success: true,
})
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "ok"}`))
}
func listOrgsHandler(w http.ResponseWriter, r *http.Request, db *database.DB, jwtManager *jwt.Manager) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
@@ -616,7 +654,7 @@ func registrationVerifyHandler(w http.ResponseWriter, r *http.Request, db *datab
// Generate JWT
orgIDs := []string{}
token, err := jwtManager.Generate(user.Email, orgIDs, session.ID.String())
token, err := jwtManager.Generate(user.ID.String(), orgIDs, session.ID.String())
if err != nil {
errors.LogError(r, err, "Token generation failed")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
@@ -718,7 +756,7 @@ func authenticationVerifyHandler(w http.ResponseWriter, r *http.Request, db *dat
}
// Generate JWT
token, err := jwtManager.Generate(user.Email, orgIDs, session.ID.String())
token, err := jwtManager.Generate(user.ID.String(), orgIDs, session.ID.String())
if err != nil {
errors.LogError(r, err, "Token generation failed")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
@@ -788,7 +826,7 @@ func passwordLoginHandler(w http.ResponseWriter, r *http.Request, db *database.D
}
// Generate JWT
token, err := jwtManager.Generate(user.Email, orgIDs, session.ID.String())
token, err := jwtManager.Generate(user.ID.String(), orgIDs, session.ID.String())
if err != nil {
errors.LogError(r, err, "Token generation failed")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)

View File

@@ -0,0 +1,46 @@
#!/bin/bash
# Database Migration Runner for b0esche.cloud
# Runs all SQL migrations in order
set -e
# Check for required environment variable
if [ -z "$DATABASE_URL" ]; then
echo "ERROR: DATABASE_URL environment variable not set"
echo "Example: DATABASE_URL=postgres://user:pass@localhost:5432/dbname"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "=== b0esche.cloud Database Migrations ==="
echo "Database: $DATABASE_URL"
echo
# Function to run a single migration
run_migration() {
local file=$1
echo "Running: $(basename $file)"
psql "$DATABASE_URL" -f "$file" -v ON_ERROR_STOP=1
if [ $? -eq 0 ]; then
echo "✓ Success"
else
echo "✗ Failed"
exit 1
fi
}
# Run migrations in order
echo "Step 1/3: Initial schema..."
run_migration "$SCRIPT_DIR/0001_initial.sql"
echo
echo "Step 2/3: Passkeys and authentication..."
run_migration "$SCRIPT_DIR/0002_passkeys.sql"
echo
echo "Step 3/3: Files and storage..."
run_migration "$SCRIPT_DIR/0003_files.sql"
echo
echo "=== All migrations completed successfully! ==="