5
\$\begingroup\$

I still write login system in Go(Golang) using cookies.But my system is still not secure enough.Can you review my code and provide some suggestions on how to improve the security?Previous question.

Main file:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "golang.org/x/crypto/bcrypt"
    "html/template"
    "log"
    "net/http"
    "strings"
    "time"
)

var (
    t *template.Template
)

func init()  {
    t, _ = template.ParseFiles("main.html","signup.html","signin.html")
}

type User struct {
    Login, Email string
}

func genToken(n int) string {
    b := make([]byte, n)
    if _, err := rand.Read(b);err != nil{
        log.Fatal(err)
    }
    return base64.URLEncoding.EncodeToString(b)
}


func setCookie(w http.ResponseWriter, name, value string,d int) {
    cookie := http.Cookie{
        Name:    name,
        Value:   value,
    }
    if d != 0{
        expires := time.Now().AddDate(0,0,d)
        cookie.Expires = expires
    }
    http.SetCookie(w, &cookie)
}

func getCookie(r *http.Request, name string) string {
    c, err := r.Cookie(name)
    if err != nil {
        return ""
    }
    return c.Value
}

func deleteCookie(w http.ResponseWriter,name string){
    cookie := http.Cookie{
        Name: name,
        MaxAge: -1,
    }
    http.SetCookie(w, &cookie)
}

func signup(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        t.ExecuteTemplate(w,"signup.html",nil)
    case "POST":
        r.ParseForm()
        data := r.Form
        var error string
        if data["login"][0] == ""{
            error = "Login can't be empty"
        } else if data["email"][0] == ""{
            error = "Email can't be empty"
        } else if data["password"][0] == ""{
            error = "Password cant't be empty"
        } else if len(data["login"][0]) < 4{
            error = "Login must be at least 4 characters"
        } else if DB.checkLogin(data["login"][0]){
            error = "User with such login already exists"
        } else if !strings.ContainsRune(data["email"][0],'@'){
            error = "Email must contain @"
        } else if DB.checkEmail(data["email"][0]) {
            error = "User with such email already exists"
        } else if len(data["password"][0]) < 8{
            error = "Password must be at least 8 characters"
        } else if data["password2"][0] != data["password"][0]{
            error = "Passwords don't match"
        }
        if error != ""{
            values :=&User{}
            values.Login = data["login"][0]
            values.Email = data["email"][0]
            t, err := template.ParseFiles("signup.html")
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            t.Execute(w,values)
            fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
        } else {
            hashedPassword, err := bcrypt.GenerateFromPassword([]byte(data["password"][0]),10)
            if err != nil{
                http.Error(w,"Internal server error",500)
            }
            DB.newUser(data["login"][0],data["email"][0],string(hashedPassword))
            http.Redirect(w,r,"/login",http.StatusSeeOther)
        }
    }
}

func signin(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        t.ExecuteTemplate(w,"signin.html",nil)
    case "POST":
        r.ParseForm()
        data := r.Form
        var error string
        if !DB.checkLogin(data["login"][0]){
            error = "User with such login doesn't exists"
        } else {
            if !DB.checkPassword(data["login"][0],data["password"][0]){
                error = "Wrong password"
            }
        }
        if error != ""{
            values :=&User{}
            values.Login = data["login"][0]
            t, err := template.ParseFiles("signin.html")
            if err != nil{
                http.Error(w,"Internal server error",http.StatusInternalServerError)
            }
            t.Execute(w,values)
            fmt.Fprintln(w,"<hr><span style='color:red;'>" + error + "</span>")
        } else {
            expiresAfter := 0
            if r.FormValue("remember") == "1"{
                expiresAfter = 30
            }
            token := genToken(32)
            setCookie(w,"login",data["login"][0],expiresAfter)
            setCookie(w,"session_token",token,expiresAfter)
            DB.newSession(data["login"][0],token)
            http.Redirect(w,r,"/",http.StatusSeeOther)
        }
    }
}

