Skip to content

Instantly share code, notes, and snippets.

@proglottis
Last active August 29, 2015 14:06
Show Gist options
  • Save proglottis/cfa82d15c0ad22b73199 to your computer and use it in GitHub Desktop.
Save proglottis/cfa82d15c0ad22b73199 to your computer and use it in GitHub Desktop.
package main
import (
"database/sql"
"encoding/json"
"net/http"
"os"
"strconv"
"time"
)
import (
_ "code.google.com/p/go-sqlite/go1/sqlite3"
"code.google.com/p/go.crypto/bcrypt"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/context"
)
const (
tokenSecret = "keyboard cat"
tokenKey = 0
)
type User struct {
Id int64 `json:"id"`
Email string `json:"email"`
Password string `json:"-"`
}
func (u *User) SetPassword(password string) error {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 0)
if err != nil {
return err
}
u.Password = string(bytes)
return nil
}
func (u *User) IsPassword(password string) bool {
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) == nil
}
type UserRepo struct {
db *sql.DB
}
func (r *UserRepo) Find(user *User, id int64) error {
q := r.db.QueryRow("SELECT id, email, password FROM users WHERE id=?", id)
if err := q.Scan(&user.Id, &user.Email, &user.Password); err != nil {
return err
}
return nil
}
func (r *UserRepo) FindByEmail(user *User, email string) error {
q := r.db.QueryRow("SELECT id, email, password FROM users WHERE email=?", email)
if err := q.Scan(&user.Id, &user.Email, &user.Password); err != nil {
return err
}
return nil
}
func (r *UserRepo) Create(user *User) error {
result, err := r.db.Exec("INSERT INTO users (email, password) VALUES (?, ?)", user.Email, user.Password)
if err != nil {
return err
}
user.Id, err = result.LastInsertId()
return err
}
func JSON(response http.ResponseWriter, thing interface{}) {
response.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(response)
encoder.Encode(thing)
}
type jsonMessage struct {
Message string `json:"message"`
}
func JSONMessage(response http.ResponseWriter, message string, code int) {
response.WriteHeader(code)
JSON(response, &jsonMessage{Message: message})
}
type authHandler struct {
next http.Handler
}
func (h *authHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
token, err := jwt.ParseFromRequest(request, func(token *jwt.Token) (interface{}, error) {
return []byte(tokenSecret), nil
})
if err != nil || !token.Valid {
JSONMessage(response, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
context.Set(request, tokenKey, token)
h.next.ServeHTTP(response, request)
}
func AuthRequired(next http.Handler) http.Handler {
return &authHandler{next: next}
}
type loginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type loginHandler struct {
userRepo *UserRepo
}
func (h *loginHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
login := &loginRequest{}
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(login); err != nil {
JSONMessage(response, err.Error(), http.StatusBadRequest)
return
}
user := &User{}
if err := h.userRepo.FindByEmail(user, login.Email); err != nil {
JSONMessage(response, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if !user.IsPassword(login.Password) {
JSONMessage(response, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
token := jwt.New(jwt.GetSigningMethod("HS256"))
token.Claims["iat"] = time.Now().Unix()
token.Claims["exp"] = time.Now().Add(time.Hour * 24 * 7).Unix()
token.Claims["sub"] = strconv.FormatInt(user.Id, 10) // spec says this must be a string
strToken, err := token.SignedString([]byte(tokenSecret))
if err != nil {
JSONMessage(response, err.Error(), http.StatusInternalServerError)
}
JSON(response, map[string]interface{}{"token": strToken})
}
type signupHandler struct {
userRepo *UserRepo
}
func (h *signupHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
login := &loginRequest{}
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(login); err != nil {
JSONMessage(response, err.Error(), http.StatusBadRequest)
return
}
user := &User{}
user.Email = login.Email
user.SetPassword(login.Password)
if err := h.userRepo.Create(user); err != nil {
JSONMessage(response, err.Error(), http.StatusInternalServerError)
return
}
JSONMessage(response, "OK", http.StatusOK)
}
type meHandler struct {
userRepo *UserRepo
}
func (h *meHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
token := context.Get(request, tokenKey).(*jwt.Token)
id, err := strconv.ParseInt(token.Claims["sub"].(string), 10, 64)
if err != nil {
JSONMessage(response, err.Error(), http.StatusInternalServerError)
return
}
user := &User{}
if err := h.userRepo.Find(user, id); err != nil {
JSONMessage(response, err.Error(), http.StatusInternalServerError)
return
}
JSON(response, user)
}
func httpAddress() string {
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
return "localhost:" + port
}
func main() {
db, err := sql.Open("sqlite3", "sqlite.db")
if err != nil {
panic(err)
}
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email CHAR(255) NOT NULL,
password CHAR(255) NOT NULL
)`); err != nil {
panic(err)
}
userRepo := &UserRepo{db: db}
http.Handle("/api/me", AuthRequired(&meHandler{userRepo: userRepo}))
http.Handle("/auth/login", &loginHandler{userRepo: userRepo})
http.Handle("/auth/signup", &signupHandler{userRepo: userRepo})
if err := http.ListenAndServe(httpAddress(), nil); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment