github.com/mundipagg/boleto-api@v0.0.0-20230620145841-3f9ec742599f/stone/authentication.go (about) 1 package stone 2 3 import ( 4 "encoding/json" 5 "strings" 6 "sync" 7 8 "github.com/mundipagg/boleto-api/config" 9 "github.com/mundipagg/boleto-api/db" 10 "github.com/mundipagg/boleto-api/log" 11 "github.com/mundipagg/boleto-api/models" 12 "github.com/mundipagg/boleto-api/util" 13 ) 14 15 var ( 16 HttpClient = &util.HTTPClient{} 17 mu sync.Mutex 18 AccessTokenPayload = map[string]string{ 19 "client_id": "", 20 "grant_type": "client_credentials", 21 "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", 22 "client_assertion": "", 23 } 24 ) 25 26 const ( 27 issuerBank = "stone" 28 BadRequestError = "status code 400" 29 DocumentNotFound = "mongo: no documents in result" 30 recoverTokenFailMessage = "token query into mongo has failed" 31 saveTokenFailMessage = "token save into mongo has failed" 32 ) 33 34 type AuthResponse struct { 35 AccessToken string `json:"access_token"` 36 AccessTokenExpiresAt int `json:"expires_in"` 37 RefreshToken string `json:"refresh_token"` 38 RefreshTokenCreatedAt int `json:"refresh_expires_in"` 39 TokenType string `json:"token_type"` 40 NotBeforePolicy int `json:"not-before-policy"` 41 SessionState string `json:"session_state"` 42 Scope string `json:"scope"` 43 } 44 45 func authenticate(clientID string, log *log.Log) (string, error) { 46 tk, err := fetchTokenFromStorage(clientID) 47 if err != nil { 48 log.Error(err, recoverTokenFailMessage) 49 } 50 51 if tk != "" { 52 log.InfoWithParams("Token recovered from mongo", "Information", map[string]interface{}{"Content": tk}) 53 return tk, nil 54 } 55 56 return authenticateAndSaveToken(clientID, log) 57 } 58 59 func authenticateAndSaveToken(clientID string, log *log.Log) (string, error) { 60 mu.Lock() 61 defer mu.Unlock() 62 63 tk, err := AuthenticationWithRetryOnBadRequest(log) 64 if err != nil { 65 return "", err 66 } 67 68 token := models.NewToken(clientID, issuerBank, tk) 69 if err := db.SaveToken(token); err != nil { 70 log.Error(err, saveTokenFailMessage) 71 } 72 73 return tk, nil 74 } 75 76 func fetchTokenFromStorage(clientID string) (string, error) { 77 token, err := db.GetTokenByClientIDAndIssuerBank(clientID, issuerBank) 78 if err != nil { 79 if err.Error() == DocumentNotFound { 80 return "", nil 81 } 82 return "", err 83 } 84 85 return token.AccessToken, nil 86 } 87 88 // AuthenticationWithRetryOnBadRequest encapsulates logic for retry access token request once again 89 // in bad request status code. That's because duplicated jti returns this mencioned status code 90 func AuthenticationWithRetryOnBadRequest(log *log.Log) (string, error) { 91 var tk string 92 var err error 93 94 if tk, err = doAuthentication(log); err != nil { 95 if !strings.Contains(err.Error(), BadRequestError) { 96 return "", err 97 } 98 return doAuthentication(log) 99 } 100 101 return tk, nil 102 } 103 104 func doAuthentication(log *log.Log) (string, error) { 105 jwt, err := generateJWT() 106 if err != nil { 107 return "", err 108 } 109 110 AccessTokenPayload["client_assertion"] = jwt 111 AccessTokenPayload["client_id"] = config.Get().StoneClientID 112 113 resp, err := HttpClient.PostFormURLEncoded(config.Get().URLStoneToken, AccessTokenPayload, log) 114 115 if err != nil { 116 return "", err 117 } 118 119 var r AuthResponse 120 err = json.Unmarshal(resp, &r) 121 if err != nil { 122 return "", err 123 } 124 125 return r.AccessToken, nil 126 }