Created
January 26, 2018 01:52
-
-
Save pciet/8529531fffbe8d9523d883c901d311da to your computer and use it in GitHub Desktop.
login session gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// insecure implementation, the session key cookie should be Secure and HttpOnly in a final version | |
import ( | |
"database/sql" | |
_ "github.com/lib/pq" | |
) | |
type DB struct { | |
*sql.DB | |
} | |
// initialized in main before http.ListenAndServe | |
var database DB | |
type TX struct { | |
*sql.Tx | |
} | |
func (db DB) Begin() TX { | |
tx, err := db.DB.Begin() | |
if err != nil { | |
panic(err.Error()) | |
} | |
return TX{ | |
Tx: tx, | |
} | |
} | |
func (tx TX) Commit() { | |
err := tx.Tx.Commit() | |
if err != nil { | |
panic(err.Error()) | |
} | |
} | |
/****************/ | |
import ( | |
"net/http" | |
) | |
// in each HTTP handler | |
key, name := database.validSession(r) | |
if key == "" { | |
http.Redirect(w, r, "/login", http.StatusFound) | |
return | |
} | |
if name == "" { | |
clearClientSession(w) | |
http.Redirect(w, r, "/login", http.StatusFound) | |
return | |
} | |
/****************/ | |
import ( | |
"golang.org/x/crypto/bcrypt" | |
) | |
// The client code is responsible for checking the specifics of password requirements because the server behavior is the same for "wrong password" and "invalid password for new player". | |
func (db DB) loginOrCreate(name, password string) string { | |
key := db.login(name, password) | |
if key != "" { | |
return key | |
} | |
return db.createAndLogin(name, password) | |
} | |
func (db DB) login(name, password string) string { | |
has, encrypt := db.playerCrypt(name) | |
if has == false { | |
return "" | |
} | |
err := bcrypt.CompareHashAndPassword([]byte(encrypt), []byte(password)) | |
if err != nil { | |
return "" | |
} | |
sessionKey := newSessionKey() | |
db.newSession(name, sessionKey) | |
return sessionKey | |
} | |
func (db DB) createAndLogin(name, password string) string { | |
exists, _ := db.playerCrypt(name) | |
if exists { | |
return "" | |
} | |
if (name == easy_computer_player) || (name == hard_computer_player) { | |
return "" | |
} | |
crypt, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | |
if err != nil { | |
panic(err.Error()) | |
} | |
db.newPlayer(name, string(crypt)) | |
return db.login(name, password) | |
} | |
/****************/ | |
import ( | |
"crypto/rand" | |
"database/sql" | |
"encoding/base64" | |
"fmt" | |
"net/http" | |
) | |
const ( | |
key_cookie = "k" | |
key_length = 64 | |
session_table = "sessions" | |
session_name = "name" | |
session_key = "key" | |
) | |
// key, name | |
func (db DB) validSession(r *http.Request) (string, string) { | |
keyCookie, err := r.Cookie(key_cookie) | |
if err != nil { | |
return "", "" | |
} | |
var name string | |
err = db.QueryRow("SELECT "+session_name+" FROM "+session_table+" WHERE "+session_key+"=$1;", keyCookie.Value).Scan(&name) | |
if err != nil { | |
if debug { | |
fmt.Println(err.Error()) | |
} | |
return "", "" | |
} | |
return keyCookie.Value, name | |
} | |
func clearClientSession(w http.ResponseWriter) { | |
http.SetCookie(w, &http.Cookie{ | |
Name: key_cookie, // from web_login.go | |
Value: "", | |
Path: "/", | |
HttpOnly: true, | |
Secure: false, // TODO: set true after TLS certification | |
}) | |
} | |
func (db DB) newSession(name, key string) { | |
tx := db.Begin() | |
defer tx.Commit() | |
var playerKey []byte | |
err := tx.QueryRow("SELECT "+session_key+" FROM "+session_table+" WHERE "+session_name+"=$1 FOR UPDATE;", name).Scan(&playerKey) | |
if err == nil { | |
if string(playerKey) != key { | |
_, err = tx.Exec("UPDATE "+session_table+" SET "+session_key+" =$1 WHERE "+session_name+" =$2;", []byte(key), name) | |
if err != nil { | |
panic(err.Error()) | |
} | |
} | |
return | |
} else if err != sql.ErrNoRows { | |
panic(err.Error()) | |
} | |
_, err = tx.Exec("INSERT INTO "+session_table+"("+session_name+", "+session_key+") VALUES ($1, $2);", name, []byte(key)) | |
if err != nil { | |
panic(err.Error()) | |
} | |
} | |
func newSessionKey() string { | |
key := make([]byte, key_length) | |
count, err := rand.Read(key) | |
if err != nil { | |
panic(err.Error()) | |
} | |
if count != key_length { | |
panic(fmt.Sprintf("count %v does not match key length %v", count, key_length)) | |
} | |
return base64.StdEncoding.EncodeToString(key) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment