Sessions in etcd KV store instead of cookies

This commit is contained in:
2022-10-30 20:11:36 +01:00
parent c2c6a72f81
commit f4c2d61dc3
41 changed files with 1008 additions and 202 deletions

View File

@@ -4,8 +4,8 @@ import (
"net/http"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
"github.com/spf13/viper"
)
@@ -13,10 +13,10 @@ type APIHandler struct {
idp *identification.IdentificationProvider
config *viper.Viper
services *services.ServicesHandler
cache *cache.CacheHandler
cache cache.CacheHandler
}
func NewAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache *cache.CacheHandler) (*APIHandler, error) {
func NewAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler) (*APIHandler, error) {
return &APIHandler{
idp: idp,
config: cfg,

101
handlers/api/export.go Normal file
View File

@@ -0,0 +1,101 @@
package api
import (
"encoding/csv"
"fmt"
"net/http"
"sort"
"strconv"
"github.com/gorilla/mux"
)
type FlatMaps []map[string]any
func (maps FlatMaps) GetHeaders() (res []string) {
keys := map[string]bool{}
for _, m := range maps {
for k, _ := range m {
if _, ok := keys[k]; !ok {
keys[k] = true
res = append(res, k)
}
}
}
sort.Strings(res)
return
}
func (maps FlatMaps) GetValues() (res [][]string) {
headers := maps.GetHeaders()
for _, m := range maps {
line := []string{}
for _, k := range headers {
if v, ok := m[k]; ok && v != nil {
line = append(line, fmt.Sprint(v))
} else {
line = append(line, "")
}
}
res = append(res, line)
}
return
}
func (h APIHandler) CacheExport(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
cacheid := vars["cacheid"]
d, err := h.cache.Get(cacheid)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusNotFound)
return
}
if data, ok := d.([]any); ok {
flatmaps := FlatMaps{}
//fmt.Println(data)
for _, v := range data {
fmt.Println(v)
fm := map[string]any{}
flatten("", v.(map[string]any), fm)
fmt.Println(fm)
flatmaps = append(flatmaps, fm)
}
fmt.Println(flatmaps)
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=export-%s.csv", cacheid))
c := csv.NewWriter(w)
c.Write(flatmaps.GetHeaders())
c.WriteAll(flatmaps.GetValues())
return
}
w.WriteHeader(http.StatusNotFound)
}
func flatten(prefix string, src map[string]any, dest map[string]any) {
if len(prefix) > 0 {
prefix += "."
}
for k, v := range src {
switch child := v.(type) {
case map[string]any:
flatten(prefix+k, child, dest)
case []any:
for i := 0; i < len(child); i++ {
dest[prefix+k+"."+strconv.Itoa(i)] = child[i]
}
default:
fmt.Println(prefix+k, " : ", v)
dest[prefix+k] = v
}
}
}

View File

