#73 implement PostSignup serverside
Some checks failed
Build Docker Image / Explore-Gitea-Actions (push) Failing after 1m0s

This commit is contained in:
Tim
2024-08-11 18:31:40 +02:00
parent 0df0e7f6f9
commit 21d39ff606
6 changed files with 117 additions and 19 deletions

View File

@@ -1,25 +1,95 @@
package controller
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"database/sql"
"encoding/base64"
"log"
"net/http"
"net/mail"
"os"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"golang.org/x/crypto/argon2"
)
var (
metricsAuthSignUp = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "mefit_api_auth_signup_total",
Help: "The total number of auth signup api requests processed",
},
[]string{"result"},
)
metricsError = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "mefit_api_error_total",
Help: "The total number of errors",
},
[]string{"result"},
)
// metricsAuthSignIn = promauto.NewCounterVec(
// prometheus.CounterOpts{
// Name: "mefit_api_auth_signin_total",
// },
// []string{"result"},
// )
privateKey = func() *ecdsa.PrivateKey {
keyBase64 := os.Getenv("PRIVATE_KEY")
if keyBase64 == "" {
log.Fatal("PRIVATE_KEY not defined")
}
keyData, err := base64.StdEncoding.DecodeString(keyBase64)
if err != nil {
log.Fatalf("Could not decode private key: %v", err)
}
key, err := x509.ParseECPrivateKey(keyData)
if err != nil {
log.Fatalf("Could not parse private key: %v", err)
}
log.Println("Successfully imported private key")
return key
}()
)
func PostSignup(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// metrics.WithLabelValues("new").Inc()
var email = r.FormValue("email")
var password = r.FormValue("password")
if email == "" || password == "" {
http.Error(w, "Missing required fields", http.StatusBadRequest)
_, err := mail.ParseAddress(email)
if err != nil {
metricsAuthSignUp.WithLabelValues("email").Inc()
http.Error(w, "Invalid email", http.StatusBadRequest)
return
}
if len(password) < 8 ||
!strings.ContainsAny(password, "0123456789") ||
!strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") ||
!strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") ||
!strings.ContainsAny(password, "!@#$%^&*()_+-=[]{}\\|;:'\",.<>/?") {
metricsAuthSignUp.WithLabelValues("password").Inc()
http.Error(w, "Password needs to be 8 characters long, contain at least one number, one special, one uppercase and one lowercase character", http.StatusBadRequest)
return
}
user_uuid, err := uuid.NewRandom()
if err != nil {
metricsError.WithLabelValues("signup_uuid").Inc()
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("Could not generate UUID: %v", err)
return
}
@@ -27,23 +97,46 @@ func PostSignup(db *sql.DB) http.HandlerFunc {
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16)
hashStr := base64.StdEncoding.EncodeToString(hash)
user_uuid, err := uuid.NewRandom()
_, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", user_uuid, email, hash, salt)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("Could not generate UUID: %v", err)
return
}
if strings.Contains(err.Error(), "email") {
metricsAuthSignUp.WithLabelValues("email-dup").Inc()
http.Error(w, "Email already exists", http.StatusBadRequest)
return
}
_, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, CURRENT_DATE)", user_uuid, email, hash, salt)
if err != nil {
metricsError.WithLabelValues("signup_sql").Inc()
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Printf("Could not insert user: %v", err)
return
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(hashStr))
token, err := generateJwt(email, user_uuid.String())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
metricsError.WithLabelValues("signup_jwt").Inc()
log.Printf("Could not generate JWT: %v", err)
return
}
metricsAuthSignUp.WithLabelValues("success").Inc()
w.WriteHeader(http.StatusOK)
w.Write([]byte(token))
}
}
func generateJwt(user_email string, user_id string) (string, error) {
var now = time.Now()
t := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"iss": "mefit",
"iat": now.Unix(),
"exp": now.Add(time.Hour * 24 * 7).Unix(),
"sub": user_email,
"user_id": user_id,
})
return t.SignedString(privateKey)
}

View File

@@ -13,7 +13,7 @@ import (
)
var (
metrics = promauto.NewCounterVec(
metricsWorkout = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "mefit_api_workout_total",
Help: "The total number of workout api requests processed",
@@ -24,7 +24,7 @@ var (
func NewWorkout(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
metrics.WithLabelValues("new").Inc()
metricsWorkout.WithLabelValues("new").Inc()
var dateStr = r.FormValue("date")
var typeStr = r.FormValue("type")
@@ -67,7 +67,7 @@ func NewWorkout(db *sql.DB) http.HandlerFunc {
func GetWorkouts(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
metrics.WithLabelValues("get").Inc()
metricsWorkout.WithLabelValues("get").Inc()
// token := r.Context().Value(middleware.TOKEN_KEY).(*auth.Token)
// var userId = token.UID
@@ -109,7 +109,7 @@ func GetWorkouts(db *sql.DB) http.HandlerFunc {
func DeleteWorkout(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
metrics.WithLabelValues("delete").Inc()
metricsWorkout.WithLabelValues("delete").Inc()
// token := r.Context().Value(middleware.TOKEN_KEY).(*auth.Token)
// var userId = token.UID

View File

@@ -3,6 +3,7 @@ module api
go 1.22.5
require (
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-migrate/migrate/v4 v4.17.1
github.com/google/uuid v1.4.0
github.com/mattn/go-sqlite3 v1.14.22

View File

@@ -4,6 +4,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=

View File

@@ -2,7 +2,7 @@
CREATE TABLE user (
user_uuid TEXT NOT NULL UNIQUE PRIMARY KEY,
email TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
email_verified BOOLEAN NOT NULL,
is_admin BOOLEAN NOT NULL,
@@ -10,5 +10,5 @@ CREATE TABLE user (
password BLOB NOT NULL,
salt BLOB NOT NULL,
created_at DATETIME NOT NULL,
created_at DATETIME NOT NULL
) WITHOUT ROWID;

View File

@@ -29,4 +29,6 @@ func RunMigrations(db *sql.DB) {
log.Fatal("Could not run migrations: ", err)
}
}
log.Println("Migrations ran successfully")
}