|
package main |
|
|
|
import ( |
|
"crypto/rand" |
|
"fmt" |
|
"log" |
|
"net/http" |
|
"sync" |
|
"time" |
|
) |
|
|
|
var sessions = make(map[string]time.Time) |
|
var sessionLock sync.RWMutex |
|
|
|
func init() { |
|
go expireSessions() |
|
} |
|
|
|
func main() { |
|
http.HandleFunc("/", index) |
|
http.HandleFunc("/login", login) |
|
http.ListenAndServe(":8000", nil) |
|
} |
|
|
|
func index(w http.ResponseWriter, r *http.Request) { |
|
sessionID := getSessionID(r) |
|
|
|
if !sessionValid(sessionID) { |
|
anon(w, r) |
|
return |
|
} |
|
|
|
w.Write(page(fmt.Sprintf("You are logged in: %q", sessionID))) |
|
} |
|
|
|
func anon(w http.ResponseWriter, r *http.Request) { |
|
w.Write(page("You are not logged in.")) |
|
} |
|
|
|
func login(w http.ResponseWriter, r *http.Request) { |
|
p := r.FormValue("password") |
|
if p == "secret" { |
|
sessionID, err := UUID4() |
|
if err != nil { |
|
log.Printf("Failed to create UUID: %s\n", err) |
|
http.Error(w, "internal error", http.StatusInternalServerError) |
|
return |
|
} |
|
c := http.Cookie{Name: "SessionID", Value: sessionID} |
|
http.SetCookie(w, &c) |
|
createSession(sessionID) |
|
http.Redirect(w, r, "/", http.StatusFound) |
|
return |
|
} |
|
w.Write(page("Incorrect password.")) |
|
} |
|
|
|
func sessionValid(sessionID string) bool { |
|
sessionLock.RLock() |
|
defer sessionLock.RUnlock() |
|
|
|
_, found := sessions[sessionID] |
|
return found |
|
} |
|
|
|
func expireSessions() { |
|
for { |
|
time.Sleep(time.Second) |
|
sessionLock.RLock() |
|
for k, v := range sessions { |
|
if v.Before(time.Now()) { |
|
go deleteSession(k) |
|
} |
|
} |
|
sessionLock.RUnlock() |
|
} |
|
} |
|
|
|
func deleteSession(key string) { |
|
sessionLock.Lock() |
|
defer sessionLock.Unlock() |
|
delete(sessions, key) |
|
} |
|
func createSession(key string) { |
|
sessionLock.Lock() |
|
defer sessionLock.Unlock() |
|
sessions[key] = time.Now().Add(time.Second * 5) |
|
} |
|
|
|
func UUID4() (string, error) { |
|
b := make([]byte, 16) |
|
|
|
_, err := rand.Read(b[:]) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
// Set the two most significant bits (bits 6 and 7) of the |
|
// clock_seq_hi_and_reserved to zero and one, respectively. |
|
b[8] = (b[8] | 0x40) & 0x7F |
|
|
|
// Set the four most significant bits (bits 12 through 15) of the |
|
// time_hi_and_version field to the 4-bit version number. |
|
b[6] = (b[6] & 0xF) | (4 << 4) |
|
|
|
// Return unparsed version of the generated UUID sequence. |
|
return fmt.Sprintf("%x-%x-%x-%x-%x", |
|
b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil |
|
} |
|
|
|
func getSessionID(r *http.Request) string { |
|
c, err := r.Cookie("SessionID") |
|
if err != nil { |
|
log.Printf("Error getting session cookie: %s\n", err) |
|
return "" |
|
} |
|
return c.Value |
|
} |
|
|
|
var template = `<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>Login demo</title> |
|
<meta name="viewport" content="width-device-width, initial-scale=1"> |
|
</head> |
|
<body> |
|
<div> |
|
<p>%s</p> |
|
<p><a href="/">home</a></p> |
|
<p><a href="/login?password=secret">log in</a></p> |
|
</div> |
|
</body> |
|
</html> |
|
` |
|
|
|
func page(content string) []byte { |
|
return []byte(fmt.Sprintf(template, content)) |
|
} |