Skip to content

Commit

Permalink
Preparing OAuth 2.0 based login (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
oxisto authored Dec 28, 2023
1 parent 8610188 commit e530300
Show file tree
Hide file tree
Showing 27 changed files with 317 additions and 90 deletions.
91 changes: 81 additions & 10 deletions cmd/moneyd/moneyd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,31 @@
package main

import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"strings"
"time"

"github.com/oxisto/money-gopher/gen/portfoliov1connect"
"github.com/oxisto/money-gopher/persistence"
"github.com/oxisto/money-gopher/service/portfolio"
"github.com/oxisto/money-gopher/service/securities"
"github.com/oxisto/money-gopher/ui"

"connectrpc.com/connect"
"github.com/MicahParks/keyfunc/v3"
"github.com/alecthomas/kong"
"github.com/golang-jwt/jwt/v5"
"github.com/lmittmann/tint"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
oauth2 "github.com/oxisto/oauth2go"
"github.com/oxisto/oauth2go/login"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"

"github.com/oxisto/money-gopher/gen/portfoliov1connect"
"github.com/oxisto/money-gopher/persistence"
"github.com/oxisto/money-gopher/service/portfolio"
"github.com/oxisto/money-gopher/service/securities"
"github.com/oxisto/money-gopher/ui"
)

var cmd moneydCmd
Expand All @@ -52,8 +59,9 @@ func main() {

func (cmd *moneydCmd) Run() error {
var (
w = os.Stdout
level = slog.LevelInfo
w = os.Stdout
level = slog.LevelInfo
authSrv *oauth2.AuthorizationServer
)

if cmd.Debug {
Expand All @@ -74,8 +82,22 @@ func (cmd *moneydCmd) Run() error {
db, err := persistence.OpenDB(persistence.Options{})
if err != nil {
slog.Error("Error while opening database", tint.Err(err))
return err
}

authSrv = oauth2.NewServer(
":8000",
oauth2.WithClient("dashboard", "", "http://localhost:5173/callback"),
oauth2.WithPublicURL("http://localhost:8000"),
login.WithLoginPage(
login.WithUser("money", "money"),
),
oauth2.WithAllowedOrigins("*"),
)
go authSrv.ListenAndServe()

interceptors := connect.WithInterceptors(NewAuthInterceptor())

mux := http.NewServeMux()
// The generated constructors return a path and a plain net/http
// handler.
Expand All @@ -84,8 +106,8 @@ func (cmd *moneydCmd) Run() error {
DB: db,
SecuritiesClient: portfoliov1connect.NewSecuritiesServiceClient(http.DefaultClient, portfolio.DefaultSecuritiesServiceURL),
},
)))
mux.Handle(portfoliov1connect.NewSecuritiesServiceHandler(securities.NewService(db)))
), interceptors))
mux.Handle(portfoliov1connect.NewSecuritiesServiceHandler(securities.NewService(db), interceptors))
mux.Handle("/", ui.SvelteKitHandler("/"))

err = http.ListenAndServe(
Expand Down Expand Up @@ -121,3 +143,52 @@ func handleCORS(h http.Handler) http.Handler {
}
})
}

func NewAuthInterceptor() connect.UnaryInterceptorFunc {
interceptor := func(next connect.UnaryFunc) connect.UnaryFunc {
k, err := keyfunc.NewDefault([]string{"http://localhost:8000/certs"})
if err != nil {
slog.Error("Error while setting up JWKS", tint.Err(err))
}

return connect.UnaryFunc(func(
ctx context.Context,
req connect.AnyRequest,
) (connect.AnyResponse, error) {
var (
claims jwt.RegisteredClaims
auth string
token string
err error
ok bool
)
auth = req.Header().Get("Authorization")
if auth == "" {
return nil, connect.NewError(
connect.CodeUnauthenticated,
errors.New("no token provided"),
)
}

token, ok = strings.CutPrefix(auth, "Bearer ")
if !ok {
return nil, connect.NewError(
connect.CodeUnauthenticated,
errors.New("no token provided"),
)
}

_, err = jwt.ParseWithClaims(token, &claims, k.Keyfunc)
if err != nil {
return nil, connect.NewError(
connect.CodeUnauthenticated,
err,
)
}

ctx = context.WithValue(ctx, "claims", claims)
return next(ctx, req)
})
}
return connect.UnaryInterceptorFunc(interceptor)
}
14 changes: 13 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
module github.com/oxisto/money-gopher

go 1.21
go 1.21.5

require (
connectrpc.com/connect v1.14.0
github.com/MicahParks/keyfunc/v3 v3.1.1
github.com/alecthomas/kong v0.8.1
github.com/fatih/color v1.16.0
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/jotaen/kong-completion v0.0.6
github.com/lmittmann/tint v1.0.3
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.19
github.com/oxisto/assert v0.0.6
github.com/oxisto/oauth2go v0.12.0
github.com/posener/complete v1.2.3
golang.org/x/net v0.19.0
golang.org/x/text v0.14.0
google.golang.org/protobuf v1.32.0
)

