From f469d20e06cea1174993d3ed8c9ca59526990e24 Mon Sep 17 00:00:00 2001 From: grumbulon Date: Fri, 30 Dec 2022 23:15:09 -0500 Subject: [PATCH] move files around, make sure cookies work, and add logout --- internal/api/api.go | 92 +------------------------------------------ internal/api/auth.go | 78 ++++++++++++++++++++++++++++++++++++ internal/api/users.go | 59 +++++++++++++++++++++++++++ internal/zone.go | 1 + 4 files changed, 140 insertions(+), 90 deletions(-) create mode 100644 internal/api/auth.go create mode 100644 internal/api/users.go diff --git a/internal/api/api.go b/internal/api/api.go index de0287b..1570dc6 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -2,10 +2,8 @@ package api import ( "context" - "encoding/json" "fmt" "log" - "math/rand" "net/http" "time" @@ -14,8 +12,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "github.com/go-chi/render" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" ) func SetDBMiddleware(next http.Handler) http.Handler { @@ -47,10 +43,12 @@ func Api() http.Handler { }) + // Open routes api.Group(func(api chi.Router) { api.Use(SetDBMiddleware) api.With(SetDBMiddleware).Post("/create", NewUser) api.With(SetDBMiddleware).Post("/login", Login) + api.Post("/logout", Logout) }) return api @@ -72,92 +70,6 @@ func Ingest(w http.ResponseWriter, r *http.Request) { // todo -- add functions to apply to master zonefile if above check is OK } -func NewUser(w http.ResponseWriter, r *http.Request) { - db, ok := r.Context().Value("DB").(*gorm.DB) - if !ok { - http.Error(w, "internal server error", http.StatusInternalServerError) - } - - var result internal.User - - r.ParseForm() - username := r.Form.Get("username") - if username == "" { - username = autoUname() - } - password := r.Form.Get("password") - if password == "" { - http.Error(w, "No password entered", http.StatusInternalServerError) - } - db.Where("username = ?", username).First(&result) - - if result.Username != "" { - http.Error(w, "User already exists", http.StatusInternalServerError) - return - } - - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - db.Create(&internal.User{Username: username, HashedPassword: string(hashedPassword)}) - data := internal.Response{ - Username: username, - HTTPResponse: http.StatusCreated, - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(data) -} - -func Login(w http.ResponseWriter, r *http.Request) { - var result internal.User - r.ParseForm() - username := r.Form.Get("username") - if username == "" { - username = autoUname() - } - password := r.Form.Get("password") - if password == "" { - http.Error(w, "No password provided", http.StatusInternalServerError) // this should prob be handled by the frontend - } - - db, ok := r.Context().Value("DB").(*gorm.DB) - if !ok { - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - - db.Model(internal.User{Username: username}).First(&result) - err := bcrypt.CompareHashAndPassword([]byte(result.HashedPassword), []byte(password)) - - if err != nil { - basicAuthFailed(w, "user") - return - } - w.WriteHeader(http.StatusAccepted) - token := makeToken(username) - - http.SetCookie(w, &http.Cookie{ - HttpOnly: true, - Expires: time.Now().Add(7 * 24 * time.Hour), - SameSite: http.SameSiteLaxMode, - // Uncomment below for HTTPS: - // Secure: true, - Name: "jwt", // Must be named "jwt" or else the token cannot be searched for by jwtauth.Verifier. - Value: token, - }) - - http.Redirect(w, r, "/", http.StatusSeeOther) - -} - -func autoUname() string { - rand.Seed(time.Now().UnixNano()) - return fmt.Sprintf("user%d", rand.Intn(99999-00000)) -} - func AuthTest(w http.ResponseWriter, r *http.Request) { _, claims, _ := jwtauth.FromContext(r.Context()) w.Write([]byte(fmt.Sprintf("protected area. hi %v", claims["user_id"]))) diff --git a/internal/api/auth.go b/internal/api/auth.go new file mode 100644 index 0000000..046801b --- /dev/null +++ b/internal/api/auth.go @@ -0,0 +1,78 @@ +package api + +import ( + "encoding/json" + "net/http" + "time" + + "git.freecumextremist.com/grumbulon/pomme/internal" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +func Login(w http.ResponseWriter, r *http.Request) { + var result internal.User + r.ParseForm() + username := r.Form.Get("username") + if username == "" { + username = autoUname() + } + password := r.Form.Get("password") + if password == "" { + http.Error(w, "No password provided", http.StatusInternalServerError) // this should prob be handled by the frontend + } + + db, ok := r.Context().Value("DB").(*gorm.DB) + if !ok { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + db.Model(internal.User{Username: username}).First(&result) + err := bcrypt.CompareHashAndPassword([]byte(result.HashedPassword), []byte(password)) + + if err != nil { + basicAuthFailed(w, "user") + return + } + + token := makeToken(username) + + http.SetCookie(w, &http.Cookie{ + HttpOnly: true, + Expires: time.Now().Add(7 * 24 * time.Hour), + SameSite: http.SameSiteLaxMode, + // Uncomment below for HTTPS: + // Secure: true, + Name: "jwt", // Must be named "jwt" or else the token cannot be searched for by jwtauth.Verifier. + Value: token, + }) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode( + internal.Response{ + Message: "Successfully logged in", + HTTPResponse: 200, + Username: token, + }) + http.Redirect(w, r, "/", http.StatusSeeOther) + +} + +func Logout(w http.ResponseWriter, r *http.Request) { + + http.SetCookie(w, &http.Cookie{ + HttpOnly: true, + MaxAge: -1, // Delete the cookie. + SameSite: http.SameSiteLaxMode, + // Secure: true, + Name: "jwt", + Value: "", + }) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode( + internal.Response{ + Message: "Successfully logged out", + HTTPResponse: 200, + }) + http.Redirect(w, r, "/", http.StatusSeeOther) +} diff --git a/internal/api/users.go b/internal/api/users.go new file mode 100644 index 0000000..98e16e3 --- /dev/null +++ b/internal/api/users.go @@ -0,0 +1,59 @@ +package api + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "time" + + "git.freecumextremist.com/grumbulon/pomme/internal" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +func NewUser(w http.ResponseWriter, r *http.Request) { + db, ok := r.Context().Value("DB").(*gorm.DB) + if !ok { + http.Error(w, "internal server error", http.StatusInternalServerError) + } + + var result internal.User + + r.ParseForm() + username := r.Form.Get("username") + if username == "" { + username = autoUname() + } + password := r.Form.Get("password") + if password == "" { + http.Error(w, "No password entered", http.StatusInternalServerError) + } + + db.Where("username = ?", username).First(&result) + + if result.Username != "" { + http.Error(w, "User already exists", http.StatusInternalServerError) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + db.Create(&internal.User{Username: username, HashedPassword: string(hashedPassword)}) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode( + internal.Response{ + Username: username, + HTTPResponse: http.StatusCreated, + }) +} + +func autoUname() string { + rand.Seed(time.Now().UnixNano()) + return fmt.Sprintf("user%d", rand.Intn(99999-00000)) +} diff --git a/internal/zone.go b/internal/zone.go index b4b542e..612034d 100644 --- a/internal/zone.go +++ b/internal/zone.go @@ -39,6 +39,7 @@ type User struct { type Response struct { Username string `json:"username"` + Message string `json:"message"` HTTPResponse int `json:"status"` }