Skip to content

Instantly share code, notes, and snippets.

@dapperAuteur
Last active March 24, 2021 03:09
Show Gist options
  • Save dapperAuteur/6647cbd60ee56dc708164381ab16efea to your computer and use it in GitHub Desktop.
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
// 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
}
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