Newer
Older
package matcher
import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"regexp"
"time"
"forge.grandlyon.com/npernoud/glcpro/internal/apientreprise"
"forge.grandlyon.com/npernoud/glcpro/internal/franceconnect"
"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"
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])?)*$")
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
)
type MandateDemand struct {
AskerSub string
Sirent string
}
// CreateMatcherServer creates the "matcher", being the server that handles the mandate requessts
func CreateMatcherServer() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/demand", func(w http.ResponseWriter, r *http.Request) {
// Work out who the user is and for what sirent he wants a mandate for
rd := oidcserver.ClientRequestData{}
_, err := tokens.ExtractAndValidateToken(r, "ClientRequestData", &rd, false)
fmt.Printf("Request Data:%v\n", rd)
if err != nil {
http.Error(w, "could not get the initial client request data from cookie", http.StatusInternalServerError)
return
}
// Work out the email of the company CEO
email, err := ioutil.ReadAll(r.Body)
if err != nil || !isEmailValid(string(email)) {
http.Error(w, "email adress is invalid", http.StatusBadRequest)
return
}
// Bundle all that into a token
m := MandateDemand{AskerSub: rd.Id.Sub, Sirent: rd.Sirent}
token, err := tokens.CreateToken(m, time.Now().Add(time.Hour*24*31))
if err != nil {
http.Error(w, "could not create demand token", http.StatusBadRequest)
return
}
// Send a mail to the CEO with the aforementionned token
err = sendMailToMandater(string(email), rd.Id.GivenName+" "+rd.Id.FamilyName, rd.Sirent, token, common.GetDomain(r))
if err != nil {
http.Error(w, "could not send email", http.StatusBadRequest)
return
}
})
mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
// Unpack the token to work out who the mandate is for
md := MandateDemand{}
tokens.ExtractAndValidateToken(r, "", &md, false)
tokens.CreateCookie(md, common.GetDomain(r), "MandateDemand", 60*time.Second, w)
// Set a cookie to redirect to the needed callback route from the front end generic callback
cookie := http.Cookie{Name: "callbackRoute", Domain: common.GetDomain(r), Path: "/", Value: "/api/matcher/callback", MaxAge: 60, Secure: false, HttpOnly: false, SameSite: http.SameSiteLaxMode}
http.SetCookie(w, &cookie)
// Perform an France Connect authentication ... and wait for the callback
franceconnect.RedirectToFranceConnect(w, r)
})
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// Get the mandater identity from
i, err := franceconnect.HandleCallBack(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the redirect url from the cookie
md := MandateDemand{}
_, err = tokens.ExtractAndValidateToken(r, "MandateDemand", &md, false)
if err != nil {
http.Error(w, "could not get the mandate demand data from cookie", http.StatusInternalServerError)
return
}
// Check that the France Connect user can create a mandate : he is the CEO of the company or has a transitive mandate
// Get the data from the API Entreprise
e, _, err := apientreprise.GetData(md.Sirent)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO : Iterate on all MandatairesSociaux and clean the big if, refactor with oidcserver
if len(e.MandatairesSociaux) < 1 {
http.Error(w, "there is no Mandataires Sociaux for this SIREN/T", http.StatusOK)
return
}
if e.MandatairesSociaux[0].Nom == i.FamilyName || e.MandatairesSociaux[0].Prenom == i.GivenName || e.MandatairesSociaux[0].DateNaissance == i.Birthdate {
// Create the mandate
mandate.Create(md.Sirent, md.AskerSub, i.Sub)
http.Redirect(w, r, "/mandatecreated", http.StatusFound)
return
}
w.Write([]byte("the mandate could not be created, you do not seem to be the company CEO"))
// TODO : Inform the original asker that is demand has been validated/rejected
return mux
}
// isEmailValid checks if the email provided passes the required structure and length.
func isEmailValid(e string) bool {
if len(e) < 3 && len(e) > 254 {
return false
}
return emailRegex.MatchString(e)
}
func sendMailToMandater(email string, mandate string, sirent string, token string, hostname string) error {
// Receiver email address.
to := []string{
email,
}
var body bytes.Buffer
mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body.Write([]byte(fmt.Sprintf("Subject: GLC PRO - Demande de validation de mandatement \n%s\n\n", mimeHeaders)))
t.Execute(&body, struct {
Mandate string
Sirent string
Token string
Hostname string
Mandate: mandate,
Sirent: sirent,
Token: token,
Hostname: hostname,
// SetTestMailSender allows overriding mail sender for test purposes
func SetTestModeAndReturnRecorder() *email.EmailRecorder {
mailTemplate = "../../mailtemplate.html"
sender, recorder := email.NewMockSender()
mailSender = sender
return recorder
}