Big refactoring of PARCOURSMOB - Initial commit

This commit is contained in:
2022-08-11 17:26:55 +02:00
commit 7225d027c9
65 changed files with 10276 additions and 0 deletions

104
utils/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,104 @@
package cache
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/spf13/viper"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/namespace"
)
type CacheHandler struct {
*clientv3.Client
}
func NewCacheHandler(cfg *viper.Viper) (*CacheHandler, error) {
var (
endpoints = cfg.GetStringSlice("cache.storage.etcd.endpoints")
prefix = cfg.GetString("cache.storage.etcd.prefix")
)
cli, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: 5 * time.Second,
})
if err != nil {
return nil, err
}
cli.KV = namespace.NewKV(cli.KV, prefix)
cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix)
cli.Lease = namespace.NewLease(cli.Lease, prefix)
return &CacheHandler{
Client: cli,
}, nil
}
func (s *CacheHandler) Put(k string, v any) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
// _, err = s.Client.KV.Put(context.TODO(), k, data.String())
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_, err = s.Client.KV.Put(ctx, k, string(data))
cancel()
if err != nil {
return err
}
return nil
}
func (s *CacheHandler) PutWithTTL(k string, v any, duration time.Duration) error {
lease, err := s.Client.Lease.Grant(context.TODO(), int64(duration.Seconds()))
if err != nil {
return err
}
data, err := json.Marshal(v)
if err != nil {
return err
}
// _, err = s.Client.KV.Put(context.TODO(), k, data.String(), clientv3.WithLease(lease.ID))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_, err = s.Client.KV.Put(ctx, k, string(data), clientv3.WithLease(lease.ID))
cancel()
if err != nil {
return err
}
return nil
}
func (s *CacheHandler) Get(k string) (any, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
resp, err := s.Client.KV.Get(ctx, k)
cancel()
if err != nil {
return nil, err
}
for _, v := range resp.Kvs {
var data any
err := json.Unmarshal([]byte(v.Value), &data)
if err != nil {
return nil, err
}
// We return directly as we want to last revision of value
return data, nil
}
return nil, errors.New(fmt.Sprintf("no value %v", k))
}
func (s *CacheHandler) Delete(k string) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_, err := s.Client.KV.Delete(ctx, k)
cancel()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package formvalidators
import "github.com/go-playground/validator/v10"
type FormValidator struct {
*validator.Validate
}
func New() (f FormValidator) {
validate := validator.New()
validate.RegisterValidation("phoneNumber", PhoneNumber)
f.Validate = validate
return
}

View File

@@ -0,0 +1,15 @@
package formvalidators
import (
"regexp"
"github.com/go-playground/validator/v10"
)
const phoneNumberRegexStreing = "^((\\+)33|0)[1-9](\\d{2}){4}$"
var phoneNumberRegex = regexp.MustCompile(phoneNumberRegexStreing)
func PhoneNumber(fl validator.FieldLevel) bool {
return phoneNumberRegex.MatchString(fl.Field().String())
}

26
utils/icons/svg-icons.go Normal file
View File

@@ -0,0 +1,26 @@
package icons
import (
"fmt"
"html/template"
)
type IconSet struct {
Icons map[string]string
}
func NewIconSet(set map[string]string) IconSet {
return IconSet{
Icons: set,
}
}
func (i IconSet) Icon(name string, classes string) template.HTML {
icon, ok := i.Icons[name]
if !ok {
return template.HTML("")
}
return template.HTML(fmt.Sprintf(icon, classes))
}

View File

