pomme/internal/api/users.go

132 lines
2.7 KiB
Go

package api
import (
"crypto/rand"
"fmt"
"math/big"
"net/http"
"time"
"dns.froth.zone/pomme/internal"
"github.com/go-chi/render"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// NewUser takes a POST request and user form and creates a user in the database.
func NewUser(w http.ResponseWriter, r *http.Request) {
db, ok := r.Context().Value(keyPrincipalContextID).(*gorm.DB)
if !ok {
logger := newResponder(Response[any]{
Message: "internal server error",
Status: http.StatusInternalServerError,
})
logger.apiError(w, r)
return
}
var result internal.User
err := r.ParseForm()
if err != nil {
logger := newResponder(Response[any]{
Message: "unable to parse request",
Status: http.StatusInternalServerError,
Err: err.Error(),
})
logger.apiError(w, r)
logger.writeLogEntry()
return
}
username := r.Form.Get("username")
if username == "" {
username = autoUname()
}
password := r.Form.Get("password")
if password == "" {
logger := newResponder(Response[any]{
Message: "no password provided",
Status: http.StatusInternalServerError,
})
logger.apiError(w, r)
return
}
db.Where("username = ?", username).First(&result)
if result.Username != "" {
logger := newResponder(Response[any]{
Message: "internal server error",
Status: http.StatusInternalServerError,
})
logger.apiError(w, r)
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logger := newResponder(Response[any]{
Message: "login failed",
Status: http.StatusUnauthorized,
Realm: "authentication",
})
logger.apiError(w, r)
return
}
db.Create(&internal.User{Username: username, HashedPassword: string(hashedPassword)})
token, err := makeToken(username)
if err != nil {
logger := newResponder(Response[any]{
Message: "internal server error",
Status: http.StatusInternalServerError,
Err: err.Error(),
})
logger.apiError(w, r)
return
}
http.SetCookie(w, &http.Cookie{
HttpOnly: true,
Expires: time.Now().Add(1 * time.Hour),
MaxAge: 3600,
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; charset=utf-8")
w.WriteHeader(http.StatusCreated)
resp := internal.Response{
Message: "Successfully created account and logged in",
}
render.JSON(w, r, resp)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func autoUname() string {
n, err := rand.Int(rand.Reader, big.NewInt(1000))
if err != nil {
return ""
}
return fmt.Sprintf("user%d", n.Int64())
}