Last active
March 24, 2021 03:09
-
-
Save dapperAuteur/6647cbd60ee56dc708164381ab16efea to your computer and use it in GitHub Desktop.
Gist Help ERROR MESSAGE: 45:23: cannot use lookup (type func(string) (*rsa.PublicKey, error)) as type auth.PublicKeyLookup in argument to auth.New
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Package auth provides authentication and authorization support. | |
package auth | |
import ( | |
"crypto/rsa" | |
"github.com/dgrijalva/jwt-go" | |
// "github.com/dgrijalva/jwt-go/v4" | |
"github.com/pkg/errors" | |
) | |
// These are the expected values for Claims.Roles. | |
const ( | |
RoleAdmin = "ADMIN" | |
RoleUser = "USER" | |
) | |
// ctxKey represents the type of value for the context key. | |
type ctxKey int | |
// Key is used to store/retrieve a Claims value from a context.Context. | |
const Key ctxKey = 1 | |
// Claims represents the authorization claims transmitted via a JWT. | |
type Claims struct { | |
jwt.StandardClaims | |
Roles []string `json:"roles"` | |
} | |
// HasRole returns true if the claims has at least one of the provided roles. | |
func (c Claims) HasRole(roles ...string) bool { | |
for _, has := range c.Roles { | |
for _, want := range roles { | |
if has == want { | |
return true | |
} | |
} | |
} | |
return false | |
} | |
// Keys represents an in memory store of keys. | |
type Keys map[string]*rsa.PrivateKey | |
/* | |
PublicKeyLookup defines the signature of a function to lookup public keys. | |
In a production system, a key id (KID) is used to retrieve the correct public key to parse a JWT for auth and claims. | |
A key lookup function is provided to perform the task of retrieving a KID for a given public key. | |
A key lookup function is required for creating an Authenticator. | |
* Private keys should be rotated. During the transition period, tokens signed with the old and new keys can coexist by looking up the correct public key by KID. | |
* KID to public key resolution is usually accomplished via a public JWKS endpoint. | |
See https://auth0.com/docs/jwks for more details. | |
*/ | |
type PublicKeyLookup func(kid string) *rsa.PublicKey | |
// Auth is used to authenticate clients. It can generate a token for a | |
// set of user claims and recreate the claims by parsing the token. | |
type Auth struct { | |
algorithm string | |
// keyLookup KeyLookup | |
// method jwt.SigningMethod | |
keyFunc func(t *jwt.Token) (interface{}, error) | |
parser *jwt.Parser | |
keys Keys | |
} | |
// New creates an *Auth to support authentication/authorization. | |
func New(algorithm string, lookup PublicKeyLookup, keys Keys) (*Auth, error) { | |
if jwt.GetSigningMethod(algorithm) == nil { | |
return nil, errors.Errorf("unknown algorithm %v", algorithm) | |
} | |
keyFunc := func(t *jwt.Token) (interface{}, error) { | |
kid, ok := t.Header["kid"] | |
if !ok { | |
return nil, errors.New("missing key id (kid) in token header") | |
} | |
kidID, ok := kid.(string) | |
if !ok { | |
return nil, errors.New("user token key id (kid) must be string") | |
} | |
return lookup(kidID), nil | |
} | |
// Create the token parser to use. The algorithm used to sign the JWT must be | |
// validated to avoid a critical vulnerability: | |
// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ | |
parser := jwt.Parser{ | |
ValidMethods: []string{algorithm}, | |
} | |
a := Auth{ | |
algorithm: algorithm, | |
// keyLookup: keyLookup, | |
// method: method, | |
keyFunc: keyFunc, | |
parser: &parser, | |
keys: keys, | |
} | |
return &a, nil | |
} | |
// AddKey adds a private key and combination kid id to our local store. | |
func (a *Auth) AddKey(privateKey *rsa.PrivateKey, kid string) { | |
a.keys[kid] = privateKey | |
} | |
// RemoveKey removes a private key and combination kid id from our local store. | |
func (a *Auth) RemoveKey(kid string) { | |
delete(a.keys, kid) | |
} | |
// GenerateToken generates a signed JWT token string representing the user Claims. | |
func (a *Auth) GenerateToken(kid string, claims Claims) (string, error) { | |
method := jwt.GetSigningMethod("RS256") | |
token := jwt.NewWithClaims(method, claims) | |
token.Header["kid"] = kid | |
privateKey, ok := a.keys[kid] | |
if !ok { | |
return "", errors.New("kid lookup failed") | |
} | |
str, err := token.SignedString(privateKey) | |
if err != nil { | |
return "", errors.Wrap(err, "signing token") | |
} | |
return str, nil | |
} | |
// ValidateToken recreates the Claims that were used to generate a token. It | |
// verifies that the token was signed using our key. | |
func (a *Auth) ValidateToken(tokenStr string) (Claims, error) { | |
var claims Claims | |
token, err := a.parser.ParseWithClaims(tokenStr, &claims, a.keyFunc) | |
if err != nil { | |
return Claims{}, errors.Wrap(err, "parsing token") | |
} | |
if !token.Valid { | |
return Claims{}, errors.New("invalid token") | |
} | |
return claims, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package auth_test | |
import ( | |
"crypto/rand" | |
"crypto/rsa" | |
"fmt" | |
"testing" | |
"time" | |
"github.com/dapperauteur/go-base-service/business/auth" | |
"github.com/dgrijalva/jwt-go" | |
// "github.com/dgrijalva/jwt-go/v4" | |
) | |
// Success and failure markers. | |
const ( | |
success = "\u2713" | |
failed = "\u2717" | |
) | |
func TestAuth(t *testing.T) { | |
t.Log("Given the need to be able to authenticate and authorize access.") | |
{ | |
testID := 0 | |
t.Logf("\tTest %d:\tWhen handling a single user.", testID) | |
{ | |
// Generate a new private key | |
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | |
if err != nil { | |
t.Fatalf("\t%s\tTest %d:\tShould be able to parse the private key from pem: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould be able to parse the private key from pem.", success, testID) | |
// The key id we are stating represents the public key in the public key store. | |
const keyID = "54bb2165-71e1-41a6-af3e-7da4a0e1e2c1" | |
lookup := func(kid string) (*rsa.PublicKey, error) { | |
switch kid { | |
case keyID: | |
return &privateKey.PublicKey, nil | |
} | |
return nil, fmt.Errorf("no public key found for the specified kid: %s", kid) | |
} | |
// ERROR MESSAGE: 45:23: cannot use lookup (type func(string) (*rsa.PublicKey, error)) as type auth.PublicKeyLookup in argument to auth.New | |
a, err := auth.New("RS256", lookup, auth.Keys{keyID: privateKey}) | |
if err != nil { | |
t.Fatalf("\t%s\tTest %d:\tShould be able to create an authenticator: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould be able to create an authenticator.", success, testID) | |
claims := auth.Claims{ | |
StandardClaims: jwt.StandardClaims{ | |
Issuer: "go-base-service project", | |
Subject: "5cf37266-3473-4006-984f-9325122678b7", | |
Audience: "students", | |
ExpiresAt: time.Now().Add(8760 * time.Hour).Unix(), | |
IssuedAt: time.Now().Unix(), | |
}, | |
Roles: []string{auth.RoleAdmin}, | |
} | |
token, err := a.GenerateToken(keyID, claims) | |
if err != nil { | |
t.Fatalf("\t%s\tTest %d:\tShould be able to generate a JWT: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould be able to generate a JWT.", success, testID) | |
parsedClaims, err := a.ValidateToken(token) | |
if err != nil { | |
t.Fatalf("\t%s\tTest %d:\tShould be able to parse the claims: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould be able to parse the claims.", success, testID) | |
if exp, got := len(claims.Roles), len(parsedClaims.Roles); exp != got { | |
t.Logf("\t\tTest %d:\texp: %d", testID, exp) | |
t.Logf("\t\tTest %d:\tgot: %d", testID, got) | |
t.Fatalf("\t%s\tTest %d:\tShould have the expected number of roles: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould have the expected number of roles.", success, testID) | |
if exp, got := claims.Roles[0], parsedClaims.Roles[0]; exp != got { | |
t.Logf("\t\tTest %d:\texp: %v", testID, exp) | |
t.Logf("\t\tTest %d:\tgot: %v", testID, got) | |
t.Fatalf("\t%s\tTest %d:\tShould have the expected roles: %v", failed, testID, err) | |
} | |
t.Logf("\t%s\tTest %d:\tShould have the expected roles.", success, testID) | |
} | |
} | |
} | |
// ============================================================================= | |
type keyStore struct { | |
pk *rsa.PrivateKey | |
} | |
func (ks *keyStore) PrivateKey(kid string) (*rsa.PrivateKey, error) { | |
return ks.pk, nil | |
} | |
func (ks *keyStore) PublicKey(kid string) (*rsa.PublicKey, error) { | |
return &ks.pk.PublicKey, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment