Skip to content

Instantly share code, notes, and snippets.

@kschaper
Last active October 30, 2017 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kschaper/0efb060c671ea550976317f58c4afaab to your computer and use it in GitHub Desktop.
Save kschaper/0efb060c671ea550976317f58c4afaab to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
type invitation struct {
email string // invitee's email address provided by an existing user
code string // generated code for this email, redeemable once
}
var code = "5d41402abc4b2a76b9719d911017c592"
// invitations "db" with one created record
var invitations = []*invitation{
&invitation{
email: "invitee@example.com",
code: code,
},
}
func getInvitation(code string) *invitation {
fmt.Println(code, invitations)
for _, i := range invitations {
if i.code == code {
return i
}
}
return nil
}
type user struct {
id int
username, email string
password string // in real code I store a bcrypt hash instead
}
// users "db"
var users []*user
func userExists(id int) bool {
for _, u := range users {
if u.id == id {
return true
}
}
return false
}
// session stuff
// TODO replace keys by values generated by `securecookie.GenerateRandomKey()`
// see http://www.gorillatoolkit.org/pkg/sessions#NewCookieStore
var store = sessions.NewCookieStore([]byte("authentication-key"), []byte("encryption-key"))
func getSession(w http.ResponseWriter, r *http.Request) *sessions.Session {
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return nil
}
return session
}
// templates
const formTpl = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>accept invitation</title>
</head>
<body>
<form action="/signup" method="post">
<p>username: <input type="text" name="username"> enter anything</p>
<p>password: <input type="password" name="password"> enter anything</p>
<p>
<input type="hidden" name="code" value="{{.Code}}">
<input type="submit" value="sign up">
</p>
</form>
</body>
</html>
`
const appTpl = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>app</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>
`
// handlers
var formHandler = func(w http.ResponseWriter, r *http.Request) {
var data struct{ Code string }
data.Code = mux.Vars(r)["code"]
t := template.Must(template.New("form").Parse(formTpl))
if err := t.Execute(w, data); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
var acceptionHandler = func(w http.ResponseWriter, r *http.Request) {
var (
code = r.FormValue("code")
username = r.FormValue("username")
password = r.FormValue("password")
)
if code == "" || username == "" || password == "" {
log.Println("required form value missing", code, username, password)
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
invitation := getInvitation(code)
if invitation == nil {
log.Println("invitation not found")
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
user := &user{123, username, invitation.email, password}
users = append(users, user)
fmt.Println(users)
session := getSession(w, r)
session.Values["user_id"] = user.id
session.Save(r, w)
http.Redirect(w, r, "/app", http.StatusFound)
}
var appHandler = func(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.New("app").Parse(appTpl))
if err := t.Execute(w, nil); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
func authMiddleware(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := getSession(w, r)
userID, found := session.Values["user_id"]
if !found {
log.Println("no user_id found in session")
http.Error(w, "unauthorised", http.StatusUnauthorized)
return
}
if userExists(userID.(int)) == false {
log.Println("user unknown")
http.Error(w, "unauthorised", http.StatusUnauthorized)
return
}
store.MaxAge(604800) // renew session 1 week
store.Save(r, w, session)
next(w, r)
}
}
func main() {
r := mux.NewRouter()
// no authentication required
r.HandleFunc("/signup/{code:[a-z0-9]{32}}", formHandler).Methods("GET")
r.HandleFunc("/signup", acceptionHandler).Methods("POST")
// authentication required
r.HandleFunc("/app", authMiddleware(appHandler)).Methods("GET")
http.Handle("/", r)
log.Printf("open http://localhost:3000/signup/%s in your browser, please", code)
if err := http.ListenAndServe("localhost:3000", r); err != nil {
log.Panicln(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment