This repository has been archived on 2025-03-30. You can view files and clone it, but cannot push or open issues or pull requests.
bactdb/api/auth.go
Matthew Dillon be9e6481d0 Roughing in JWT-based authentication.
Todo: Actually utilize this stuff somewhere.
2014-12-10 13:52:26 -09:00

146 lines
3.7 KiB
Go

package api
import (
"errors"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
)
const (
privKeyPath = "keys/app.rsa" // openssl genrsa -out app.rsa keysize
pubKeyPath = "keys/app.rsa.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub
tokenName = "AccessToken"
)
var (
verifyKey, signKey []byte
errWhileSigningToken = errors.New("error while signing token")
errPleaseLogIn = errors.New("please log in")
errWhileParsingCookie = errors.New("error while parsing cookie")
errTokenExpired = errors.New("token expired")
errGenericError = errors.New("generic error")
)
func init() {
var err error
signKey, err = ioutil.ReadFile(privKeyPath)
if err != nil {
// Before exploding, check up one level...
signKey, err = ioutil.ReadFile("../" + privKeyPath)
if err != nil {
log.Fatalf("Error reading private key: ", err)
return
}
}
verifyKey, err = ioutil.ReadFile(pubKeyPath)
if err != nil {
// Before exploding, check up one level...
verifyKey, err = ioutil.ReadFile("../" + pubKeyPath)
if err != nil {
log.Fatalf("Error reading public key: ", err)
return
}
}
}
func serveToken(w http.ResponseWriter, r *http.Request) error {
t := jwt.New(jwt.GetSigningMethod("RS256"))
// Set our claims
t.Claims["AccessToken"] = "level1"
t.Claims["CustomUserInfo"] = struct {
Name string
Kind string
}{"mrdillon", "human"}
// Set the expire time
// See http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
t.Claims["exp"] = time.Now().Add(time.Minute * 1).Unix()
tokenString, err := t.SignedString(signKey)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return errWhileSigningToken
}
http.SetCookie(w, &http.Cookie{
Name: tokenName,
Value: tokenString,
Path: "/",
RawExpires: "0",
})
return writeJSON(w, Message{"success"})
}
type authHandler func(http.ResponseWriter, *http.Request) error
// Only accessible with a valid token
func (h authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Even though writeJSON sets the content type, we need to set it here because
// calls to WriteHeader write out the entire header.
w.Header().Set("content-type", "application/json; charset=utf-8")
tokenCookie, err := r.Cookie(tokenName)
switch {
case err == http.ErrNoCookie:
w.WriteHeader(http.StatusUnauthorized)
writeJSON(w, Error{errPleaseLogIn})
return
case err != nil:
w.WriteHeader(http.StatusInternalServerError)
writeJSON(w, Error{errWhileParsingCookie})
return
}
if tokenCookie.Value == "" {
w.WriteHeader(http.StatusUnauthorized)
writeJSON(w, Error{errPleaseLogIn})
return
}
// Validate the token
token, err := jwt.Parse(tokenCookie.Value, func(token *jwt.Token) (interface{}, error) {
return verifyKey, nil
})
// Branch out into the possible error from signing
switch err.(type) {
case nil: // No error
if !token.Valid { // But may still be invalid
w.WriteHeader(http.StatusUnauthorized)
writeJSON(w, Error{errPleaseLogIn})
return
}
case *jwt.ValidationError: // Something was wrong during the validation
vErr := err.(*jwt.ValidationError)
switch vErr.Errors {
case jwt.ValidationErrorExpired:
w.WriteHeader(http.StatusUnauthorized)
writeJSON(w, Error{errTokenExpired})
return
default:
w.WriteHeader(http.StatusInternalServerError)
writeJSON(w, Error{errGenericError})
return
}
default: // Something else went wrong
w.WriteHeader(http.StatusInternalServerError)
writeJSON(w, Error{errGenericError})
return
}
hErr := h(w, r)
if hErr != nil {
w.WriteHeader(http.StatusInternalServerError)
writeJSON(w, Error{hErr})
}
}
func restrictedHandler(w http.ResponseWriter, r *http.Request) error {
return writeJSON(w, Message{"great success"})
}