Skip to content

Commit

Permalink
Lots of stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Albin Vass committed May 26, 2024
1 parent 2020ecb commit 49fe814
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ devenv.local.nix

# pre-commit
.pre-commit-config.yaml

.env
7 changes: 7 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
splitfree-backend = {
exec = "go run ./splitfree-backend";
process-compose = {
environment = [
''AUTH0_CALLBACK_URL=''${AUTH0_CALLBACK_URL}''
''AUTH0_DOMAIN=''${AUTH0_DOMAIN}''
''AUTH0_CLIENT_ID=''${AUTH0_CLIENT_ID}''
''AUTH0_CLIENT_SECRET=''${AUTH0_CLIENT_SECRET}''
"SPLITFREE_CANONICAL_URL=http://localhost:3000"
];
depends_on = {
postgres = { condition = "process_healthy"; };
};
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ go 1.21.7

require (
entgo.io/ent v0.13.1
github.com/coreos/go-oidc/v3 v3.10.0
github.com/danielgtaylor/huma/v2 v2.17.0
github.com/go-chi/chi/v5 v5.0.12
github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9
github.com/sirupsen/logrus v1.9.3
golang.org/x/oauth2 v0.20.0
)

require (
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
Expand All @@ -23,6 +27,7 @@ require (
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tj
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danielgtaylor/huma/v2 v2.17.0 h1:alxef5oO5tcDNmbIf+amjVsxwWYE1HkoNxKH2xGH8ZY=
github.com/danielgtaylor/huma/v2 v2.17.0/go.mod h1:fFOnahr3rZdFha4rqDq7rjb8q3CPuZvCjoP37qg8fTI=
Expand All @@ -16,6 +18,8 @@ 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/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
Expand Down Expand Up @@ -68,10 +72,14 @@ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
Expand Down
56 changes: 56 additions & 0 deletions splitfree-backend/authenticator/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// platform/authenticator/auth.go

package authenticator

import (
"context"
"errors"
"os"

"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)

// Authenticator is used to authenticate our users.
type Authenticator struct {
*oidc.Provider
oauth2.Config
}

// New instantiates the *Authenticator.
func New() (*Authenticator, error) {
provider, err := oidc.NewProvider(
context.Background(),
"https://"+os.Getenv("AUTH0_DOMAIN")+"/",
)
if err != nil {
return nil, err
}

conf := oauth2.Config{
ClientID: os.Getenv("AUTH0_CLIENT_ID"),
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile"},
}

return &Authenticator{
Provider: provider,
Config: conf,
}, nil
}

// VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken.
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, errors.New("no id_token field in oauth2 token")
}

oidcConfig := &oidc.Config{
ClientID: a.ClientID,
}

return a.Verifier(oidcConfig).Verify(ctx, rawIDToken)
}
83 changes: 83 additions & 0 deletions splitfree-backend/callback/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// web/app/callback/callback.go

package callback

import (
"context"
"encoding/base64"
"encoding/json"
"net/http"

"github.com/albinvass/splitfree/splitfree-backend/authenticator"
"github.com/danielgtaylor/huma/v2"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)

type CallbackInput struct {
Session http.Cookie `cookie:"session"`
State uuid.UUID `query:"state"`
Code string `query:"code"`
}

type CallbackOutput struct {
Status int
Cookies []http.Cookie `header:"Set-Cookie"`
Url string `header:"Location"`
}

// Handler for our callback.
func Handler(auth *authenticator.Authenticator) func(context.Context, *CallbackInput) (*CallbackOutput, error) {
return func(ctx context.Context, callbackInput *CallbackInput) (*CallbackOutput, error) {
state, err := uuid.Parse(callbackInput.Session.Value)
if err != nil {
return nil, err
}
if callbackInput.State != state {
return nil, huma.Error400BadRequest("Invalid state parameter.")
}

// Exchange an authorization code for a token.
token, err := auth.Exchange(ctx, callbackInput.Code)
if err != nil {
return nil, huma.Error401Unauthorized("Failed to exchange an authorization code for a token.")
}

idToken, err := auth.VerifyIDToken(ctx, token)
if err != nil {
return nil, huma.Error500InternalServerError("Failed to verify ID Token.")
}

var profile map[string]interface{}
if err := idToken.Claims(&profile); err != nil {
return nil, huma.Error500InternalServerError("", err)
}

accessTokenCookie := http.Cookie{
Name: "access_token",
Value: token.AccessToken,
}

profileJson, err := json.Marshal(profile)
if err != nil {
return nil, huma.Error500InternalServerError("", err)
}

profileJsonBase64 := base64.StdEncoding.EncodeToString(profileJson)

log.Infof("storing cookie: %s", profileJsonBase64)
profileCookie := http.Cookie{
Name: "profile",
Value: profileJsonBase64,
}

return &CallbackOutput{
Status: http.StatusTemporaryRedirect,
Cookies: []http.Cookie{
accessTokenCookie,
profileCookie,
},
Url: "/user",
}, nil
}
}
39 changes: 39 additions & 0 deletions splitfree-backend/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package login

import (
"context"
"github.com/albinvass/splitfree/splitfree-backend/authenticator"
"github.com/google/uuid"
"golang.org/x/oauth2"
"net/http"
"time"
)

type LoginOutput struct {
Status int
Url string `header:"Location"`
Cookie http.Cookie `header:"Set-Cookie"`
}

func Handler(auth *authenticator.Authenticator) func(context.Context, *struct{}) (*LoginOutput, error) {
return func(ctx context.Context, _ *struct{}) (*LoginOutput, error) {
state := uuid.New()

url := auth.AuthCodeURL(state.String(), oauth2.AccessTypeOnline)

cookie := http.Cookie{
Name: "session",
Value: state.String(),
Path: "/",
MaxAge: int(time.Hour.Seconds()),
Secure: false,
HttpOnly: true,
}

return &LoginOutput{
Status: http.StatusTemporaryRedirect,
Url: url,
Cookie: cookie,
}, nil
}
}
44 changes: 44 additions & 0 deletions splitfree-backend/logout/logout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// web/app/logout/logout.go

package logout

import (
"context"
"github.com/danielgtaylor/huma/v2"
"net/http"
"net/url"
"os"
)

type LogoutInput struct {
}

type LogoutOutput struct {
Status int
Url string `header:"Location"`
}

// Handler for our logout.
func Handler(ctx context.Context, loginInput *LogoutInput) (*LogoutOutput, error) {
logoutUrl, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout")
if err != nil {
return nil, huma.Error500InternalServerError("", err)
}

canonicalUrl := os.Getenv("SPLITFREE_CANONICAL_URL")

returnTo, err := url.Parse(canonicalUrl)
if err != nil {
return nil, huma.Error500InternalServerError("", err)
}

parameters := url.Values{}
parameters.Add("returnTo", returnTo.String())
parameters.Add("client_id", os.Getenv("AUTH0_CLIENT_ID"))
logoutUrl.RawQuery = parameters.Encode()

return &LogoutOutput{
Status: http.StatusTemporaryRedirect,
Url: logoutUrl.String(),
}, nil
}
33 changes: 26 additions & 7 deletions splitfree-backend/splitfree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@ import (
"fmt"
"net/http"

"github.com/albinvass/splitfree/splitfree-backend/authenticator"
"github.com/albinvass/splitfree/splitfree-backend/callback"
"github.com/albinvass/splitfree/splitfree-backend/ent"
"github.com/albinvass/splitfree/splitfree-backend/login"
"github.com/albinvass/splitfree/splitfree-backend/logout"
"github.com/albinvass/splitfree/splitfree-backend/user"

"github.com/albinvass/splitfree/splitfree-backend/ent/user"
schemaUser "github.com/albinvass/splitfree/splitfree-backend/ent/user"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
"github.com/danielgtaylor/huma/v2/adapters/humachi"

"github.com/go-chi/chi/v5"

"github.com/danielgtaylor/huma/v2/humacli"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -40,7 +48,7 @@ func (s *SplitfreeBackend) Close() error {
}

func (s *SplitfreeBackend) ensureUser(ctx context.Context, name string, email string) error {
user, err := s.dbClient.User.Query().Where(user.Name(name)).All(ctx)
user, err := s.dbClient.User.Query().Where(schemaUser.Name(name)).All(ctx)
if err != nil {
return err
}
Expand Down Expand Up @@ -75,18 +83,29 @@ func (s *SplitfreeBackend) Run() error {
}

if err := s.InitDB(ctx); err != nil {
panic(err)
panic(fmt.Errorf("failed to initialize database: %v", err))
}

log.Info("successfully created schema")

r := http.NewServeMux()
api := humago.New(r, huma.DefaultConfig("My API", "1.0.0"))
r := chi.NewMux()
config := huma.DefaultConfig("My API", "1.0.0")
api := humachi.New(r, config)

auth, err := authenticator.New()
if err != nil {
panic(err)
}

huma.Get(api, "/login", login.Handler(auth))
huma.Get(api, "/callback", callback.Handler(auth))
huma.Get(api, "/user", user.Handler)
huma.Get(api, "/logout", logout.Handler)

huma.Put(api, "/api/expense", s.CreateExpense)
huma.Get(api, "/api/expenses", s.GetExpenses)

huma.Get(api, "/api/users", s.GetUsers)

hooks.OnStart(func() {
log.Infof("listening on: %s", s.listenAddress)
http.ListenAndServe(fmt.Sprintf(":%d", options.Port), r)
Expand Down
Loading

0 comments on commit 49fe814

Please sign in to comment.