diff --git a/assets/dk.css b/assets/dk.css index 78f8b8c..00e9478 100644 --- a/assets/dk.css +++ b/assets/dk.css @@ -162,4 +162,52 @@ form#move-compass { height: 38px; } } +} + +button.btn { + font-family: inherit; + font-size: 1rem; + appearance: none; + outline: none; + background-color: #808080; + background-image: url("/assets/images/overlay.png"); + border: 1px solid #808080; + padding: 0.5rem 1rem; + cursor: pointer; + color: white; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2); + text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1); + + &:hover { + background-color: #909090; + } +} + +form.standard { + & > div.row { + display: flex; + flex-direction: column; + margin-bottom: 1.5rem; + + label { + font-weight: bold; + } + } + + span.help { + font-size: 0.8em; + color: #555; + } + + & > div.actions { + margin-top: 1rem; + } +} + +.mb-1 { + margin-bottom: 1rem; +} + +.mb-05 { + margin-bottom: 0.5rem; } \ No newline at end of file diff --git a/assets/images/overlay.png b/assets/images/overlay.png new file mode 100644 index 0000000..d845fc9 Binary files /dev/null and b/assets/images/overlay.png differ diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 13846af..774830b 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -6,7 +6,6 @@ import ( "dk/internal/auth" "dk/internal/csrf" - "dk/internal/database" "dk/internal/middleware" "dk/internal/password" "dk/internal/router" @@ -21,174 +20,138 @@ import ( func RegisterAuthRoutes(r *router.Router) { // Guest routes guestGroup := r.Group("") - guestGroup.Use(middleware.RequireGuest("/")) + guestGroup.Use(middleware.RequireGuest()) guestGroup.Get("/login", showLogin) - guestGroup.Post("/login", processLogin()) + guestGroup.Post("/login", processLogin) guestGroup.Get("/register", showRegister) - guestGroup.Post("/register", processRegister()) + guestGroup.Post("/register", processRegister) // Authenticated routes authGroup := r.Group("") - authGroup.Use(middleware.RequireAuth("/login")) + authGroup.Use(middleware.RequireAuth()) - authGroup.Post("/logout", processLogout()) + authGroup.Post("/logout", processLogout) } // showLogin displays the login form -func showLogin(ctx router.Ctx, params []string) { - loginTmpl, err := template.Cache.Load("auth/login.html") - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "Template error: %v", err) - return - } - - loginFormData := map[string]any{ +func showLogin(ctx router.Ctx, _ []string) { + components.RenderPageTemplate(ctx, "Log In", "auth/login.html", map[string]any{ "csrf_token": csrf.GetToken(ctx, auth.Manager), "csrf_field": csrf.HiddenField(ctx, auth.Manager), "error_message": "", - } - - loginContent := loginTmpl.RenderNamed(loginFormData) - - pageData := components.NewPageData("Login - Dragon Knight", loginContent) - if err := components.RenderPage(ctx, pageData, nil); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "Template error: %v", err) - return - } + }) } // processLogin handles login form submission -func processLogin() router.Handler { - return func(ctx router.Ctx, params []string) { - if !csrf.ValidateFormToken(ctx, auth.Manager) { - ctx.SetStatusCode(fasthttp.StatusForbidden) - ctx.WriteString("CSRF validation failed") - return - } - - email := strings.TrimSpace(string(ctx.PostArgs().Peek("email"))) - userPassword := string(ctx.PostArgs().Peek("password")) - - if email == "" || userPassword == "" { - showLoginError(ctx, "Email and password are required") - return - } - - user, err := auth.Manager.Authenticate(email, userPassword) - if err != nil { - showLoginError(ctx, "Invalid email or password") - return - } - - middleware.Login(ctx, auth.Manager, user) - - // Transfer CSRF token from cookie to session for authenticated user - if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { - if session := csrf.GetCurrentSession(ctx); session != nil { - csrf.StoreToken(session, cookieToken) - } - } - - ctx.Redirect("/", fasthttp.StatusFound) +func processLogin(ctx router.Ctx, _ []string) { + if !csrf.ValidateFormToken(ctx, auth.Manager) { + ctx.SetStatusCode(fasthttp.StatusForbidden) + ctx.WriteString("CSRF validation failed") + return } + + email := strings.TrimSpace(string(ctx.PostArgs().Peek("email"))) + userPassword := string(ctx.PostArgs().Peek("password")) + + if email == "" || userPassword == "" { + showLoginError(ctx, "Email and password are required") + return + } + + user, err := auth.Manager.Authenticate(email, userPassword) + if err != nil { + showLoginError(ctx, "Invalid email or password") + return + } + + middleware.Login(ctx, auth.Manager, user) + + // Transfer CSRF token from cookie to session for authenticated user + if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { + if session := csrf.GetCurrentSession(ctx); session != nil { + csrf.StoreToken(session, cookieToken) + } + } + + ctx.Redirect("/", fasthttp.StatusFound) } // showRegister displays the registration form func showRegister(ctx router.Ctx, _ []string) { - registerTmpl, err := template.Cache.Load("auth/register.html") - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "Template error: %v", err) - return - } - - registerContent := registerTmpl.RenderNamed(map[string]any{ + components.RenderPageTemplate(ctx, "Register", "auth/register.html", map[string]any{ "csrf_token": csrf.GetToken(ctx, auth.Manager), "csrf_field": csrf.HiddenField(ctx, auth.Manager), "error_message": "", "username": "", "email": "", }) - - pageData := components.NewPageData("Register - Dragon Knight", registerContent) - if err := components.RenderPage(ctx, pageData, nil); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "Template error: %v", err) - return - } } // processRegister handles registration form submission -func processRegister() router.Handler { - return func(ctx router.Ctx, params []string) { - if !csrf.ValidateFormToken(ctx, auth.Manager) { - ctx.SetStatusCode(fasthttp.StatusForbidden) - ctx.WriteString("CSRF validation failed") - return - } - - username := strings.TrimSpace(string(ctx.PostArgs().Peek("username"))) - email := strings.TrimSpace(string(ctx.PostArgs().Peek("email"))) - userPassword := string(ctx.PostArgs().Peek("password")) - confirmPassword := string(ctx.PostArgs().Peek("confirm_password")) - - if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil { - showRegisterError(ctx, err.Error(), username, email) - return - } - - if _, err := users.GetByUsername(username); err == nil { - showRegisterError(ctx, "Username already exists", username, email) - return - } - - if _, err := users.GetByEmail(email); err == nil { - showRegisterError(ctx, "Email already registered", username, email) - return - } - - user := users.New() - user.Username = username - user.Email = email - user.Password = password.Hash(userPassword) - user.ClassID = 1 - user.Auth = 1 - - if err := user.Insert(); err != nil { - showRegisterError(ctx, "Failed to create account", username, email) - return - } - - // Auto-login after registration - middleware.Login(ctx, auth.Manager, user) - - // Transfer CSRF token from cookie to session for authenticated user - if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { - if session := csrf.GetCurrentSession(ctx); session != nil { - csrf.StoreToken(session, cookieToken) - } - } - - ctx.Redirect("/", fasthttp.StatusFound) +func processRegister(ctx router.Ctx, _ []string) { + if !csrf.ValidateFormToken(ctx, auth.Manager) { + ctx.SetStatusCode(fasthttp.StatusForbidden) + ctx.WriteString("CSRF validation failed") + return } + + username := strings.TrimSpace(string(ctx.PostArgs().Peek("username"))) + email := strings.TrimSpace(string(ctx.PostArgs().Peek("email"))) + userPassword := string(ctx.PostArgs().Peek("password")) + confirmPassword := string(ctx.PostArgs().Peek("confirm_password")) + + if err := validateRegistration(username, email, userPassword, confirmPassword); err != nil { + showRegisterError(ctx, err.Error(), username, email) + return + } + + if _, err := users.GetByUsername(username); err == nil { + showRegisterError(ctx, "Username already exists", username, email) + return + } + + if _, err := users.GetByEmail(email); err == nil { + showRegisterError(ctx, "Email already registered", username, email) + return + } + + user := users.New() + user.Username = username + user.Email = email + user.Password = password.Hash(userPassword) + user.ClassID = 1 + user.Auth = 1 + + if err := user.Insert(); err != nil { + showRegisterError(ctx, "Failed to create account", username, email) + return + } + + // Auto-login after registration + middleware.Login(ctx, auth.Manager, user) + + // Transfer CSRF token from cookie to session for authenticated user + if cookieToken := csrf.GetTokenFromCookie(ctx); cookieToken != "" { + if session := csrf.GetCurrentSession(ctx); session != nil { + csrf.StoreToken(session, cookieToken) + } + } + + ctx.Redirect("/", fasthttp.StatusFound) } // processLogout handles logout -func processLogout() router.Handler { - return func(ctx router.Ctx, params []string) { - // Validate CSRF token - if !csrf.ValidateFormToken(ctx, auth.Manager) { - ctx.SetStatusCode(fasthttp.StatusForbidden) - ctx.WriteString("CSRF validation failed") - return - } - - middleware.Logout(ctx, auth.Manager) - ctx.Redirect("/", fasthttp.StatusFound) +func processLogout(ctx router.Ctx, params []string) { + // Validate CSRF token + if !csrf.ValidateFormToken(ctx, auth.Manager) { + ctx.SetStatusCode(fasthttp.StatusForbidden) + ctx.WriteString("CSRF validation failed") + return } + + middleware.Logout(ctx, auth.Manager) + ctx.Redirect("/", fasthttp.StatusFound) } // Helper functions @@ -279,23 +242,3 @@ func validateRegistration(username, email, password, confirmPassword string) err } return nil } - -// createUser inserts a new user into the database -// This is a simplified version - in a real app you'd have a proper users.Create function -func createUser(user *users.User) error { - query := `INSERT INTO users (username, password, email, verified, auth) VALUES (?, ?, ?, ?, ?)` - - err := database.Exec(query, user.Username, user.Password, user.Email, user.Verified, user.Auth) - if err != nil { - return fmt.Errorf("failed to insert user: %w", err) - } - - // Get the user ID (simplified - in real app you'd return it from insert) - createdUser, err := users.GetByUsername(user.Username) - if err != nil { - return fmt.Errorf("failed to get created user: %w", err) - } - - user.ID = createdUser.ID - return nil -} diff --git a/templates/auth/login.html b/templates/auth/login.html index 224ecc8..6910e7e 100644 --- a/templates/auth/login.html +++ b/templates/auth/login.html @@ -1,28 +1,30 @@ +
+ Want to play? You gotta register your own character. +
+ ++ You may also change your password, or + request a new one if you've lost yours. +
\ No newline at end of file diff --git a/templates/auth/register.html b/templates/auth/register.html index f66d963..8d5b9fa 100644 --- a/templates/auth/register.html +++ b/templates/auth/register.html @@ -1,44 +1,46 @@ +