diff --git a/internal/features/auth/handler.go b/internal/features/auth/handler.go index 2507719..9d20f28 100644 --- a/internal/features/auth/handler.go +++ b/internal/features/auth/handler.go @@ -3,9 +3,7 @@ package auth import ( "errors" "net/http" - "time" - "mal/internal/database" "mal/internal/templates" ) @@ -15,38 +13,6 @@ type Handler struct { const rateLimitFormError = "Too many attempts in a short time. Please wait a minute and try again." -const ( - accountPasswordChangedMessage = "Password updated successfully." - accountRecoveryKeyRotatedMessage = "Recovery key rotated. Save this new key now." - accountPasswordErrorMessage = "Unable to update password with those details." - accountRecoveryErrorMessage = "Unable to rotate recovery key with those details." - accountUnexpectedErrorMessage = "Something went wrong. Please try again." - accountMissingFieldsErrorMessage = "Please complete all required fields." - accountPasswordMismatchErrorMessage = "New password and confirm password must match." -) - -func (h *Handler) accountUserFromRequest(r *http.Request) (*database.User, bool) { - cookie, err := r.Cookie("session_id") - if err != nil { - return nil, false - } - - user, err := h.authService.ValidateSession(r.Context(), cookie.Value) - if err != nil { - return nil, false - } - - return user, true -} - -func accountCreatedAt(createdAt time.Time) string { - return createdAt.Local().Format("Jan 2, 2006 at 15:04") -} - -func renderAccountPage(w http.ResponseWriter, r *http.Request, user *database.User, passwordError string, passwordSuccess string, recoveryError string, recoverySuccess string, recoveryKey string) { - templates.Account(user.Username, accountCreatedAt(user.CreatedAt), passwordError, passwordSuccess, recoveryError, recoverySuccess, recoveryKey).Render(r.Context(), w) -} - func NewHandler(authService *Service) *Handler { return &Handler{authService: authService} } @@ -86,41 +52,6 @@ func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } -func (h *Handler) HandleRegister(w http.ResponseWriter, r *http.Request) { - if err := r.ParseForm(); err != nil { - templates.Register("Something went wrong. Please try again.", "").Render(r.Context(), w) - return - } - - username := r.FormValue("username") - password := r.FormValue("password") - - if username == "" || password == "" { - templates.Register("Please enter both email and password.", username).Render(r.Context(), w) - return - } - - _, recoveryKey, err := h.authService.RegisterUser(r.Context(), username, password) - if err != nil { - if errors.Is(err, ErrInvalidPassword) || errors.Is(err, ErrUserExists) { - templates.Register("Unable to create account with those details.", username).Render(r.Context(), w) - return - } - templates.Register("Something went wrong. Please try again.", username).Render(r.Context(), w) - return - } - - // Auto-login after successful registration - session, err := h.authService.Login(r.Context(), username, password) - if err != nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - SetSessionCookie(w, session.ID, session.ExpiresAt) - templates.RegistrationRecoveryKey(recoveryKey).Render(r.Context(), w) -} - func (h *Handler) HandleLogout(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session_id") if err == nil { @@ -136,10 +67,6 @@ func (h *Handler) HandleLoginPage(w http.ResponseWriter, r *http.Request) { templates.Login(rateLimitErrorFromQuery(r), "").Render(r.Context(), w) } -func (h *Handler) HandleRegisterPage(w http.ResponseWriter, r *http.Request) { - templates.Register(rateLimitErrorFromQuery(r), "").Render(r.Context(), w) -} - func (h *Handler) HandleRecoverPage(w http.ResponseWriter, r *http.Request) { templates.Recover(rateLimitErrorFromQuery(r), "", "").Render(r.Context(), w) } @@ -171,98 +98,3 @@ func (h *Handler) HandleRecover(w http.ResponseWriter, r *http.Request) { templates.RecoveryComplete(newRecoveryKey).Render(r.Context(), w) } - -func (h *Handler) HandleAccountPage(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - user, ok := h.accountUserFromRequest(r) - if !ok { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - renderAccountPage(w, r, user, "", "", "", "", "") -} - -func (h *Handler) HandleAccountPassword(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - user, ok := h.accountUserFromRequest(r) - if !ok { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - if err := r.ParseForm(); err != nil { - renderAccountPage(w, r, user, accountUnexpectedErrorMessage, "", "", "", "") - return - } - - currentPassword := r.FormValue("current_password") - newPassword := r.FormValue("new_password") - confirmNewPassword := r.FormValue("confirm_new_password") - - if currentPassword == "" || newPassword == "" || confirmNewPassword == "" { - renderAccountPage(w, r, user, accountMissingFieldsErrorMessage, "", "", "", "") - return - } - - if newPassword != confirmNewPassword { - renderAccountPage(w, r, user, accountPasswordMismatchErrorMessage, "", "", "", "") - return - } - - err := h.authService.ChangePassword(r.Context(), user.ID, currentPassword, newPassword) - if err != nil { - if errors.Is(err, ErrInvalidCredentials) || errors.Is(err, ErrInvalidPassword) { - renderAccountPage(w, r, user, accountPasswordErrorMessage, "", "", "", "") - return - } - renderAccountPage(w, r, user, accountUnexpectedErrorMessage, "", "", "", "") - return - } - - renderAccountPage(w, r, user, "", accountPasswordChangedMessage, "", "", "") -} - -func (h *Handler) HandleAccountRecoveryKey(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - user, ok := h.accountUserFromRequest(r) - if !ok { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - if err := r.ParseForm(); err != nil { - renderAccountPage(w, r, user, "", "", accountUnexpectedErrorMessage, "", "") - return - } - - password := r.FormValue("password") - if password == "" { - renderAccountPage(w, r, user, "", "", accountMissingFieldsErrorMessage, "", "") - return - } - - newRecoveryKey, err := h.authService.RegenerateRecoveryKey(r.Context(), user.ID, password) - if err != nil { - if errors.Is(err, ErrInvalidCredentials) { - renderAccountPage(w, r, user, "", "", accountRecoveryErrorMessage, "", "") - return - } - renderAccountPage(w, r, user, "", "", accountUnexpectedErrorMessage, "", "") - return - } - - renderAccountPage(w, r, user, "", "", "", accountRecoveryKeyRotatedMessage, newRecoveryKey) -} diff --git a/internal/server/routes.go b/internal/server/routes.go index 0c4f971..2775750 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -68,13 +68,6 @@ func NewRouter(cfg Config) http.Handler { middleware.RateLimitAuth(middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleLogin))).ServeHTTP(w, r) } }) - mux.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - authHandler.HandleRegisterPage(w, r) - } else { - middleware.RateLimitAuth(middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleRegister))).ServeHTTP(w, r) - } - }) mux.HandleFunc("/recover", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { authHandler.HandleRecoverPage(w, r) @@ -85,13 +78,6 @@ func NewRouter(cfg Config) http.Handler { mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleLogout)).ServeHTTP(w, r) }) - mux.HandleFunc("/account", authHandler.HandleAccountPage) - mux.HandleFunc("/account/password", func(w http.ResponseWriter, r *http.Request) { - middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleAccountPassword)).ServeHTTP(w, r) - }) - mux.HandleFunc("/account/recovery-key", func(w http.ResponseWriter, r *http.Request) { - middleware.VerifyOrigin(http.HandlerFunc(authHandler.HandleAccountRecoveryKey)).ServeHTTP(w, r) - }) // Watchlist Endpoints mux.HandleFunc("/api/watchlist/export", watchlistHandler.HandleExportWatchlist) diff --git a/internal/shared/middleware/auth.go b/internal/shared/middleware/auth.go index de7b8b3..0fc6f6f 100644 --- a/internal/shared/middleware/auth.go +++ b/internal/shared/middleware/auth.go @@ -59,8 +59,8 @@ func RequireAuth(next http.Handler) http.Handler { func RequireGlobalAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Allow unauthenticated access to login, register, search, and static files - if r.URL.Path == "/login" || r.URL.Path == "/register" || r.URL.Path == "/recover" || + // Allow unauthenticated access to auth pages, search, and static files + if r.URL.Path == "/login" || r.URL.Path == "/recover" || strings.HasPrefix(r.URL.Path, "/static/") || strings.HasPrefix(r.URL.Path, "/dist/") || r.URL.Path == "/search" || r.URL.Path == "/api/search" || r.URL.Path == "/api/search-quick" || r.URL.Path == "/" { diff --git a/internal/templates/auth.templ b/internal/templates/auth.templ index 66aff3f..377da15 100644 --- a/internal/templates/auth.templ +++ b/internal/templates/auth.templ @@ -20,9 +20,6 @@ templ Login(formError string, username string) { } -

- Don't have an account? Register -

Lost access? Recover account

@@ -31,56 +28,6 @@ templ Login(formError string, username string) { } } -templ Register(formError string, username string) { - @Layout("Register", false) { -
-
-

Register

-

Create a new account to track anime.

-
-
- - -
-
- - -
-

- Password must be at least 12 characters and include an uppercase letter, lowercase letter, number, and special character. -

- - if formError != "" { - - } -
-

- Already have an account? Sign in -

-
-
- } -} - -templ RegistrationRecoveryKey(recoveryKey string) { - @Layout("Save recovery key", false) { -
-
-

Save your recovery key

-

Store this key somewhere safe. It is shown only once.

-
-

{ recoveryKey }

- -
-

-

If you lose your password, this key is the only way to recover your account without email.

-

- I saved it, continue -

-
-
- } -} templ Recover(formError string, username string, recoveryKey string) { @Layout("Recover account", false) { @@ -133,80 +80,3 @@ templ RecoveryComplete(newRecoveryKey string) { } } - -templ Account(username string, createdAt string, passwordError string, passwordSuccess string, recoveryError string, recoverySuccess string, recoveryKey string) { - @Layout("mal - account", true) { -
-
-

Account

-
-
- Email / Username - { username } -
-
- Created - { createdAt } -
-
-
- -
-

Change password

-
-
- - -
-
- - -
-
- - -
- - if passwordError != "" { - - } - if passwordSuccess != "" { -

{ passwordSuccess }

- } -
-
- -
-

Recovery key

-

To view a new recovery key, confirm your current password. This rotates your old key.

-
-
- - -
- - if recoveryError != "" { - - } - if recoverySuccess != "" { -

{ recoverySuccess }

- } -
- if recoveryKey != "" { -
-

{ recoveryKey }

- -
-

- } -
- -
-

Danger zone

-
- -
-
-
- } -} diff --git a/internal/templates/layout.templ b/internal/templates/layout.templ index 7af871f..1ac0ea1 100644 --- a/internal/templates/layout.templ +++ b/internal/templates/layout.templ @@ -31,7 +31,6 @@ templ Layout(title string, showHeader bool) { Discover Notifications Watchlist - Account