Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 5954381

Browse files
authored
Merge pull request #6 from wahyd4/revise-ui
Revise UI
2 parents dda44b9 + 0ef262b commit 5954381

9 files changed

Lines changed: 607 additions & 438 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ k8s/*-secret.yaml
5656

5757
# Development files
5858
dev-*
59+
60+
*.log

CHANGELOG.md

Lines changed: 0 additions & 73 deletions
This file was deleted.

config.example.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,15 @@ auth:
5353
# - "user1@yourcompany.com"
5454
# - "user2@yourcompany.com"
5555

56+
# Admin email - user with this email will be auto-approved and can access admin panel
57+
# Can also be set via ADMIN_EMAIL environment variable
58+
admin_email: "" # Set this to your admin email address
59+
5660
# Environment-specific overrides can be set via environment variables:
5761
# - PORT: Server port
5862
# - HOST: Server host
5963
# - WEBAUTHN_RP_ID: WebAuthn Relying Party ID
6064
# - DATABASE_PATH: Database file path
6165
# - SESSION_SECRET: Session encryption secret
6266
# - ALLOWED_EMAILS: Comma-separated list of allowed emails
67+
# - ADMIN_EMAIL: Admin email address

config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ auth:
2222
allowed_emails:
2323
# - "admin@example.com"
2424
# - "user@example.com"
25+
# Admin email - user with this email will be auto-approved and can access admin panel
26+
admin_email: "" # Set this to your admin email or use ADMIN_EMAIL environment variable

internal/config/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type AuthConfig struct {
4040
SessionSecret string `yaml:"session_secret"`
4141
RequireApproval bool `yaml:"require_approval"`
4242
AllowedEmails []string `yaml:"allowed_emails"`
43+
AdminEmail string `yaml:"admin_email"`
4344
}
4445

4546
func Load() (*Config, error) {
@@ -113,6 +114,9 @@ func Load() (*Config, error) {
113114
config.Auth.AllowedEmails[i] = strings.TrimSpace(email)
114115
}
115116
}
117+
if adminEmail := os.Getenv("ADMIN_EMAIL"); adminEmail != "" {
118+
config.Auth.AdminEmail = adminEmail
119+
}
116120

117121
return config, nil
118122
}
@@ -135,6 +139,11 @@ func (c *Config) IsEmailAllowed(email string) bool {
135139
return false
136140
}
137141

142+
// IsAdmin checks if an email address is the admin email
143+
func (c *Config) IsAdmin(email string) bool {
144+
return c.Auth.AdminEmail != "" && email == c.Auth.AdminEmail
145+
}
146+
138147
// validateConfigPath ensures the config path is safe and doesn't allow path traversal
139148
func validateConfigPath(path string) error {
140149
// Clean the path and check for path traversal attempts

internal/database/database.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type Credential struct {
3131
}
3232

3333
func New(dbPath string) (*DB, error) {
34-
conn, err := sql.Open("sqlite3", dbPath+"?_fk=1")
34+
conn, err := sql.Open("sqlite", dbPath+"?_fk=1")
3535
if err != nil {
3636
return nil, err
3737
}
@@ -98,6 +98,23 @@ func (db *DB) CreateUser(email, displayName string) (*User, error) {
9898
return db.GetUser(int(id))
9999
}
100100

101+
func (db *DB) CreateUserWithApproval(email, displayName string, approved bool) (*User, error) {
102+
result, err := db.conn.Exec(
103+
"INSERT INTO users (email, display_name, approved) VALUES (?, ?, ?)",
104+
email, displayName, approved,
105+
)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
id, err := result.LastInsertId()
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
return db.GetUser(int(id))
116+
}
117+
101118
func (db *DB) GetUser(id int) (*User, error) {
102119
var user User
103120
err := db.conn.QueryRow(

internal/handlers/handlers.go

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,18 @@ func (h *Handlers) BeginRegistration(w http.ResponseWriter, r *http.Request) {
9494
}
9595

9696
// Create new user
97-
user, err := h.db.CreateUser(req.Email, req.DisplayName)
97+
isAdmin := h.config.IsAdmin(req.Email)
98+
user, err := h.db.CreateUserWithApproval(req.Email, req.DisplayName, isAdmin)
9899
if err != nil {
99100
logrus.Errorf("Failed to create user: %v", err)
100101
h.writeError(w, "Failed to create user", http.StatusInternalServerError)
101102
return
102103
}
103104

105+
if isAdmin {
106+
logrus.Infof("Admin user auto-approved: %s", req.Email)
107+
}
108+
104109
webAuthnUser := &auth.WebAuthnUser{}
105110
webAuthnUser.SetUser(user)
106111

@@ -190,7 +195,17 @@ func (h *Handlers) FinishRegistration(w http.ResponseWriter, r *http.Request) {
190195
return
191196
}
192197

193-
// Clear session
198+
// Set authenticated session after successful registration
199+
authSession, _ := h.store.Get(r, "auth-session")
200+
authSession.Values["authenticated"] = true
201+
authSession.Values["user_id"] = user.ID
202+
authSession.Values["user_email"] = user.Email
203+
if err := authSession.Save(r, w); err != nil {
204+
h.writeError(w, "Failed to save auth session", http.StatusInternalServerError)
205+
return
206+
}
207+
208+
// Clear webauthn session
194209
session.Values["challenge"] = nil
195210
session.Values["user_id"] = nil
196211
if err := session.Save(r, w); err != nil {
@@ -364,11 +379,55 @@ func (h *Handlers) AuthCheck(w http.ResponseWriter, r *http.Request) {
364379
w.WriteHeader(http.StatusOK)
365380
}
366381

382+
// GetAuthStatus returns the current authentication status
383+
func (h *Handlers) GetAuthStatus(w http.ResponseWriter, r *http.Request) {
384+
session, err := h.store.Get(r, "auth-session")
385+
if err != nil {
386+
h.writeError(w, "Failed to get session", http.StatusInternalServerError)
387+
return
388+
}
389+
390+
userEmail, ok := session.Values["user_email"].(string)
391+
if !ok || userEmail == "" {
392+
h.writeError(w, "Not authenticated", http.StatusUnauthorized)
393+
return
394+
}
395+
396+
// Get user details from database
397+
user, err := h.db.GetUserByEmail(userEmail)
398+
if err != nil {
399+
h.writeError(w, "User not found", http.StatusNotFound)
400+
return
401+
}
402+
403+
// Check if user is approved
404+
if !user.Approved {
405+
h.writeError(w, "User not approved", http.StatusForbidden)
406+
return
407+
}
408+
409+
response := map[string]interface{}{
410+
"authenticated": true,
411+
"user": map[string]interface{}{
412+
"id": user.ID,
413+
"email": user.Email,
414+
"display_name": user.DisplayName,
415+
"approved": user.Approved,
416+
"is_admin": h.config.IsAdmin(user.Email),
417+
},
418+
}
419+
420+
h.writeJSON(w, response)
421+
}
422+
367423
// Admin endpoints
368424

369425
// ListUsers returns all users (admin endpoint)
370426
func (h *Handlers) ListUsers(w http.ResponseWriter, r *http.Request) {
371-
// TODO: Add admin authentication check
427+
if !h.requireAdmin(w, r) {
428+
return
429+
}
430+
372431
users, err := h.db.ListUsers()
373432
if err != nil {
374433
logrus.Errorf("Failed to list users: %v", err)
@@ -381,7 +440,10 @@ func (h *Handlers) ListUsers(w http.ResponseWriter, r *http.Request) {
381440

382441
// CreateUser creates a new user (admin endpoint)
383442
func (h *Handlers) CreateUser(w http.ResponseWriter, r *http.Request) {
384-
// TODO: Add admin authentication check
443+
if !h.requireAdmin(w, r) {
444+
return
445+
}
446+
385447
var req struct {
386448
Email string `json:"email"`
387449
DisplayName string `json:"display_name"`
@@ -399,7 +461,7 @@ func (h *Handlers) CreateUser(w http.ResponseWriter, r *http.Request) {
399461
return
400462
}
401463

402-
user, err := h.db.CreateUser(req.Email, req.DisplayName)
464+
user, err := h.db.CreateUserWithApproval(req.Email, req.DisplayName, req.Approved)
403465
if err != nil {
404466
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
405467
h.writeError(w, "User already exists", http.StatusConflict)
@@ -410,18 +472,15 @@ func (h *Handlers) CreateUser(w http.ResponseWriter, r *http.Request) {
410472
return
411473
}
412474

413-
if req.Approved {
414-
if err := h.db.ApproveUser(user.ID); err != nil {
415-
logrus.Errorf("Failed to approve user: %v", err)
416-
}
417-
}
418-
419475
h.writeJSON(w, user)
420476
}
421477

422478
// UpdateUser updates a user (admin endpoint)
423479
func (h *Handlers) UpdateUser(w http.ResponseWriter, r *http.Request) {
424-
// TODO: Add admin authentication check
480+
if !h.requireAdmin(w, r) {
481+
return
482+
}
483+
425484
vars := mux.Vars(r)
426485
idStr, ok := vars["id"]
427486
if !ok {
@@ -467,7 +526,10 @@ func (h *Handlers) UpdateUser(w http.ResponseWriter, r *http.Request) {
467526

468527
// DeleteUser deletes a user (admin endpoint)
469528
func (h *Handlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
470-
// TODO: Add admin authentication check
529+
if !h.requireAdmin(w, r) {
530+
return
531+
}
532+
471533
vars := mux.Vars(r)
472534
idStr, ok := vars["id"]
473535
if !ok {
@@ -489,3 +551,25 @@ func (h *Handlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
489551

490552
h.writeJSON(w, map[string]string{"status": "success"})
491553
}
554+
555+
func (h *Handlers) isAdmin(r *http.Request) bool {
556+
session, err := h.store.Get(r, "auth-session")
557+
if err != nil {
558+
return false
559+
}
560+
561+
userEmail, ok := session.Values["user_email"].(string)
562+
if !ok {
563+
return false
564+
}
565+
566+
return h.config.IsAdmin(userEmail)
567+
}
568+
569+
func (h *Handlers) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
570+
if !h.isAdmin(r) {
571+
h.writeError(w, "Admin access required", http.StatusForbidden)
572+
return false
573+
}
574+
return true
575+
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func main() {
5454
api.HandleFunc("/login/begin", h.BeginLogin).Methods("POST")
5555
api.HandleFunc("/login/finish", h.FinishLogin).Methods("POST")
5656
api.HandleFunc("/logout", h.Logout).Methods("POST")
57+
api.HandleFunc("/auth/status", h.GetAuthStatus).Methods("GET")
5758
api.HandleFunc("/users", h.ListUsers).Methods("GET")
5859
api.HandleFunc("/users", h.CreateUser).Methods("POST")
5960
api.HandleFunc("/users/{id}", h.UpdateUser).Methods("PUT")

0 commit comments

Comments
 (0)