// HandlerAuth implements authorize endpoint func (rh *RequestHandler) HandlerAuth(c web.C, w http.ResponseWriter, r *http.Request) { authreq := &compliance.AuthRequest{} authreq.FromRequest(r) err := authreq.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } var authData compliance.AuthData err = json.Unmarshal([]byte(authreq.Data), &authData) if err != nil { errorResponse := protocols.NewInvalidParameterError("data", authreq.Data) log.WithFields(errorResponse.LogData).Warn(errorResponse.Error()) server.Write(w, errorResponse) return } senderStellarToml, err := rh.StellarTomlResolver.GetStellarTomlByAddress(authData.Sender) if err != nil { log.WithFields(log.Fields{"err": err, "sender": authData.Sender}).Warn("Cannot get stellar.toml of sender") server.Write(w, protocols.InvalidParameterError) return } if senderStellarToml.SigningKey == "" { errorResponse := protocols.NewInvalidParameterError("data.sender", authData.Sender) log.WithFields(errorResponse.LogData).Warn("No SIGNING_KEY in stellar.toml of sender") server.Write(w, errorResponse) return } // Verify signature signatureBytes, err := base64.StdEncoding.DecodeString(authreq.Signature) if err != nil { errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature) log.WithFields(errorResponse.LogData).Warn("Error decoding signature") server.Write(w, errorResponse) return } err = rh.SignatureSignerVerifier.Verify(senderStellarToml.SigningKey, []byte(authreq.Data), signatureBytes) if err != nil { log.WithFields(log.Fields{ "signing_key": senderStellarToml.SigningKey, "data": authreq.Data, "sig": authreq.Signature, }).Warn("Invalid signature") errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature) server.Write(w, errorResponse) return } b64r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(authData.Tx)) var tx xdr.Transaction _, err = xdr.Unmarshal(b64r, &tx) if err != nil { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) log.WithFields(log.Fields{ "err": err, "tx": authData.Tx, }).Warn("Error decoding Transaction XDR") server.Write(w, errorResponse) return } if tx.Memo.Hash == nil { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) log.WithFields(log.Fields{"tx": authData.Tx}).Warn("Transaction does not contain Memo.Hash") server.Write(w, errorResponse) return } // Validate memo preimage hash memoPreimageHashBytes := sha256.Sum256([]byte(authData.Memo)) memoBytes := [32]byte(*tx.Memo.Hash) if memoPreimageHashBytes != memoBytes { errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx) h := xdr.Hash(memoPreimageHashBytes) tx.Memo.Hash = &h var txBytes bytes.Buffer _, err = xdr.Marshal(&txBytes, tx) if err != nil { log.Error("Error mashaling transaction") server.Write(w, protocols.InternalServerError) return } expectedTx := base64.StdEncoding.EncodeToString(txBytes.Bytes()) log.WithFields(log.Fields{"tx": authData.Tx, "expected_tx": expectedTx}).Warn("Memo preimage hash does not equal tx Memo.Hash") server.Write(w, errorResponse) return } var memoPreimage memo.Memo err = json.Unmarshal([]byte(authData.Memo), &memoPreimage) if err != nil { errorResponse := protocols.NewInvalidParameterError("data.memo", authData.Memo) log.WithFields(log.Fields{ "err": err, "memo": authData.Memo, }).Warn("Cannot unmarshal memo preimage") server.Write(w, errorResponse) return } transactionHash, err := submitter.TransactionHash(&tx, rh.Config.NetworkPassphrase) if err != nil { log.WithFields(log.Fields{"err": err}).Warn("Error calculating tx hash") server.Write(w, protocols.InternalServerError) return } response := compliance.AuthResponse{} // Sanctions check if rh.Config.Callbacks.Sanctions == "" { response.TxStatus = compliance.AuthStatusOk } else { resp, err := rh.Client.PostForm( rh.Config.Callbacks.Sanctions, url.Values{"sender": {memoPreimage.Transaction.SenderInfo}}, ) if err != nil { log.WithFields(log.Fields{ "sanctions": rh.Config.Callbacks.Sanctions, "err": err, }).Error("Error sending request to sanctions server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading sanctions server response") server.Write(w, protocols.InternalServerError) return } switch resp.StatusCode { case http.StatusOK: // AuthStatusOk response.TxStatus = compliance.AuthStatusOk case http.StatusAccepted: // AuthStatusPending response.TxStatus = compliance.AuthStatusPending var pendingResponse compliance.PendingResponse err := json.Unmarshal(body, &pendingResponse) if err != nil { // Set default value response.Pending = 600 } else { response.Pending = pendingResponse.Pending } case http.StatusForbidden: // AuthStatusDenied response.TxStatus = compliance.AuthStatusDenied default: log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from sanctions server") server.Write(w, protocols.InternalServerError) return } } // User info if authData.NeedInfo { if rh.Config.Callbacks.AskUser == "" { response.InfoStatus = compliance.AuthStatusDenied // Check AllowedFi tokens := strings.Split(authData.Sender, "*") if len(tokens) != 2 { log.WithFields(log.Fields{ "sender": authData.Sender, }).Warn("Invalid stellar address") server.Write(w, protocols.InternalServerError) return } allowedFi, err := rh.Repository.GetAllowedFiByDomain(tokens[1]) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedFi from DB") server.Write(w, protocols.InternalServerError) return } if allowedFi == nil { // FI not found check AllowedUser allowedUser, err := rh.Repository.GetAllowedUserByDomainAndUserID(tokens[1], tokens[0]) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedUser from DB") server.Write(w, protocols.InternalServerError) return } if allowedUser != nil { response.InfoStatus = compliance.AuthStatusOk } } else { response.InfoStatus = compliance.AuthStatusOk } } else { // Ask user var amount, assetType, assetCode, assetIssuer string if len(tx.Operations) > 0 { operationBody := tx.Operations[0].Body if operationBody.Type == xdr.OperationTypePayment { amount = baseAmount.String(operationBody.PaymentOp.Amount) operationBody.PaymentOp.Asset.Extract(&assetType, &assetCode, &assetIssuer) } else if operationBody.Type == xdr.OperationTypePathPayment { amount = baseAmount.String(operationBody.PathPaymentOp.DestAmount) operationBody.PathPaymentOp.DestAsset.Extract(&assetType, &assetCode, &assetIssuer) } } resp, err := rh.Client.PostForm( rh.Config.Callbacks.AskUser, url.Values{ "amount": {amount}, "asset_code": {assetCode}, "asset_issuer": {assetIssuer}, "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, }, ) if err != nil { log.WithFields(log.Fields{ "ask_user": rh.Config.Callbacks.AskUser, "err": err, }).Error("Error sending request to ask_user server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading ask_user server response") server.Write(w, protocols.InternalServerError) return } switch resp.StatusCode { case http.StatusOK: // AuthStatusOk response.InfoStatus = compliance.AuthStatusOk case http.StatusAccepted: // AuthStatusPending response.InfoStatus = compliance.AuthStatusPending var pendingResponse compliance.PendingResponse err := json.Unmarshal(body, &pendingResponse) if err != nil { // Set default value response.Pending = 600 } else { response.Pending = pendingResponse.Pending } case http.StatusForbidden: // AuthStatusDenied response.InfoStatus = compliance.AuthStatusDenied default: log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from ask_user server") server.Write(w, protocols.InternalServerError) return } } if response.InfoStatus == compliance.AuthStatusOk { // Fetch Info fetchInfoRequest := compliance.FetchInfoRequest{Address: memoPreimage.Transaction.Route} resp, err := rh.Client.PostForm( rh.Config.Callbacks.FetchInfo, fetchInfoRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error sending request to fetch_info server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error reading fetch_info server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != http.StatusOK { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "status": resp.StatusCode, "body": string(body), }).Error("Error response from fetch_info server") server.Write(w, protocols.InternalServerError) return } response.DestInfo = string(body) } } else { response.InfoStatus = compliance.AuthStatusOk } if response.TxStatus == compliance.AuthStatusOk && response.InfoStatus == compliance.AuthStatusOk { authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: hex.EncodeToString(transactionHash[:]), Memo: base64.StdEncoding.EncodeToString(memoBytes[:]), TransactionXdr: authData.Tx, AuthorizedAt: time.Now(), Data: authreq.Data, } err = rh.EntityManager.Persist(authorizedTransaction) if err != nil { log.WithFields(log.Fields{"err": err}).Warn("Error persisting AuthorizedTransaction") server.Write(w, protocols.InternalServerError) return } } server.Write(w, &response) }
// HandlerSend implements /send endpoint func (rh *RequestHandler) HandlerSend(c web.C, w http.ResponseWriter, r *http.Request) { request := &compliance.SendRequest{} request.FromRequest(r) err := request.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } destinationObject, stellarToml, err := rh.FederationResolver.Resolve(request.Destination) if err != nil { log.WithFields(log.Fields{ "destination": request.Destination, "err": err, }).Print("Cannot resolve address") server.Write(w, compliance.CannotResolveDestination) return } if stellarToml.AuthServer == "" { log.Print("No AUTH_SERVER in stellar.toml") server.Write(w, compliance.AuthServerNotDefined) return } var payWithMutator *b.PayWithPath if request.SendMax != "" { // Path payment var sendAsset b.Asset if request.SendAssetCode != "" && request.SendAssetIssuer != "" { sendAsset = b.CreditAsset(request.SendAssetCode, request.SendAssetIssuer) } else if request.SendAssetCode == "" && request.SendAssetIssuer == "" { sendAsset = b.NativeAsset() } else { log.Print("Missing send asset param.") server.Write(w, protocols.MissingParameterError) return } payWith := b.PayWith(sendAsset, request.SendMax) for _, asset := range request.Path { if asset.Code == "" && asset.Issuer == "" { payWith = payWith.Through(b.NativeAsset()) } else { payWith = payWith.Through(b.CreditAsset(asset.Code, asset.Issuer)) } } payWithMutator = &payWith } mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.CreditAmount{ request.AssetCode, request.AssetIssuer, request.Amount, }, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } operationMutator := b.Payment(mutators...) if operationMutator.Err != nil { log.WithFields(log.Fields{ "err": operationMutator.Err, }).Error("Error creating operation") server.Write(w, protocols.InternalServerError) return } // Fetch Sender Info senderInfo := "" if rh.Config.Callbacks.FetchInfo != "" { fetchInfoRequest := compliance.FetchInfoRequest{Address: request.Sender} resp, err := rh.Client.PostForm( rh.Config.Callbacks.FetchInfo, fetchInfoRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error sending request to fetch_info server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "err": err, }).Error("Error reading fetch_info server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != http.StatusOK { log.WithFields(log.Fields{ "fetch_info": rh.Config.Callbacks.FetchInfo, "status": resp.StatusCode, "body": string(body), }).Error("Error response from fetch_info server") server.Write(w, protocols.InternalServerError) return } senderInfo = string(body) } memoPreimage := &memo.Memo{ Transaction: memo.Transaction{ SenderInfo: senderInfo, Route: destinationObject.Memo, Extra: request.ExtraMemo, }, } memoJSON := memoPreimage.Marshal() memoHashBytes := sha256.Sum256(memoJSON) memoMutator := &b.MemoHash{xdr.Hash(memoHashBytes)} transaction, err := submitter.BuildTransaction( request.Source, rh.Config.NetworkPassphrase, operationMutator, memoMutator, ) var txBytes bytes.Buffer _, err = xdr.Marshal(&txBytes, transaction) if err != nil { log.Error("Error mashaling transaction") server.Write(w, protocols.InternalServerError) return } txBase64 := base64.StdEncoding.EncodeToString(txBytes.Bytes()) authData := compliance.AuthData{ Sender: request.Sender, NeedInfo: rh.Config.NeedsAuth, Tx: txBase64, Memo: string(memoJSON), } data, err := json.Marshal(authData) if err != nil { log.Error("Error mashaling authData") server.Write(w, protocols.InternalServerError) return } sig, err := rh.SignatureSignerVerifier.Sign(rh.Config.Keys.SigningSeed, data) if err != nil { log.Error("Error signing authData") server.Write(w, protocols.InternalServerError) return } authRequest := compliance.AuthRequest{ Data: string(data), Signature: sig, } resp, err := rh.Client.PostForm( stellarToml.AuthServer, authRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{ "auth_server": stellarToml.AuthServer, "err": err, }).Error("Error sending request to auth server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading auth server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != 200 { log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from auth server") server.Write(w, protocols.InternalServerError) return } var authResponse compliance.AuthResponse err = json.Unmarshal(body, &authResponse) if err != nil { log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error unmarshalling auth response") server.Write(w, protocols.InternalServerError) return } response := compliance.SendResponse{ AuthResponse: authResponse, TransactionXdr: txBase64, } server.Write(w, &response) }