diff --git a/go.mod b/go.mod index 922014c..3649357 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,11 @@ go 1.22.5 require ( github.com/a-h/templ v0.2.771 github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/google/uuid v1.4.0 github.com/joho/godotenv v1.5.1 github.com/mattn/go-sqlite3 v1.14.22 github.com/prometheus/client_golang v1.20.2 + golang.org/x/crypto v0.20.0 ) require ( diff --git a/go.sum b/go.sum index 859afb9..a22ef81 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMn github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -41,6 +43,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/handler.go b/handler.go index ae4420b..31f9271 100644 --- a/handler.go +++ b/handler.go @@ -24,6 +24,7 @@ func getHandler(db *sql.DB) http.Handler { router.HandleFunc("/auth/signin", service.SignInPage) router.HandleFunc("/auth/signup", service.SignUpPage) router.HandleFunc("/api/auth/signup", service.SignUp(db)) + router.HandleFunc("/api/auth/userinfo", service.UserInfoComp(db)) return middleware.Logging(middleware.EnableCors(router)) } diff --git a/migrations/001_initial_schema.up.sql b/migrations/001_initial_schema.up.sql index 9e3e5c9..df7bae9 100644 --- a/migrations/001_initial_schema.up.sql +++ b/migrations/001_initial_schema.up.sql @@ -6,3 +6,4 @@ CREATE TABLE workout ( sets INTEGER NOT NULL, reps INTEGER NOT NULL ); + diff --git a/migrations/002_user_and_session.up.sql b/migrations/002_user_and_session.up.sql new file mode 100644 index 0000000..98aa0cf --- /dev/null +++ b/migrations/002_user_and_session.up.sql @@ -0,0 +1,21 @@ + +CREATE TABLE user ( + user_uuid TEXT NOT NULL UNIQUE PRIMARY KEY, + + email TEXT NOT NULL UNIQUE, + email_verified BOOLEAN NOT NULL, + + is_admin BOOLEAN NOT NULL, + + password BLOB NOT NULL, + salt BLOB NOT NULL, + + created_at DATETIME NOT NULL +) WITHOUT ROWID; + +CREATE TABLE session ( + session_id TEXT NOT NULL UNIQUE PRIMARY KEY, + user_uuid TEXT NOT NULL, + + created_at DATETIME NOT NULL +) WITHOUT ROWID; diff --git a/service/auth.go b/service/auth.go index db6714a..6670836 100644 --- a/service/auth.go +++ b/service/auth.go @@ -3,6 +3,7 @@ package service import ( "crypto/rand" "database/sql" + "encoding/base64" "log" "net/http" "net/mail" @@ -25,57 +26,14 @@ func SignUpPage(w http.ResponseWriter, r *http.Request) { template.Layout(signIn).Render(r.Context(), w) } -func SignUp(db *sql.DB) http.HandlerFunc { +func UserInfoComp(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - + //TODO } } -// 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 { +func SignUp(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var email = r.FormValue("email") var password = r.FormValue("password") @@ -104,12 +62,12 @@ func PostSignup(db *sql.DB) http.HandlerFunc { salt := make([]byte, 16) rand.Read(salt) - hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16) + hash := getHashPassword(password, salt) _, 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 { if strings.Contains(err.Error(), "email") { - http.Error(w, "Email already exists", http.StatusBadRequest) + http.Error(w, "Bad Request", http.StatusBadRequest) return } @@ -118,7 +76,71 @@ func PostSignup(db *sql.DB) http.HandlerFunc { return } + result := tryCreateSessionAndSetCookie(r, w, db, user_uuid) + if !result { + return + } + w.WriteHeader(http.StatusOK) - // w.Write([]byte(token)) } } + +// 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"}, +// // ) +// ) + +func tryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sql.DB, user_uuid uuid.UUID) bool { + var session_id_bytes []byte = make([]byte, 32) + _, err := rand.Reader.Read(session_id_bytes) + if err != nil { + log.Printf("Could not generate session ID: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return false + } + session_id := base64.StdEncoding.EncodeToString(session_id_bytes) + + _, err = db.Exec("INSERT INTO session (session_id, user_uuid, created_at) VALUES (?, ?, datetime())", session_id, user_uuid) + if err != nil { + log.Printf("Could not insert session: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return false + } + + cookie := http.Cookie{ + Name: "id", + Value: session_id, + MaxAge: 60 * 60 * 8, // 8 hours + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteStrictMode, + Path: "/", + } + http.SetCookie(w, &cookie) + + return true +} + +func getHashPassword(password string, salt []byte) []byte { + return argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16) +} diff --git a/utils/auth.go b/utils/auth.go deleted file mode 100644 index d4b585b..0000000 --- a/utils/auth.go +++ /dev/null @@ -1 +0,0 @@ -package utils