Skip to content

Commit

Permalink
implement auth middleware and prop hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
caioeverest committed Jan 28, 2024
1 parent 6a0c0d5 commit 0de746f
Show file tree
Hide file tree
Showing 25 changed files with 581 additions and 164 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
issues:
exclude-rules:
- path: pkg/user/handler_oauth.go
- path: pkg/user/middleware.go
text: 'SA1029: should not use built-in type string as key for value; define your own type to avoid collisions'
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ install-deps:
run:
@go run main.go

key-gen:
@go run main.go key-gen

new-migration:
@go run main.go migrator create $(migration) go

Expand Down
33 changes: 33 additions & 0 deletions cmd/generate_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import (
"crypto/ed25519"
"encoding/base64"
"fmt"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(generateJWTKey)
}

var (
generateJWTKey = &cobra.Command{
Use: "key-gen",
Short: "Generate a private and a public ED25519 key",
RunE: func(cmd *cobra.Command, args []string) (err error) {
pub, pk, err := ed25519.GenerateKey(nil)
if err != nil {
return err
}

pkb64 := base64.StdEncoding.EncodeToString(pk)
pubb64 := base64.StdEncoding.EncodeToString(pub)

fmt.Printf("PK: %s\n", pkb64)
fmt.Printf("PUB: %s\n", pubb64)
return nil
},
}
)
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/marcopollivier/techagenda/lib/database"
_ "github.com/marcopollivier/techagenda/lib/logger"
"github.com/marcopollivier/techagenda/lib/server"
"github.com/marcopollivier/techagenda/lib/ssr"
"github.com/marcopollivier/techagenda/pkg/event"
"github.com/marcopollivier/techagenda/pkg/lending"
"github.com/marcopollivier/techagenda/pkg/static"
Expand All @@ -26,6 +27,7 @@ var rootCmd = &cobra.Command{
fx.New(
fx.Provide(database.NewDB),
fx.Provide(server.NewHTTPServer),
ssr.Module(),
static.Module(),
user.Module(),
event.Module(),
Expand Down
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ require (
github.com/caarlos0/env/v10 v10.0.0 // indirect
github.com/evanw/esbuild v0.19.11 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
Expand All @@ -20,6 +21,7 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/echo-contrib v0.15.0 // indirect
github.com/labstack/echo/v4 v4.11.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/lib/pq v1.10.9 // indirect
Expand Down Expand Up @@ -49,6 +51,7 @@ require (
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gorm.io/driver/postgres v1.5.4 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -118,13 +120,19 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
Expand All @@ -151,6 +159,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU=
github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
Expand Down Expand Up @@ -374,6 +384,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Expand Down
1 change: 1 addition & 0 deletions lib/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ type Config struct {
LogFormat string `env:"LOG_FORMAT" envDefault:"text"`
DB Database `envPrefix:"DATABASE_"`
Providers Providers `envPrefix:"PROVIDER_"`
JWT JWT `envPrefix:"JWT_"`
}
21 changes: 21 additions & 0 deletions lib/config/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package config

import (
"encoding/base64"
)

type JWT struct {
Private Cert `env:"PRIVATE_KEY,required"`
Public Cert `env:"PUBLIC_KEY,required"`
}

type Cert []byte

func (c *Cert) UnmarshalText(text []byte) error {
out, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return err
}
*c = Cert(out)
return nil
}
110 changes: 110 additions & 0 deletions lib/server/http.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
package server

import (
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io/ioutil"

Check failure on line 9 in lib/server/http.go

View workflow job for this annotation

GitHub Actions / lint

SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details. (staticcheck)
"log/slog"
"net/http"
"strings"
"sync/atomic"
"time"

"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"github.com/markbates/goth/gothic"
"go.uber.org/fx"

"github.com/marcopollivier/techagenda/lib/config"
)

const (
SessionName = "_tech_agenda_s"
CurrentState = "sess"
)

var (
sessionStorage atomic.Value
)

func NewHTTPServer(lc fx.Lifecycle) *echo.Echo {
srv := echo.New()
cfg := config.Get()
maxAge := 30 * (24 * time.Hour)
sessionManager := sessions.NewCookieStore([]byte("secret"))
sessionManager.MaxAge(int(maxAge.Milliseconds()))
srv.Use(session.Middleware(sessionManager))
gothic.Store = sessionManager
sessionStorage.Store(sessionManager)

lc.Append(fx.Hook{
OnStart: func(_ context.Context) error {
slog.Info(fmt.Sprintf("Starting HTTP server at %d", cfg.HTTPPort))
go func() {

if err := srv.Start(fmt.Sprintf(":%d", cfg.HTTPPort)); err != nil {
slog.Error("Fail to start http server", "error", err)
panic(err)
Expand All @@ -29,3 +57,85 @@ func NewHTTPServer(lc fx.Lifecycle) *echo.Echo {
})
return srv
}

func GetSessionStorage() *sessions.CookieStore {
ss, ok := sessionStorage.Load().(*sessions.CookieStore)
if !ok {
return nil
}
return ss
}

// StoreInSession stores a specified key/value pair in the session.
func StoreInSession(value string, req *http.Request, res http.ResponseWriter) error {
session, _ := GetSessionStorage().New(req, SessionName)

if err := updateSessionValue(session, CurrentState, value); err != nil {
return err
}

return session.Save(req, res)
}

// GetFromSession retrieves a previously-stored value from the session.
// If no value has previously been stored at the specified key, it will return an error.
func GetFromSession(req *http.Request) (string, error) {
session, _ := GetSessionStorage().Get(req, SessionName)
value, err := getSessionValue(session, CurrentState)
if err != nil {
return "", errors.New("could not find a matching session for this request")
}

return value, nil
}

// Logout invalidates a user session.
func Logout(res http.ResponseWriter, req *http.Request) error {
session, err := GetSessionStorage().Get(req, SessionName)
if err != nil {
return err
}
session.Options.MaxAge = -1
session.Values = make(map[interface{}]interface{})
err = session.Save(req, res)
if err != nil {
return errors.New("Could not delete user session ")
}
return nil
}

func getSessionValue(session *sessions.Session, key string) (string, error) {
value := session.Values[key]
if value == nil {
return "", fmt.Errorf("could not find a matching session for this request")
}

rdata := strings.NewReader(value.(string))
r, err := gzip.NewReader(rdata)
if err != nil {
return "", err
}
s, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}

return string(s), nil
}

func updateSessionValue(session *sessions.Session, key, value string) error {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write([]byte(value)); err != nil {
return err
}
if err := gz.Flush(); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}

session.Values[key] = b.String()
return nil
}
79 changes: 79 additions & 0 deletions lib/session/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package session

import (
"crypto/ed25519"
"encoding/json"
"errors"
"time"

"github.com/golang-jwt/jwt"
"github.com/marcopollivier/techagenda/lib/config"
"github.com/markbates/goth"
)

type UserSession struct {
ID uint `json:"id"`
Provider string `json:"provider"`
Token string `json:"token"`
AuthUser goth.User `json:"auth_user"`
}

func GenerateJWT(userID uint, auth goth.User) (tokenString string, err error) {
var (
pk = ed25519.PrivateKey(config.Get().JWT.Private)
token = jwt.New(jwt.SigningMethodEdDSA)
claims = token.Claims.(jwt.MapClaims)
sess UserSession
)

sess = UserSession{
ID: userID,
Provider: auth.Provider,
Token: auth.AccessToken,
AuthUser: auth,
}
claims["exp"] = float64(time.Now().Add(24 * time.Hour).UnixMilli())
claims["authorized"] = true
claims["session"] = sess

if tokenString, err = token.SignedString(pk); err != nil {
return "", err
}

return tokenString, nil
}

func UnmarshalSession(tokenString string) (sess UserSession, err error) {
var (
token *jwt.Token
claims jwt.MapClaims
ok bool
bytes []byte
)
if token, err = jwt.Parse(tokenString, jwtParser); err != nil {
return sess, err
}
if !token.Valid {
return sess, errors.New("invalid token session")
}
if claims, ok = token.Claims.(jwt.MapClaims); !ok {
return sess, errors.New("unable to extract claims")
}
if bytes, err = json.Marshal(claims["session"]); err != nil {
return sess, errors.New("unable to extract session")
}
if err = json.Unmarshal(bytes, &sess); err != nil {
return sess, errors.New("unable to parse session")
}
return sess, nil

}

func jwtParser(token *jwt.Token) (any, error) {
key := ed25519.PublicKey(config.Get().JWT.Public)
_, ok := token.Method.(*jwt.SigningMethodEd25519)
if !ok {
return "", errors.New("fail to open session token")
}
return key, nil
}
Loading

0 comments on commit 0de746f

Please sign in to comment.