ducky-dash/handler/auth.go

261 lines
7.6 KiB
Go

package handler
import (
// "fmt"
"encoding/json"
"fmt"
"log/slog"
"net/http"
// "time"
"git.duckylabs.xyz/duckbox/ducky-dash/db"
"git.duckylabs.xyz/duckbox/ducky-dash/models"
"git.duckylabs.xyz/duckbox/ducky-dash/pkg/kit/validate"
// "git.duckylabs.xyz/duckbox/ducky-dash/view"
// "git.duckylabs.xyz/duckbox/ducky-dash/pkg/logger"
"git.duckylabs.xyz/duckbox/ducky-dash/pkg/sb"
// "git.duckylabs.xyz/duckbox/ducky-dash/pkg/util"
"git.duckylabs.xyz/duckbox/ducky-dash/view/auth"
"git.duckylabs.xyz/duckbox/ducky-dash/view/settings"
"git.duckylabs.xyz/duckbox/ducky-dash/view/ui"
"github.com/nedpals/supabase-go"
)
func HandleLoginIndex(w http.ResponseWriter, r *http.Request) error {
return render(r, w, auth.Login())
}
func HandleSignupIndex(w http.ResponseWriter, r *http.Request) error {
return render(r, w, auth.Signup())
}
func HandleResetPasswordIndex(w http.ResponseWriter, r *http.Request) error {
return render(r, w, auth.ResetPassword())
}
func HandleAuthCallback(w http.ResponseWriter, r *http.Request) error {
accessToken := r.URL.Query().Get("access_token")
if len(accessToken) == 0 {
return render(r, w, auth.CallbackScript())
}
setAuthCookie(w, accessToken)
http.Redirect(w, r, "/", http.StatusSeeOther)
return nil
}
func setAuthCookie(w http.ResponseWriter, accessToken string) {
cookie := &http.Cookie{
Value: accessToken,
Name: "at",
Path: "/",
HttpOnly: true,
Secure: false,
}
http.SetCookie(w, cookie)
}
func HandleLoginCreate(w http.ResponseWriter, r *http.Request) error {
credentials := supabase.UserCredentials{
Email: r.FormValue("email"),
Password: r.FormValue("password"),
}
resp, err := sb.Client.Auth.SignIn(r.Context(), credentials)
if err != nil {
slog.Error("login authentication error", "err", err, "user", credentials.Email)
return render(r, w, auth.SignInForm(credentials, auth.LoginErrors{
InvalidCredentials: "The credentials you have entered are invalid",
}))
}
setAuthCookie(w, resp.AccessToken)
fmt.Println("user logged in")
slog.Info("login authentication successful", "user", credentials.Email)
// TEST: delay for loading spinner
// time.Sleep(1 * time.Second)
return hxRedirect(w, r, "/")
}
func HandleLogoutCreate(w http.ResponseWriter, r *http.Request) error {
cookie := http.Cookie{
Value: "",
Name: "at",
MaxAge: -1,
Path: "/",
HttpOnly: true,
Secure: false,
}
http.SetCookie(w, &cookie)
http.Redirect(w, r, "/login", http.StatusSeeOther)
slog.Info("authenticated user logged out")
return nil
}
func HandleSignupCreate(w http.ResponseWriter, r *http.Request) error {
params := auth.SignupParams{
Email: r.FormValue("email"),
Password: r.FormValue("password"),
ConfirmPassword: r.FormValue("confirmPassword"),
}
errors := auth.SignupErrors{}
if ok := validate.New(&params, validate.Fields{
"Email": validate.Rules(validate.Email),
"Password": validate.Rules(validate.Password),
"ConfirmPassword": validate.Rules(
validate.Equal(params.Password),
validate.Message("Passwords do not match"),
),
}).Validate(&errors); !ok {
slog.Error("error validating signup form", "err", errors, "user", params.Email)
return render(r, w, auth.SignUpForm(params, errors))
}
user, err := sb.Client.Auth.SignUp(r.Context(), supabase.UserCredentials{
Email: params.Email,
Password: params.Password,
})
if err != nil {
slog.Error("new signup error", "err", err, "user", user.Email)
return render(r, w, ui.ToastError("Error signing up on the server"))
}
slog.Info("user sign up successful", "user", params.Email)
return render(r, w, auth.SignupSuccess(user.Email))
}
// Unautheticated users reset password
func HandleResetPasswordCreate(w http.ResponseWriter, r *http.Request) error {
params := auth.ResetPasswordParams{
Email: r.FormValue("email"),
}
errors := auth.ResetPasswordErrors{}
if ok := validate.New(&params, validate.Fields{
"Email": validate.Rules(validate.Email),
}).Validate(&errors); !ok {
slog.Error("error validating reset password form", "err", errors, "user", params.Email)
return render(r, w, auth.ResetPasswordForm(params, errors))
}
slog.Info("user reset password request", "user", params.Email)
err := sb.Client.Auth.ResetPasswordForEmail(r.Context(), params.Email)
if err != nil {
slog.Error("error sending reset password email", "err", err, "user", params.Email)
return err
}
slog.Info("user reset password email sent", "user", params.Email)
return render(r, w, auth.ResetPasswordToast(params))
}
// Authenticated users reset password
func HandleResetPasswordUpdate(w http.ResponseWriter, r *http.Request) error {
user := getAuthenticatedUser(r)
params := map[string]any{
"password": r.FormValue("password"),
}
_, err := sb.Client.Auth.UpdateUser(r.Context(), user.AccessToken, params)
errors := settings.UpdatePasswordErrors{
NewPassword: "Please enter a valid password",
}
if err != nil {
return render(r, w, settings.UpdatePasswordForm(errors))
}
return hxRedirect(w, r, "/")
}
func HandleAccountCreate(w http.ResponseWriter, r *http.Request) error {
user := getAuthenticatedUser(r)
err := r.ParseForm()
if err != nil {
return err
}
params := settings.AccountSetupParams{
Username: r.FormValue("username"),
Bio: r.FormValue("bio"),
Title: r.FormValue("title"),
Location: r.FormValue("location"),
Department: r.FormValue("department"),
Status: r.FormValue("status"),
}
var errors settings.AccountSetupErrors
ok := validate.New(&params, validate.Fields{
"Username": validate.Rules(validate.Min(2), validate.Max(50)),
"Bio": validate.Rules(validate.Max(200)),
}).Validate(&errors)
if !ok {
return render(r, w, settings.AccountSetupForm(params, errors, user))
}
account := models.Account{
UserID: user.ID,
Username: params.Username,
Bio: params.Bio,
Title: params.Title,
Location: params.Location,
Department: params.Department,
Status: params.Status,
}
fmt.Println(account)
if err := db.CreateAccount(&account); err != nil {
slog.Error("error creating account", "err", err, "user", user.Username)
return render(r, w, ui.ToastError("Error setting up account on the server"))
}
return hxRedirect(w, r, "/")
}
func HandleAccountUpdate(w http.ResponseWriter, r *http.Request) error {
user := getAuthenticatedUser(r)
r.ParseForm()
params := settings.SettingsFormParams{
Username: r.FormValue("username"),
Email: r.FormValue("email"),
Department: r.FormValue("department"),
Title: r.FormValue("title"),
Status: r.FormValue("status"),
Location: r.FormValue("location"),
Bio: r.FormValue("bio"),
}
fmt.Println(params.Status)
var errors settings.SettingsFormErrors
// ok := validate.New(&params, validate.Fields{
// "Username": validate.Rules(validate.Min(2), validate.Max(50)),
// }).Validate(&errors)
// if !ok {
// return render(r, w, settings.SettingsForm(params, errors, user))
// }
if err := db.UpdateAccount(&user.Account); err != nil {
slog.Error("failed to update account", "err", err, "user", user.Username)
return err
}
return render(r, w, settings.SettingsForm(params, errors, user))
}
func HandleAccountsQuery(w http.ResponseWriter, r *http.Request) error {
accounts, err := db.GetAllAccounts()
if err != nil {
slog.Error("error querying accounts", "err", err)
return err
}
// TEST: Print accounts to CLI
for _, account := range accounts {
fmt.Println(account.Username)
}
// Load accounts into response header
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
if err := encoder.Encode(accounts); err != nil {
slog.Error("error encoding accounts", "err", err)
return err
}
return err
}