github.com/safedep/dry@v0.0.0-20241016050132-a15651f0548b/apiguard/builder.go (about)

     1  package apiguard
     2  
     3  import (
     4  	"crypto/subtle"
     5  	"errors"
     6  	"net/http"
     7  	"os"
     8  )
     9  
    10  const (
    11  	// API Guard config is the source of truth for these
    12  	// headers. Changes should be reflected in the API Guard
    13  	headerRemoteAddr         = "X-Remote-Addr"
    14  	headerPath               = "X-Path"
    15  	headerRequestId          = "X-Request-Id"
    16  	headerTrustToken         = "X-Gateway-Trust"
    17  	headerTokenEmail         = "X-Jwt-Email"
    18  	headerTokenEmailVerified = "X-Jwt-Email-Verified"
    19  	headerTokenSub           = "X-Jwt-Sub"
    20  	headerTokenAud           = "X-Jwt-Aud"
    21  	headerMetaOrgId          = "X-Org-Id"
    22  	headerMetaTeamId         = "X-Team-Id"
    23  	headerMetaUserId         = "X-User-Id"
    24  	headerMetaKeyId          = "X-Key-Id"
    25  )
    26  
    27  func EmailFromTokenHeaderName() string {
    28  	return headerTokenEmail
    29  }
    30  
    31  // SecurelyBuildFromHeader builds a context from the header and validates
    32  // the trust token. Multiple tokens can be passed to allow zero downtime
    33  // token rotation at the API Guard.
    34  func SecurelyBuildFromHeader(header http.Header, tokens ...string) (*Context, error) {
    35  	if len(tokens) == 0 {
    36  		trustToken := os.Getenv("APIGUARD_TRUST_TOKEN")
    37  		if trustToken != "" {
    38  			tokens = append(tokens, trustToken)
    39  		}
    40  	}
    41  
    42  	if len(tokens) == 0 {
    43  		return nil, errors.New("APIGuard: Trust token not provided")
    44  	}
    45  
    46  	ctx, err := buildFromHeader(header)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	for _, trustToken := range tokens {
    52  		if subtle.ConstantTimeCompare([]byte(ctx.TrustToken), []byte(trustToken)) == 1 {
    53  			return ctx, nil
    54  		}
    55  	}
    56  
    57  	return nil, errors.New("APIGuard: Trust token mismatch")
    58  }
    59  
    60  // Build a context from the header. This is useful when the API Guard
    61  // is a reverse proxy and the information is passed as headers.
    62  func buildFromHeader(header http.Header) (*Context, error) {
    63  	ctx := Context{
    64  		RemoteAddr: header.Get(headerRemoteAddr),
    65  		RequestID:  header.Get(headerRequestId),
    66  		Path:       header.Get(headerPath),
    67  		TrustToken: header.Get(headerTrustToken),
    68  		Key: KeyInfo{
    69  			OrganizationID: header.Get(headerMetaOrgId),
    70  			TeamID:         header.Get(headerMetaTeamId),
    71  			UserID:         header.Get(headerMetaUserId),
    72  			KeyID:          header.Get(headerMetaKeyId),
    73  		},
    74  		Token: TokenInfo{
    75  			Email:         header.Get(headerTokenEmail),
    76  			EmailVerified: header.Get(headerTokenEmailVerified) == "true",
    77  			Subject:       header.Get(headerTokenSub),
    78  			Audience:      header.Get(headerTokenAud),
    79  		},
    80  	}
    81  
    82  	return &ctx, nil
    83  }