func mainPage(w http.ResponseWriter, r *http.Request) {
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    if !DB.checkToken(login,token){
        http.Redirect(w,r,"/login",http.StatusSeeOther)
    }
    user := DB.getUser(login)
    t.ExecuteTemplate(w,"main.html",user)
}

func logout(w http.ResponseWriter,r *http.Request){
    login := getCookie(r,"login")
    token := getCookie(r,"session_token")
    deleteCookie(w,"login")
    deleteCookie(w,"session_token")
    DB.deleteSession(login,token)
    http.Redirect(w,r,"/login",http.StatusSeeOther)
}

func main() {
    http.HandleFunc("/register", signup)
    http.HandleFunc("/login", signin)
    http.HandleFunc("/", mainPage)
    http.HandleFunc("/logout",logout)
    http.ListenAndServe(":8080", nil)
}

Database file:

package main

import (
    "database/sql"
    "golang.org/x/crypto/bcrypt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

var DB = newDB("root:root574@/signin")

type db struct {
    DB *sql.DB
}

func newDB(name string) *db {
    conn, err := sql.Open("mysql", name)
    if err != nil {
        log.Fatal(err)
    }
    if err = conn.Ping(); err != nil {
        log.Fatal(err)
    }
    return &db{DB: conn}
}

func (db db) newUser(login, email, password string) {
    db.DB.Exec("INSERT INTO users(login,email,password) VALUES (?,?,?)", login, email, password)
}

func (db db) newSession(login, token string) {
    db.DB.Exec("INSERT INTO sessions(login,token) VALUES (?,?)",login,token)
}

func (db db) deleteSession(login, token string) {
    db.DB.Exec("DELETE FROM sessions WHERE login = ? and token = ?",login,token)
}

func (db db) checkLogin(login string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE login = ?", login)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkEmail(email string) bool {
    var rows, _ = db.DB.Query("SELECT id FROM users WHERE email = ?", email)
    if rows.Next() {
        return true
    }
    rows.Close()
    return false
}

func (db db) checkPassword(login, password string) bool{
    var rows, _ = db.DB.Query("SELECT password FROM users WHERE login = ?", login)
    var dbpassword string
    rows.Next()
    rows.Scan(&dbpassword)
    rows.Close()
    if bcrypt.CompareHashAndPassword([]byte(dbpassword),[]byte(password)) != nil{
        return false
    }
    return true
}

func (db db) checkToken(login, token string) bool {
    var rows, _ = db.DB.Query("SELECT token FROM sessions WHERE login = ? and token = ?",login,token)
    if rows.Next(){
        return true
    }
    rows.Close()
    return false
}

func (db db) getUser(login string) *User {
    var rows, _ = db.DB.Query("select email FROM users WHERE login = ?",login)
    user := &User{}
    rows.Next()
    rows.Scan(&user.Email)
    rows.Close()
    user.Login = login
    return user
}

Database users table:

+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int          | NO   | PRI | NULL    | auto_increment |
| login    | varchar(255) | YES  |     | NULL    |                |
| email    | varchar(255) | YES  |     | NULL    |                |
| password | varchar(255) | YES  |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+

Database sessions table:

+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| login | varchar(255) | YES  |     | NULL    |       |
| token | varchar(44)  | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
\$\endgroup\$
4
  • \$\begingroup\$ What are your concerns? I also don't quite understand what is "cookie-based" about your auth, looks like vanilla session-based auth to me. \$\endgroup\$ – D. SM Sep 12 '20 at 7:36
  • \$\begingroup\$ @D.SM I think it looks like this,isn't it?.Can you provide some suggestion on how to improve the security? \$\endgroup\$ – Honor 373 Sep 13 '20 at 7:57
  • \$\begingroup\$ Cookies are used to implement sessions. Your auth uses sessions. You didn't say what your concerns were. \$\endgroup\$ – D. SM Sep 14 '20 at 9:05
  • \$\begingroup\$ @D.SM I'm concerned about security \$\endgroup\$ – Honor 373 Sep 14 '20 at 12:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.