From ff8f3c2f4511ce2d8de37dab9df2a224b3d6be0c Mon Sep 17 00:00:00 2001 From: mykola2312 <49044616+mykola2312@users.noreply.github.com> Date: Sat, 23 Nov 2024 07:57:07 +0200 Subject: [PATCH] login password authorization complete --- go.mod | 1 + go.sum | 2 + main.go | 135 ++++++++++++++++++++++++++++++++++++++-- migrations/000_init.sql | 2 +- templates/login.html | 17 +++++ 5 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 templates/login.html diff --git a/go.mod b/go.mod index 7b870ef..c4a75d3 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.23.2 require ( github.com/mattn/go-sqlite3 v1.14.24 github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 + golang.org/x/crypto v0.29.0 ) diff --git a/go.sum b/go.sum index b6e0fcc..2d8b795 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,5 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 h1:qk1XyC6UGfPa51PGmsTQJavyhfMLScqw97pEV3sFClI= github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4/go.mod h1:X6iKjXCleSyo/LZzKZ9zDF/ZB2L9gC36I5gLMf32w3M= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= diff --git a/main.go b/main.go index 7ba5d71..f2c1c60 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "fmt" "log" "net/http" @@ -8,25 +9,37 @@ import ( "strconv" "text/template" "wargh/db" + + "golang.org/x/crypto/bcrypt" ) +var DB *sql.DB var templates *template.Template +type UserSession struct { + Id int +} + +var sessions map[string]UserSession + func indexHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { - //templates.Lookup("index.html").Execute(w, nil) - redirectError(w, r, ERROR_TEST) + templates.Lookup("index.html").Execute(w, nil) } } const ( - ERROR_UNKNOWN = 0 - ERROR_TEST = 1 + ERROR_UNKNOWN = 0 + ERROR_INVALID_INPUT = 1 + ERROR_INVALID_LOGIN_PASSWORD = 2 + ERROR_UNAUTHORIZED = 3 ) var ERROR_TEXT = []string{ "Unknown error", - "Test error", + "Invalid input", + "Invalid login or password", + "Unathorized", } func errorHandler(w http.ResponseWriter, r *http.Request) { @@ -53,13 +66,121 @@ func redirectError(w http.ResponseWriter, r *http.Request, errorCode int) { http.Redirect(w, r, fmt.Sprintf("/error?error=%d", errorCode), http.StatusSeeOther) } +func isFirstUser() (bool, error) { + row := DB.QueryRow("SELECT COUNT(id) FROM `user`;") + if row.Err() != nil { + return false, row.Err() + } + + var count int + err := row.Scan(&count) + if err != nil { + return false, err + } + + return count == 0, nil +} + +func createUser(login string, password string) (int, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + if err != nil { + return 0, err + } + + _, err = DB.Exec("INSERT INTO `user` (login, password) VALUES (?,?);", login, bytes) + if err != nil { + return 0, err + } + + row := DB.QueryRow("SELECT id FROM `user` WHERE login = ?;", login) + if row.Err() != nil { + return 0, row.Err() + } + + var id int + row.Scan(&id) + + return id, nil +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + templates.Lookup("login.html").Execute(w, nil) + } else if r.Method == "POST" { + err := r.ParseForm() + if err != nil { + log.Print(err) + + redirectError(w, r, ERROR_UNKNOWN) + return + } + + loginParam := r.Form.Get("login") + passwordParam := r.Form.Get("password") + if loginParam == "" || passwordParam == "" { + redirectError(w, r, ERROR_INVALID_INPUT) + return + } + + isFirst, err := isFirstUser() + if err != nil { + log.Print(err) + + redirectError(w, r, ERROR_UNKNOWN) + } + + if isFirst { + id, err := createUser(loginParam, passwordParam) + if err != nil { + log.Print(err) + } else { + log.Printf("created user %d\n", id) + } + + // create session + } else { + // try authorize + row := DB.QueryRow("SELECT id,password FROM `user` WHERE login = ?;", loginParam) + if row.Err() != nil { + if row.Err().Error() == sql.ErrNoRows.Error() { + redirectError(w, r, ERROR_INVALID_LOGIN_PASSWORD) + return + } else { + log.Print(err) + + redirectError(w, r, ERROR_UNKNOWN) + return + } + } + + var id int + password := make([]byte, 8) + + err = row.Scan(&id, &password) + if err != nil { + // can't scan blob? fatal error + log.Fatal(err) + } + + if bcrypt.CompareHashAndPassword(password, []byte(passwordParam)) != nil { + redirectError(w, r, ERROR_INVALID_LOGIN_PASSWORD) + return + } + + // create session + log.Printf("user %d authorized!\n", id) + } + } +} + func main() { db.Init(&db.DBConfig{ DBPath: "wargh.db", MigrationsPath: "migrations", }) - DB, err := db.Open() + var err error + DB, err = db.Open() if err != nil { log.Fatal(err) os.Exit(1) @@ -69,6 +190,7 @@ func main() { templates, err = template.New("templates").ParseFiles( "templates/index.html", "templates/error.html", + "templates/login.html", ) if err != nil { log.Fatal(err) @@ -76,6 +198,7 @@ func main() { http.HandleFunc("/", indexHandler) http.HandleFunc("/error", errorHandler) + http.HandleFunc("/login", loginHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/migrations/000_init.sql b/migrations/000_init.sql index 4554408..48d5707 100644 --- a/migrations/000_init.sql +++ b/migrations/000_init.sql @@ -1,5 +1,5 @@ CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL UNIQUE, - password TEXT NOT NULL + password BLOB NOT NULL ); diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..0071328 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,17 @@ + + +
+