package communication import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "html/template" "net/http" "net/url" "time" "github.com/rs/zerolog" "base/config" "base/pkg/email" ) type client struct { logger zerolog.Logger endpoint string accessKey string apiVersion string senderAddress string templates *template.Template } func New(logger zerolog.Logger, config *config.AppConfig) email.Email { return &client{ logger: logger, endpoint: config.AzureCommunicationConfig.Endpoint, accessKey: config.AzureCommunicationConfig.AccessKey, apiVersion: config.AzureCommunicationConfig.ApiVersion, senderAddress: config.AzureCommunicationConfig.SenderAddress, } } func (c client) Send(ctx context.Context, params email.Request) (*email.Response, error) { var tpl bytes.Buffer if err := c.templates.ExecuteTemplate(&tpl, generateTemplateName(params.Template.EmailTemplateName), params.Template.Data); err != nil { return nil, err } html := tpl.String() request := &ApiRequest{ SenderAddress: c.senderAddress, Content: ApiContentDto{ Subject: params.Subject, Html: html, }, Recipients: ApiRecipientDto{ To: []ApiRecipientDetailDto{ { Address: params.RecipientAddress, DisplayName: params.UserFullName, }, }, CC: make([]ApiRecipientDetailDto, 0), BCC: make([]ApiRecipientDetailDto, 0), }, } byteBody, err := json.Marshal(&request) if err != nil { return nil, errors.New("marshaling error") } method := "POST" endpoint := c.endpoint u, _ := url.Parse(endpoint) snedPathAndQuery := fmt.Sprintf( "/emails:send?api-version=%s", c.apiVersion, ) date := time.Now().In(time.FixedZone("GMT", 0)).Format("Mon, 02 Jan 2006 15:04:05 GMT") host := u.Host contentHash := computeContentHash(byteBody) stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", method, snedPathAndQuery, date, host, contentHash) signature, err := computeSignature(stringToSign, c.accessKey) if err != nil { return nil, err } authHeader := fmt.Sprintf("HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=%s", signature) fullURL := endpoint + snedPathAndQuery req, _ := http.NewRequest(method, fullURL, bytes.NewReader(byteBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("x-ms-date", date) req.Header.Set("x-ms-content-sha256", contentHash) req.Header.Set("Authorization", authHeader) req.Header.Set("Host", host) client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusAccepted { response := &ApiErrorResponse{} if err := json.NewDecoder(resp.Body).Decode(response); err != nil { return nil, err } c.logger.Info().Msgf("email sending failed. %v", response) return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } c.logger.Info().Msgf("email sending done. %v", resp.Body) response := &email.Response{} if err := json.NewDecoder(resp.Body).Decode(response); err != nil { return nil, err } return response, nil } func computeContentHash(body []byte) string { sum := sha256.Sum256(body) return base64.StdEncoding.EncodeToString(sum[:]) } func computeSignature(stringToSign, base64AccessKey string) (string, error) { key, err := base64.StdEncoding.DecodeString(base64AccessKey) if err != nil { return "", err } mac := hmac.New(sha256.New, key) _, err = mac.Write([]byte(stringToSign)) if err != nil { return "", err } sig := mac.Sum(nil) return base64.StdEncoding.EncodeToString(sig), nil } func generateTemplateName(emailTemplateName email.Template) string { return fmt.Sprintf("%s.html", emailTemplateName.String()) }