github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/examples/composed-auth/auth/authorizers.go (about)

     1  package auth
     2  
     3  import (
     4  	"crypto/rsa"
     5  	"os"
     6  
     7  	errors "github.com/go-openapi/errors"
     8  	jwt "github.com/golang-jwt/jwt/v5"
     9  	models "github.com/thetreep/go-swagger/examples/composed-auth/models"
    10  )
    11  
    12  const (
    13  	// currently unused: privateKeyPath = "keys/apiKey.prv"
    14  	publicKeyPath = "keys/apiKey.pem"
    15  	issuerName    = "example.com"
    16  )
    17  
    18  var (
    19  	userDb map[string]string
    20  
    21  	// Keys used to sign and verify our tokens
    22  	verifyKey *rsa.PublicKey
    23  	// currently unused: signKey   *rsa.PrivateKey
    24  )
    25  
    26  // roleClaims describes the format of our JWT token's claims
    27  type roleClaims struct {
    28  	Roles []string `json:"roles"`
    29  	jwt.MapClaims
    30  }
    31  
    32  func init() {
    33  	// emulates the loading of a local users database
    34  	userDb = map[string]string{
    35  		"fred": "scrum",
    36  		"ivan": "terrible",
    37  	}
    38  
    39  	// loads public keys to verify our tokens
    40  	verifyKeyBuf, err := os.ReadFile(publicKeyPath)
    41  	if err != nil {
    42  		panic("Cannot load public key for tokens")
    43  	}
    44  	verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyKeyBuf)
    45  	if err != nil {
    46  		panic("Invalid public key for tokens")
    47  	}
    48  }
    49  
    50  // Customized authorizer methods for our sample API
    51  
    52  // IsRegistered determines if the user is properly registered,
    53  // i.e if a valid username:password pair has been provided
    54  func IsRegistered(user, pass string) (*models.Principal, error) {
    55  	password, ok := userDb[user]
    56  	if !ok || pass != password {
    57  		return nil, errors.New(401, "Unauthorized: not a registered user")
    58  	}
    59  
    60  	return &models.Principal{
    61  		Name: user,
    62  	}, nil
    63  }
    64  
    65  // IsReseller tells if the API key is a JWT signed by us with a claim to be a reseller
    66  func IsReseller(token string) (*models.Principal, error) {
    67  	claims, err := parseAndCheckToken(token)
    68  	if err != nil {
    69  		return nil, errors.New(401, "Unauthorized: invalid API key token: %v", err)
    70  	}
    71  
    72  	issuer, err := claims.GetIssuer()
    73  	if issuer != issuerName {
    74  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %v", err)
    75  	}
    76  
    77  	id, err := claims.GetSubject()
    78  	if err != nil {
    79  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: missing subject: %v", err)
    80  	}
    81  
    82  	if issuer != issuerName || id == "" {
    83  		return nil, errors.New(403, "Forbidden: insufficient API key privileges")
    84  	}
    85  
    86  	isReseller := false
    87  	for _, role := range claims.Roles {
    88  		if role == "reseller" {
    89  			isReseller = true
    90  			break
    91  		}
    92  	}
    93  
    94  	if !isReseller {
    95  		return nil, errors.New(403, "Forbidden: insufficient API key privileges")
    96  	}
    97  
    98  	return &models.Principal{
    99  		Name: id,
   100  	}, nil
   101  }
   102  
   103  // HasRole tells if the Bearer token is a JWT signed by us with a claim to be
   104  // member of an authorization scope.
   105  // We verify that the claimed role is one of the passed scopes
   106  func HasRole(token string, scopes []string) (*models.Principal, error) {
   107  	claims, err := parseAndCheckToken(token)
   108  	if err != nil {
   109  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: %v", err)
   110  	}
   111  	issuer, err := claims.GetIssuer()
   112  	if err != nil {
   113  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %v", err)
   114  	}
   115  
   116  	if issuer != issuerName {
   117  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %s", issuer)
   118  	}
   119  
   120  	id, err := claims.GetSubject()
   121  	if err != nil {
   122  		return nil, errors.New(401, "Unauthorized: invalid Bearer token: missing subject: %v", err)
   123  	}
   124  
   125  	isInScopes := false
   126  	claimedRoles := []string{}
   127  	for _, scope := range scopes {
   128  		for _, role := range claims.Roles {
   129  			if role == scope {
   130  				isInScopes = true
   131  				// we enrich the principal with all claimed roles within scope (hence: not breaking here)
   132  				claimedRoles = append(claimedRoles, role)
   133  			}
   134  		}
   135  	}
   136  
   137  	if !isInScopes {
   138  		return nil, errors.New(403, "Forbidden: insufficient privileges")
   139  	}
   140  
   141  	return &models.Principal{
   142  		Name:  id,
   143  		Roles: claimedRoles,
   144  	}, nil
   145  }
   146  
   147  func parseAndCheckToken(token string) (*roleClaims, error) {
   148  	// the API key is a JWT signed by us with a claim to be a reseller
   149  	parsedToken, err := jwt.ParseWithClaims(
   150  		token, &roleClaims{}, func(parsedToken *jwt.Token) (interface{}, error) {
   151  			// the key used to validate tokens
   152  			return verifyKey, nil
   153  		},
   154  	)
   155  
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	claims, ok := parsedToken.Claims.(*roleClaims)
   161  	if !ok || !parsedToken.Valid {
   162  		return nil, errors.New(401, "invalid token missing expected claims")
   163  	}
   164  
   165  	return claims, nil
   166  }