diff --git a/TODO.md b/TODO.md index 323b7afdba378689b6f159f38294bd30d7b7c05c..ba8d5f96d5c19e0932f539c8b04b5de28b9dbff7 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ [ ] Tests : matcher, mandatement and missing FC [ ] OIDC : correct id token and user info -[ ] Remove useless fmt.Print [ ] Business : SIRET handling (work out SIREN for mandatement) [ ] Business : mandate duration [ ] Business : allow user to see his mandates and delete them @@ -8,3 +7,4 @@ [ ] Admin view, with Client ID and Client Secret Management [ ] TODOs scattered around the code [ ] UI : message animations +[ ] Remove useless fmt.Print / console.log diff --git a/internal/clientstub/main.js b/internal/clientstub/main.js index cb1441e5f63568e1a68c36a84d91d40d087707fa..d715211750006ad840b9eaffe471fb53f48a034c 100644 --- a/internal/clientstub/main.js +++ b/internal/clientstub/main.js @@ -5,18 +5,16 @@ document.addEventListener("DOMContentLoaded", async () => { }); const query = new URLSearchParams(window.location.search); const code = query.get("code"); - console.log(code); // Exchange the code for an id token (not secure, for demo purposes ONLY, since client ID is not checked) if (query != undefined && query != "") { try { - const response = await fetch( - "http://localhost:8080/api/oidc/token?code=" + + const response = await fetch("http://localhost:8080/api/oidc/token", { + method: "POST", + body: + "code=" + encodeURIComponent(code) + "&client_id=an_id&client_secret=a_secret&grant_type=authorization_code", - { - method: "GET", - } - ); + }); if (response.status !== 200) { throw new Error(`Could not get token (status ${response.status})`); } diff --git a/internal/matcher/matcher.go b/internal/matcher/matcher.go index 341f944346d24f6b750cf7bdf47edf26e3985128..8dd7b2491dfdb6194e1450390f16b819843c1312 100644 --- a/internal/matcher/matcher.go +++ b/internal/matcher/matcher.go @@ -6,7 +6,6 @@ import ( "html/template" "io/ioutil" "net/http" - "net/smtp" "regexp" "time" @@ -15,16 +14,21 @@ import ( "forge.grandlyon.com/npernoud/glcpro/internal/mandate" "forge.grandlyon.com/npernoud/glcpro/internal/oidcserver" "forge.grandlyon.com/npernoud/glcpro/pkg/common" + "forge.grandlyon.com/npernoud/glcpro/pkg/email" "forge.grandlyon.com/npernoud/glcpro/pkg/tokens" ) var ( - mailTemplate = "mailtemplate.html" - emailSenderAddress = common.StringValueFromEnv("EMAIL_SENDER_ADDRESS", "") - emailSenderPassword = common.StringValueFromEnv("EMAIL_SENDER_PASSWORD", "") - emailSMTPServer = common.StringValueFromEnv("EMAIL_SMTP_SERVER", "") - emailSMTPPort = common.StringValueFromEnv("EMAIL_SMTP_PORT", "") - emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + mailTemplate = "mailtemplate.html" + mailConfig = email.EmailConfig{ + Username: common.StringValueFromEnv("EMAIL_SENDER_ADDRESS", ""), + Password: common.StringValueFromEnv("EMAIL_SENDER_PASSWORD", ""), + ServerHost: common.StringValueFromEnv("EMAIL_SMTP_SERVER", ""), + ServerPort: common.StringValueFromEnv("EMAIL_SMTP_PORT", ""), + SenderAddr: common.StringValueFromEnv("EMAIL_SENDER_ADDRESS", ""), + } + mailSender = email.NewSender(mailConfig) + emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") ) type MandateDemand struct { @@ -144,9 +148,6 @@ func sendMailToMandater(email string, mandate string, sirent string, token strin email, } - // Authentication. - auth := smtp.PlainAuth("", emailSenderAddress, emailSenderPassword, emailSMTPServer) - t, _ := template.ParseFiles(mailTemplate) var body bytes.Buffer @@ -167,5 +168,5 @@ func sendMailToMandater(email string, mandate string, sirent string, token strin }) // Sending email. - return smtp.SendMail(emailSMTPServer+":"+emailSMTPPort, auth, emailSenderAddress, to, body.Bytes()) + return mailSender.Send(to, body.Bytes()) } diff --git a/internal/matcher/matcher_test.go b/internal/matcher/matcher_test.go index 44ec96a6773e7851aa4641c4b3009bc6c89bfe7f..5a9371ef0e8538fdb3b2b95b3f084117893f90da 100644 --- a/internal/matcher/matcher_test.go +++ b/internal/matcher/matcher_test.go @@ -1,9 +1,16 @@ package matcher -import "testing" +import ( + "strings" + "testing" + + "forge.grandlyon.com/npernoud/glcpro/pkg/email" +) func Test_sendMailToMandater(t *testing.T) { mailTemplate = "../../mailtemplate.html" + sender, recorder := email.NewMockSender() + mailSender = sender type args struct { email string mandate string @@ -22,6 +29,9 @@ func Test_sendMailToMandater(t *testing.T) { if err := sendMailToMandater(tt.args.email, tt.args.mandate, tt.args.sirent, tt.args.token, "http://localhost"); (err != nil) != tt.wantErr { t.Errorf("sendMailToMandater() error = %v, wantErr %v", err, tt.wantErr) } + if !strings.Contains(recorder.Msg(), "Demande de mandatement") || !strings.Contains(recorder.Msg(), "http://localhost") { + t.Errorf("received body is not what is expected") + } }) } } diff --git a/internal/matcher/test.sh b/internal/matcher/test.sh deleted file mode 100755 index 391faadfe8881a4a1795f72c49f105be1c67d418..0000000000000000000000000000000000000000 --- a/internal/matcher/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -export EMAIL_SENDER_ADDRESS=glcpro@alpha.grandlyon.com -export EMAIL_SENDER_PASSWORD=*** -export EMAIL_SMTP_SERVER=mail.alpha.grandlyon.com -export EMAIL_SMTP_PORT=587 -go test . diff --git a/pkg/email/email.go b/pkg/email/email.go new file mode 100644 index 0000000000000000000000000000000000000000..4259f72394130f631ef24da057756500c718a6be --- /dev/null +++ b/pkg/email/email.go @@ -0,0 +1,57 @@ +package email + +import ( + "net/smtp" +) + +type EmailConfig struct { + Username string + Password string + ServerHost string + ServerPort string + SenderAddr string +} + +type EmailSender interface { + Send(to []string, body []byte) error +} + +func NewSender(conf EmailConfig) EmailSender { + return &emailSender{conf, smtp.SendMail} +} + +type emailSender struct { + conf EmailConfig + send func(string, smtp.Auth, string, []string, []byte) error +} + +func (e *emailSender) Send(to []string, body []byte) error { + addr := e.conf.ServerHost + ":" + e.conf.ServerPort + auth := smtp.PlainAuth("", e.conf.Username, e.conf.Password, e.conf.ServerHost) + return e.send(addr, auth, e.conf.SenderAddr, to, body) +} + +func NewMockSender() (EmailSender, *EmailRecorder) { + f, r := mockSend(nil) + return &emailSender{send: f}, r +} + +func mockSend(errToReturn error) (func(string, smtp.Auth, string, []string, []byte) error, *EmailRecorder) { + r := new(EmailRecorder) + return func(addr string, a smtp.Auth, from string, to []string, msg []byte) error { + *r = EmailRecorder{addr, a, from, to, msg} + return errToReturn + }, r +} + +type EmailRecorder struct { + addr string + auth smtp.Auth + from string + to []string + msg []byte +} + +func (r *EmailRecorder) Msg() string { + return string(r.msg) +} diff --git a/pkg/email/email_test.go b/pkg/email/email_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dbedcc9e8037a13645f539541a56b291f03c45c9 --- /dev/null +++ b/pkg/email/email_test.go @@ -0,0 +1,18 @@ +package email + +import ( + "testing" +) + +func TestEmail_SendSuccessful(t *testing.T) { + sender, recorder := NewMockSender() + body := "Hello World" + err := sender.Send([]string{"me@example.com"}, []byte(body)) + + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if string(recorder.msg) != body { + t.Errorf("wrong message body.\n\nexpected: %v\n got: %s", body, recorder.msg) + } +}