require (
github.com/MicahParks/jwkset v0.5.4 // indirect
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)

// needed until https://github.com/golang/oauth2/issues/615 is resolved
require (
github.com/golang/protobuf v1.5.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
)

replace github.com/posener/complete v1.2.3 => github.com/oxisto/complete v0.0.0-20231209194436-0b605e2b5bff
30 changes: 24 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
connectrpc.com/connect v1.13.0 h1:lGs5maZZzWOOD+PFFiOt5OncKmMsk9ZdPwpy5jcmaYg=
connectrpc.com/connect v1.13.0/go.mod h1:uHAFHtYgeSZJxXrkN1IunDpKghnTXhYbVh0wW4StPW0=
connectrpc.com/connect v1.14.0 h1:PDS+J7uoz5Oui2VEOMcfz6Qft7opQM9hPiKvtGC01pA=
connectrpc.com/connect v1.14.0/go.mod h1:uoAq5bmhhn43TwhaKdGKN/bZcGtzPW1v+ngDTn5u+8s=
github.com/MicahParks/jwkset v0.5.4 h1:59s9OUNIKF3g+IXYm3pa4vPXXEudRNetyy3+H6KpKdw=
github.com/MicahParks/jwkset v0.5.4/go.mod h1:fOx7dCX+XgPDzcRbZzi9DMY3vyebWXmsz7XPqstr3ms=
github.com/MicahParks/keyfunc/v3 v3.1.1 h1:ghC5jcuU4/TTQQ9Ns7TEVuhnscQOH+WL4//Jmsy5/DA=
github.com/MicahParks/keyfunc/v3 v3.1.1/go.mod h1:Qmrhb9tkHX1i/kCiLAPDOCWIEfN9yq7u/tkP16lmLL8=
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
Expand All @@ -12,7 +14,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand All @@ -27,32 +34,43 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/oxisto/assert v0.0.6 h1:Z/wRt0qndURRof+eOGr7GbcJ6BHZT2nyZd9diuZHS8o=
github.com/oxisto/assert v0.0.6/go.mod h1:07ANKfyBm6j+pZk1qArFueno6fCoEGKvPbPeJSQkH3s=
github.com/oxisto/complete v0.0.0-20231209194436-0b605e2b5bff h1:WiQOAXar+naQAYzv3P01lE1dagFrc61ZC/fbg9z5wuc=
github.com/oxisto/complete v0.0.0-20231209194436-0b605e2b5bff/go.mod h1:wEf3y/0bTolv0kZjmKzgbU7L+PvFfm6KoWGeP/f2oCQ=
github.com/oxisto/oauth2go v0.12.0 h1:MQd9ZdI7NO/hNrbmuLyuR4kUT2sbHhZeEBHUSdlUjFM=
github.com/oxisto/oauth2go v0.12.0/go.mod h1:IKjnDnmIiXQTaHYhuWgDMXDg8kqZzLYb8ClQs3r0n6Q=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
17 changes: 14 additions & 3 deletions service/portfolio/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package portfolio

import (
"context"
"fmt"

moneygopher "github.com/oxisto/money-gopher"
"github.com/oxisto/money-gopher/finance"
Expand Down Expand Up @@ -69,14 +70,16 @@ func (svc *service) GetPortfolioSnapshot(ctx context.Context, req *connect.Reque
// Retrieve market value of filtered securities
secres, err = svc.securities.ListSecurities(
context.Background(),
connect.NewRequest(&portfoliov1.ListSecuritiesRequest{
forwardAuth(connect.NewRequest(&portfoliov1.ListSecuritiesRequest{
Filter: &portfoliov1.ListSecuritiesRequest_Filter{
SecurityNames: names,
},
}),
}), req),
)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
return nil, connect.NewError(connect.CodeInternal,
fmt.Errorf("internal error while calling ListSecurities on securities service: %w", err),
)
}

// Make a map out of the securities list so we can access it easier
Expand Down Expand Up @@ -143,3 +146,11 @@ func keys[M ~map[K]V, K comparable, V any](m M) (keys []K) {

return keys
}

// forwardAuth uses the authorization header of [authenticatedReq] to
// authenticate [req]. This is a little workaround, until we have proper
// service-to-service authentication.
func forwardAuth[T any, S any](req *connect.Request[T], authenticatedReq *connect.Request[S]) *connect.Request[T] {
req.Header().Set("Authorization", authenticatedReq.Header().Get("Authorization"))
return req
}
26 changes: 26 additions & 0 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-svelte": "^2.30.0",
"inter-ui": "^4.0.0",
"oidc-client-ts": "^2.4.0",
"postcss": "^8.4.27",
"prettier": "^3.0.1",
"prettier-plugin-svelte": "^3.0.3",
Expand Down
10 changes: 0 additions & 10 deletions ui/src/hooks.server.js

This file was deleted.

Loading

0 comments on commit e530300

Please sign in to comment.