From aa4e1c8e215e7cdab638e41dbd48fbb34e1f982e Mon Sep 17 00:00:00 2001
From: Bruno Michel <bmichel@menfin.info>
Date: Fri, 26 Feb 2021 14:21:10 +0100
Subject: [PATCH] Show the local time for new connection mail

When sending the new connection mail, the stack will look at the geodb
to find the timezone of the user and will use it to format the time of
the connection in the mail.
---
 assets/locales/en.po           |  3 +++
 assets/locales/fr.po           |  3 +++
 go.mod                         |  1 +
 go.sum                         |  6 ++----
 model/session/login_history.go | 25 +++++++++++++++++++++----
 pkg/i18n/i18n.go               | 31 +++++++++++++++++++++++++++++++
 6 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/assets/locales/en.po b/assets/locales/en.po
index c14f30a22..289ad68b9 100644
--- a/assets/locales/en.po
+++ b/assets/locales/en.po
@@ -3,6 +3,9 @@ msgstr ""
 
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
+msgid "Time Format Long"
+msgstr "the Jan 2 2006 at 15h04"
+
 msgid "Tree Administrative"
 msgstr "Administrative"
 
diff --git a/assets/locales/fr.po b/assets/locales/fr.po
index 641c8a5e3..412f1fd17 100644
--- a/assets/locales/fr.po
+++ b/assets/locales/fr.po
@@ -16,6 +16,9 @@ msgstr ""
 "Language: fr\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
+msgid "Time Format Long"
+msgstr "le 2 Jan 2006 à 15h04"
+
 msgid "Tree Administrative"
 msgstr "Administratif"
 
diff --git a/go.mod b/go.mod
index c0be49911..1078e36b0 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
 	github.com/gofrs/uuid v3.4.0+incompatible
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
+	github.com/goodsign/monday v1.0.0
 	github.com/google/go-querystring v1.0.0
 	github.com/google/gops v0.3.14
 	github.com/gorilla/websocket v1.4.2
diff --git a/go.sum b/go.sum
index ca03ae963..2074f47bf 100644
--- a/go.sum
+++ b/go.sum
@@ -104,8 +104,6 @@ github.com/cozy/goexif2 v0.0.0-20200819113101-00e1cc8cc9d3 h1:5suSF3q7eNhzpwjs+k
 github.com/cozy/goexif2 v0.0.0-20200819113101-00e1cc8cc9d3/go.mod h1:PWaQhEQb7UWuVUvXxpFBscfsXmUASdlmMgq97QGcTwU=
 github.com/cozy/gomail v0.0.0-20170313100128-1395d9a6a6c0 h1:bQVNaGvnUI7m8J8k3hklFVXRT1F+WJcIV6hYHIgjKHE=
 github.com/cozy/gomail v0.0.0-20170313100128-1395d9a6a6c0/go.mod h1:DlX8Rq7OKA0F9I1e0tz6+PCOXkKZ/l6aD+bWxCC6Qfo=
-github.com/cozy/httpcache v0.0.0-20180914105234-d3dc4988de66 h1:b7VTmlsWlhYzJqGLjfhvIiVOpRpNCsOIoV4h0krSkyE=
-github.com/cozy/httpcache v0.0.0-20180914105234-d3dc4988de66/go.mod h1:rLnjIcybyvs+PoCzi4+GmpOVp0+q+qdcuZKnKUKJoF4=
 github.com/cozy/httpcache v0.0.0-20210224123405-3f334f841945 h1:EfeD2CzaZclMHyFxSuaA1BTfqVTLaFwqlASiNNil4nE=
 github.com/cozy/httpcache v0.0.0-20210224123405-3f334f841945/go.mod h1:rLnjIcybyvs+PoCzi4+GmpOVp0+q+qdcuZKnKUKJoF4=
 github.com/cozy/prosemirror-go v0.4.9 h1:urukelN1w2qBP+mU2pz2jGEdF/hCJ2C0/1VgjSV1FCw=
@@ -201,6 +199,8 @@ github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
+github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -515,7 +515,6 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -689,7 +688,6 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
 golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/model/session/login_history.go b/model/session/login_history.go