@@ -0,0 +1,74 @@
package identification
import (
"context"
"fmt"
"net/http"
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
)
const GroupKey ContextKey = "group"
const RolesKey ContextKey = "roles"
func (p *IdentificationProvider) GroupsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value(ClaimsKey).(map[string]any)
session, _ := p.SessionsStore.Get(r, "parcoursmob_session")
o, ok := session.Values["organization"]
if !ok || o == nil {
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
return
}
org := o.(string)
claimgroups, ok := claims["groups"].([]any)
if !ok {
fmt.Println("cast issue")
w.WriteHeader(http.StatusInternalServerError)
return
}
for _, group := range claimgroups {
if group == org {
request := &groupsmanagement.GetGroupRequest{
Id: group.(string),
}
resp, err := p.Services.GRPC.GroupsManagement.GetGroup(context.TODO(), request)
if err != nil {
delete(session.Values, "organization")
session.Save(r, w)
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
return
}
ctx := context.WithValue(r.Context(), GroupKey, resp.Group.ToStorageType())
roles := map[string]bool{}
for _, role := range claimgroups {
//TODO handle flexible roles / roles discovery
if role == fmt.Sprintf("%s:admin", org) {
roles[role.(string)] = true
}
}
ctx = context.WithValue(ctx, RolesKey, roles)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
}
// Session organization is not in the available groups
delete(session.Values, "organization")
session.Save(r, w)
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
})
}

View File

@@ -0,0 +1,117 @@
package identification
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"net/http"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
"github.com/coreos/go-oidc"
"github.com/gorilla/sessions"
"github.com/spf13/viper"
"golang.org/x/oauth2"
)
type ContextKey string
const IdtokenKey ContextKey = "idtoken"
const ClaimsKey ContextKey = "claims"
type IdentificationProvider struct {
SessionsStore sessions.Store
Provider *oidc.Provider
OAuth2Config oauth2.Config
TokenVerifier *oidc.IDTokenVerifier
Services *services.ServicesHandler
}
func NewIdentificationProvider(cfg *viper.Viper, services *services.ServicesHandler) (*IdentificationProvider, error) {
var (
providerURL = cfg.GetString("identification.oidc.provider")
clientID = cfg.GetString("identification.oidc.client_id")
clientSecret = cfg.GetString("identification.oidc.client_secret")
redirectURL = cfg.GetString("identification.oidc.redirect_url")
sessionsSecret = cfg.GetString("identification.sessions.secret")
)
provider, err := oidc.NewProvider(context.Background(), providerURL)
if err != nil {
return nil, err
}
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{oidc.ScopeOpenID, "groups", "profile"},
}
var store = sessions.NewCookieStore([]byte(sessionsSecret))
verifier := provider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})
return &IdentificationProvider{
SessionsStore: store,
Provider: provider,
OAuth2Config: oauth2Config,
TokenVerifier: verifier,
Services: services,
}, nil
}
func (p *IdentificationProvider) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, _ := p.SessionsStore.Get(r, "parcoursmob_session")
if session.Values["idtoken"] == nil || session.Values["idtoken"] == "" {
state, err := newState()
if err != nil {
panic(err)
}
session.Values["state"] = state
session.Save(r, w)
http.Redirect(w, r, p.OAuth2Config.AuthCodeURL(state), http.StatusFound)
return
}
idtoken, err := p.TokenVerifier.Verify(context.Background(), session.Values["idtoken"].(string))
if err != nil {
state, err := newState()
if err != nil {
panic(err)
}
session.Values["state"] = state
delete(session.Values, "idtoken")
http.Redirect(w, r, p.OAuth2Config.AuthCodeURL(state), http.StatusFound)
return
}
var claims map[string]any
err = idtoken.Claims(&claims)
if err != nil {
fmt.Println(err)
}
ctx := context.WithValue(r.Context(), IdtokenKey, idtoken)
ctx = context.WithValue(ctx, ClaimsKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func newState() (string, error) {
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}

View File

@@ -0,0 +1,37 @@
package profilepictures
import (
"fmt"
"image"
"image/color"
"image/draw"
"github.com/fogleman/gg"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
func DefaultProfilePicture(initials string) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
col := color.RGBA{36, 56, 135, 255}
white := color.RGBA{255, 255, 255, 255}
point := fixed.Point26_6{fixed.I(40), fixed.I(200)}
draw.Draw(img, img.Bounds(), &image.Uniform{col}, image.Point{X: 0, Y: 0}, draw.Src)
ff, err := gg.LoadFontFace("themes/default/fonts/bitter.ttf", 150.0)
if err != nil {
fmt.Println(err)
return img
}
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(white),
Face: ff,
Dot: point,
}
d.DrawString(initials)
return img
}