@@ -18,6 +18,7 @@ func (h APIHandler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
fmt.Println("issue retrieving token")
w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -36,11 +37,20 @@ func (h APIHandler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
redirect := "/app/"
if session.Values["redirect"] != nil && session.Values["redirect"] != "" {
fmt.Println("no redirect stuff")
redirect = session.Values["redirect"].(string)
delete(session.Values, "redirect")
}
session.Save(r, w)
if err = session.Save(r, w); err != nil {
fmt.Println(err)
panic(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
fmt.Println(session.Values)
fmt.Println("redirect")
http.Redirect(w, r, redirect, http.StatusFound)
}

View File

@@ -229,7 +229,7 @@ func (h *ApplicationHandler) AdministrationGroupInviteAdmin(w http.ResponseWrite
}
key := base64.RawURLEncoding.EncodeToString(b)
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour) // 1 week TTL
data := map[string]any{
"group": groupresp.Group.ToStorageType().Data["name"],
@@ -247,6 +247,89 @@ func (h *ApplicationHandler) AdministrationGroupInviteAdmin(w http.ResponseWrite
return
}
func (h *ApplicationHandler) AdministrationGroupInviteMember(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
groupid := vars["groupid"]
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), &groupsmanagement.GetGroupRequest{
Id: groupid,
Namespace: "parcoursmob_organizations",
})
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
group := groupresp.Group.ToStorageType()
r.ParseForm()
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &accounts.GetAccountUsernameRequest{
Username: r.FormValue("username"),
Namespace: "parcoursmob",
})
if err == nil {
account := accountresp.Account.ToStorageType()
account.Data["groups"] = append(account.Data["groups"].([]any), group.ID)
as, _ := accounts.AccountFromStorageType(&account)
_, err = h.services.GRPC.MobilityAccounts.UpdateData(
context.TODO(),
&accounts.UpdateDataRequest{
Account: as,
},
)
fmt.Println(err)
data := map[string]any{
"group": group.Data["name"],
}
if err := h.emailing.Send("onboarding.existing_member", r.FormValue("username"), data); err != nil {
fmt.Println(err)
}
http.Redirect(w, r, "/app/group/settings", http.StatusFound)
return
} else {
// Onboard now administrator
onboarding := map[string]any{
"username": r.FormValue("username"),
"group": group.ID,
"admin": false,
}
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
key := base64.RawURLEncoding.EncodeToString(b)
h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour) // 1 week TTL
data := map[string]any{
"group": group.Data["name"],
"key": key,
}
if err := h.emailing.Send("onboarding.new_member", r.FormValue("username"), data); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
http.Redirect(w, r, "/app/administration/groups/"+group.ID, http.StatusFound)
return
}
func (h *ApplicationHandler) members() ([]*accounts.Account, error) {
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), &accounts.GetAccountsRequest{
Namespaces: []string{"parcoursmob"},

View File

@@ -5,7 +5,7 @@ import (
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
"git.coopgo.io/coopgo-platform/emailing"
"github.com/spf13/viper"
)
@@ -14,11 +14,11 @@ type ApplicationHandler struct {
config *viper.Viper
Renderer *renderer.Renderer
services *services.ServicesHandler
cache *cache.CacheHandler
cache cache.CacheHandler
emailing *emailing.Mailer
}
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache *cache.CacheHandler, emailing *emailing.Mailer) (*ApplicationHandler, error) {
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache cache.CacheHandler, emailing *emailing.Mailer) (*ApplicationHandler, error) {
templates_root := cfg.GetString("templates.root")
renderer := renderer.NewRenderer(cfg, templates_root)
return &ApplicationHandler{

View File

@@ -9,6 +9,7 @@ import (
"image/png"
"log"
"net/http"
"sort"
"strconv"
"strings"
"time"
@@ -20,6 +21,7 @@ import (
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
"git.coopgo.io/coopgo-platform/groups-management/storage"
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
"github.com/google/uuid"
"github.com/gorilla/mux"
"google.golang.org/protobuf/types/known/structpb"
@@ -29,12 +31,21 @@ type BeneficiariesForm struct {
FirstName string `json:"first_name" validate:"required"`
LastName string `json:"last_name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Birthdate *time.Time `json:"birthdate"`
Birthdate *time.Time `json:"birthdate" validate:"required"`
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
FileNumber string `json:"file_number" validate:"required"`
Address any `json:"address,omitempty"`
Gender string `json:"gender"`
}
type BeneficiariesByName []mobilityaccountsstorage.Account
func (e BeneficiariesByName) Len() int { return len(e) }
func (e BeneficiariesByName) Less(i, j int) bool {
return e[i].Data["first_name"].(string) < e[j].Data["first_name"].(string)
}
func (e BeneficiariesByName) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (h *ApplicationHandler) BeneficiariesList(w http.ResponseWriter, r *http.Request) {
accounts, err := h.beneficiaries(r)
@@ -44,6 +55,8 @@ func (h *ApplicationHandler) BeneficiariesList(w http.ResponseWriter, r *http.Re
return
}
sort.Sort(BeneficiariesByName(accounts))
cacheid := uuid.NewString()
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
@@ -252,8 +265,8 @@ func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool {
return true
}
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]any, error) {
var accounts = []any{}
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
var accounts = []mobilityaccountsstorage.Account{}
g := r.Context().Value(identification.GroupKey)
if g == nil {
return accounts, errors.New("no group provided")
@@ -301,6 +314,7 @@ func parseBeneficiariesForm(r *http.Request) (map[string]any, error) {
Email: r.PostFormValue("email"),
Birthdate: date,
PhoneNumber: r.PostFormValue("phone_number"),
FileNumber: r.PostFormValue("file_number"),
Gender: r.PostFormValue("gender"),
}

View File

@@ -81,8 +81,8 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
journeys, err = session.Journeys(context.Background(), request)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
// w.WriteHeader(http.StatusBadRequest)
// return
}
//CARPOOL
@@ -93,8 +93,6 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
// departuredatetime.Format("2006-01-02"), departuredatetime.Add(24*time.Hour).Format("2006-01-02"))
carpoolrequest := "https://api.rdex.ridygo.fr/journeys.json"
fmt.Println(carpoolrequest)
client := &http.Client{}
req, err := http.NewRequest("GET", carpoolrequest, nil)
if err != nil {
@@ -113,11 +111,21 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
fmt.Println(err)
}
err = json.NewDecoder(resp.Body).Decode(&carpoolresults)
if err != nil {
fmt.Println(err)
if err == nil && resp.StatusCode == http.StatusOK {
err = json.NewDecoder(resp.Body).Decode(&carpoolresults)
if err != nil {
fmt.Println(err)
}
if carpoolresults == nil {
carpoolresults = []any{}
}
} else {
carpoolresults = []any{}
}
fmt.Println(carpoolresults)
// Vehicles
vehiclerequest := &fleets.GetVehiclesRequest{

View File

@@ -3,8 +3,9 @@ package auth
import (
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
"git.coopgo.io/coopgo-platform/emailing"
"github.com/spf13/viper"
)
@@ -13,10 +14,11 @@ type AuthHandler struct {
config *viper.Viper
services *services.ServicesHandler
Renderer *renderer.Renderer
cache *cache.CacheHandler
cache cache.CacheHandler
emailing *emailing.Mailer
}
func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache *cache.CacheHandler) (*AuthHandler, error) {
func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler, emailing *emailing.Mailer) (*AuthHandler, error) {
templates_root := cfg.GetString("templates.root")
renderer := renderer.NewRenderer(cfg, templates_root)
return &AuthHandler{
@@ -25,5 +27,6 @@ func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider
services: svc,
Renderer: renderer,
cache: cache,
emailing: emailing,
}, nil
}

View File

@@ -0,0 +1,97 @@
package auth
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"net/http"
"time"
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
)
func (h *AuthHandler) LostPasswordInit(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
r.ParseForm()
email := r.FormValue("email")
if email != "" {
account, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &grpcapi.GetAccountUsernameRequest{
Username: email,
Namespace: "parcoursmob",
})
if err != nil {
fmt.Println(err)
http.Redirect(w, r, "/app/", http.StatusFound)
return
}
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
key := base64.RawURLEncoding.EncodeToString(b)
passwordretrieval := map[string]any{
"username": email,
"account_id": account.Account.Id,
"key": key,
}
h.cache.PutWithTTL("retrieve-password/"+key, passwordretrieval, 72*time.Hour)
if err := h.emailing.Send("auth.retrieve_password", email, passwordretrieval); err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/app/", http.StatusFound)
}
}
h.Renderer.LostPasswordInit(w, r)
}
func (h *AuthHandler) LostPasswordRecover(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
key := r.FormValue("key")
recover, err := h.cache.Get("retrieve-password/" + key)
if err != nil {
fmt.Println(err)
h.Renderer.LostPasswordRecoverKO(w, r, key)
return
}
if r.Method == "POST" {
newpassword := r.FormValue("password")
if newpassword == "" {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Password is empty"))
return
}
_, err := h.services.GRPC.MobilityAccounts.ChangePassword(context.TODO(), &grpcapi.ChangePasswordRequest{
Id: recover.(map[string]any)["account_id"].(string),
Password: newpassword,
})
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
}
err = h.cache.Delete("retrieve-password/" + key)
if err != nil {
fmt.Println(err)
}
http.Redirect(w, r, "/app/", http.StatusFound)
}
h.Renderer.LostPasswordRecover(w, r, recover)
}

View File

@@ -16,7 +16,7 @@ func (h *AuthHandler) Onboarding(w http.ResponseWriter, r *http.Request) {
onboarding, err := h.cache.Get("onboarding/" + key)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusInternalServerError)
h.Renderer.AuthOnboardingKO(w, r, key)
return
}
@@ -72,6 +72,12 @@ func (h *AuthHandler) Onboarding(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
return
}
err = h.cache.Delete("onboarding/" + key)
if err != nil {
fmt.Println(err)
}
http.Redirect(w, r, "/app/", http.StatusFound)
}