index a386fb43c..3858cdb4a 100644
--- a/model/session/login_history.go
+++ b/model/session/login_history.go
@@ -1,6 +1,7 @@
 package session
 
 import (
+	"fmt"
 	"net"
 	"net/http"
 	"net/url"
@@ -13,6 +14,7 @@ import (
 	"github.com/cozy/cozy-stack/pkg/consts"
 	"github.com/cozy/cozy-stack/pkg/couchdb"
 	"github.com/cozy/cozy-stack/pkg/couchdb/mango"
+	"github.com/cozy/cozy-stack/pkg/i18n"
 	"github.com/cozy/cozy-stack/pkg/logger"
 	"github.com/mssola/user_agent"
 	maxminddb "github.com/oschwald/maxminddb-golang"
@@ -58,7 +60,7 @@ func (l *LoginEntry) Clone() couchdb.Doc {
 	return &clone
 }
 
-func lookupIP(ip, locale string) (city, subdivision, country string) {
+func lookupIP(ip, locale string) (city, subdivision, country, timezone string) {
 	geodb := config.GetConfig().GeoDB
 	if geodb == "" {
 		return
@@ -80,6 +82,9 @@ func lookupIP(ip, locale string) (city, subdivision, country string) {
 		Country struct {
 			Names map[string]string `maxminddb:"names"`
 		} `maxminddb:"country"`
+		Location struct {
+			TimeZone string `maxminddb:"time_zone"`
+		} `maxminddb:"location"`
 	}
 
 	err = db.Lookup(net.ParseIP(ip), &record)
@@ -104,6 +109,7 @@ func lookupIP(ip, locale string) (city, subdivision, country string) {
 	} else if c, ok := record.Country.Names["en"]; ok {
 		country = c
 	}
+	timezone = record.Location.TimeZone
 	return
 }
 
@@ -118,12 +124,20 @@ func StoreNewLoginEntry(i *instance.Instance, sessionID, clientID string, req *h
 		ip = strings.Split(req.RemoteAddr, ":")[0]
 	}
 
-	city, subdivision, country := lookupIP(ip, i.Locale)
+	city, subdivision, country, timezone := lookupIP(ip, i.Locale)
 	ua := user_agent.New(req.UserAgent())
 
 	browser, _ := ua.Browser()
 	os := ua.OS()
 
+	createdAt := time.Now()
+	if timezone != "" {
+		if loc, err := time.LoadLocation(timezone); err == nil {
+			createdAt = createdAt.In(loc)
+			fmt.Printf("createdAt = %v\n", createdAt)
+		}
+	}
+
 	l := &LoginEntry{
 		IP:                 ip,
 		SessionID:          sessionID,
@@ -134,7 +148,7 @@ func StoreNewLoginEntry(i *instance.Instance, sessionID, clientID string, req *h
 		OS:                 os,
 		Browser:            browser,
 		ClientRegistration: clientID != "",
-		CreatedAt:          time.Now(),
+		CreatedAt:          createdAt,
 	}
 
 	if err := couchdb.CreateDoc(i, l); err != nil {
@@ -183,8 +197,11 @@ func sendLoginNotification(i *instance.Instance, l *LoginEntry) error {
 		activateTwoFALink = settingsURL.String()
 	}
 
+	layout := i.Translate("Time Format Long")
+	time := i18n.LocalizeTime(l.CreatedAt, i.Locale, layout)
+
 	templateValues := map[string]interface{}{
-		"Time":                 l.CreatedAt.Format("2006-01-02 15:04:05Z07:00"),
+		"Time":                 time,
 		"IP":                   l.IP,
 		"Browser":              l.Browser,
 		"OS":                   l.OS,
diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go
index 4e6bf32cb..41543d7dd 100644
--- a/pkg/i18n/i18n.go
+++ b/pkg/i18n/i18n.go
@@ -3,9 +3,11 @@ package i18n
 import (
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/cozy/cozy-stack/pkg/consts"
 	"github.com/cozy/cozy-stack/pkg/logger"
+	"github.com/goodsign/monday"
 	"github.com/leonelquinteros/gotext"
 )
 
@@ -46,3 +48,32 @@ func Translate(key, locale string, vars ...interface{}) string {
 	}
 	return fmt.Sprintf(key, vars...)
 }
+
+// LocalizeTime transforms a date+time in a string for the given locale.
+// The layout is in the same format as the one given to time.Format.
+func LocalizeTime(t time.Time, locale, layout string) string {
+	return monday.Format(t, layout, mondayLocale(locale))
+}
+
+func mondayLocale(locale string) monday.Locale {
+	switch locale {
+	case "de", "de_DE":
+		return monday.LocaleDeDE
+	case "es", "es_ES":
+		return monday.LocaleEsES
+	case "fr", "fr_FR":
+		return monday.LocaleFrFR
+	case "it", "it_IT":
+		return monday.LocaleItIT
+	case "ja", "ja_JP":
+		return monday.LocaleJaJP
+	case "nl", "nl_NL":
+		return monday.LocaleNlNL
+	case "pt", "pt_PT":
+		return monday.LocalePtPT
+	case "ru", "ru_RU":
+		return monday.LocaleRuRU
+	default:
+		return monday.LocaleEnUS
+	}
+}
-- 
GitLab