/
auth.go
124 lines (98 loc) · 3.11 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Reference:
// https://gist.github.com/cryptix/45c33ecf0ae54828e63b
// http://jwt.io
package gogoapi
import (
"crypto/rsa"
"io/ioutil"
"net/http"
"time"
jwt "github.com/dgrijalva/jwt-go"
)
const (
DEFAULT_TOKEN_DURATION = 24 * 60 // 24 hours, probably too long
)
type AuthValidator func(request *http.Request) (bool, map[string]interface{}, StatusResponse)
type AuthResponse struct {
AccessToken string `json:"access_token"`
}
type AuthResource struct {
verifyKey *rsa.PublicKey
signKey *rsa.PrivateKey
tokenDuration time.Duration
validator AuthValidator
}
func NewAuthResource(privateKeyPath, publicKeyPath string, tokenDuration time.Duration, validator AuthValidator) *AuthResource {
verifyBytes, err := ioutil.ReadFile(publicKeyPath)
fatal(err)
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
fatal(err)
signBytes, err := ioutil.ReadFile(privateKeyPath)
fatal(err)
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err)
if tokenDuration < 1 {
tokenDuration = DEFAULT_TOKEN_DURATION
}
if nil == validator {
validator = DummyAuthValidator
}
return &AuthResource{verifyKey, signKey, tokenDuration, validator}
}
func (auth *AuthResource) AuthTokenResponse(claims map[string]interface{}) (int, interface{}, http.Header) {
// create a signer for rsa 256
token := jwt.New(jwt.GetSigningMethod("RS256"))
// set our claims
token.Claims = claims
// set the expire time
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
token.Claims["exp"] = time.Now().Add(time.Minute * auth.tokenDuration).Unix()
tokenString, err := token.SignedString(auth.signKey)
if err != nil {
status := http.StatusInternalServerError
return status, JSONError{status, "Token signing error."}, nil
}
response := AuthResponse{
AccessToken: tokenString,
}
return http.StatusOK, response, nil
}
// get token
func (auth *AuthResource) Post(request *http.Request) (int, interface{}, http.Header) {
// validate using user supplied function
success, claims, errorResponseObject := auth.validator(request)
if !success {
return errorResponseObject.Status(), errorResponseObject, nil
}
return auth.AuthTokenResponse(claims)
}
// authorization test
// using this method will require a reference to the auth object
func (auth *AuthResource) IsAuthorized(request *http.Request) (bool, *jwt.Token, error) {
tokenString := request.Header.Get("Authorization")
if "" == tokenString {
return false, nil, nil
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return auth.verifyKey, nil
})
switch err {
case nil:
return token.Valid, token, nil
default:
return false, token, err
}
}
// canned failure response
func AuthorizationFailed() (int, interface{}, http.Header) {
status := http.StatusUnauthorized
return status, JSONError{status, "Access denied."}, nil
}
// authorization wrapper
func (auth *AuthResource) AuthorizationRequired(request *http.Request, inner HandlerFunc) (int, interface{}, http.Header) {
authorized, _, _ := auth.IsAuthorized(request)
if !authorized {
return AuthorizationFailed()
}
return inner(request)
}