More work

Signed-off-by: jolheiser <john.olheiser@gmail.com>
main
jolheiser 2022-03-11 22:34:27 -06:00
parent 48792d56e0
commit 6965ada867
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
6 changed files with 178 additions and 25 deletions

48
flag.go 100644
View File

@ -0,0 +1,48 @@
package main
import (
"flag"
"fmt"
"sort"
"github.com/gorilla/securecookie"
)
var (
fs = flag.NewFlagSet("invitea", flag.ExitOnError)
// Optional
jsonLog = fs.Bool("json", false, "Enable JSON logging")
port = fs.Int("port", 8080, "Port to run on")
sessionSecret = fs.String("session-secret", string(securecookie.GenerateRandomKey(32)), "Session secret")
// Required
domain = fs.String("domain", "", "Domain Invitea is running on")
giteaURL = fs.String("gitea.url", "", "Gitea URL")
giteaClientKey = fs.String("gitea.client-key", "", "Gitea OAuth2 Client Key")
giteaClientSecret = fs.String("gitea.client-secret", "", "Gitea OAuth2 Client Secret")
giteaToken = fs.String("gitea.token", "", "Gitea admin token")
)
func requiredFlags() error {
required := map[string]*string{
"domain": domain,
"gitea.url": giteaURL,
"gitea.client-key": giteaClientKey,
"gitea.client-secret": giteaClientSecret,
"gitea.token": giteaToken,
}
var unset []string
for k, v := range required {
if *v == "" {
unset = append(unset, k)
}
}
if len(unset) > 0 {
sort.Strings(unset)
return fmt.Errorf("the following flags were unset, but are required: %v", unset)
}
return nil
}

6
go.mod
View File

@ -3,7 +3,10 @@ module go.jolheiser.com/invitea
go 1.17 go 1.17
require ( require (
code.gitea.io/sdk/gitea v0.15.1
github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/chi/v5 v5.0.7
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.1.1
github.com/markbates/goth v1.69.0 github.com/markbates/goth v1.69.0
github.com/peterbourgon/ff/v3 v3.1.2 github.com/peterbourgon/ff/v3 v3.1.2
github.com/rs/zerolog v1.26.1 github.com/rs/zerolog v1.26.1
@ -13,8 +16,7 @@ require (
github.com/golang/protobuf v1.4.2 // indirect github.com/golang/protobuf v1.4.2 // indirect
github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect github.com/gorilla/mux v1.6.2 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/hashicorp/go-version v1.2.1 // indirect
github.com/gorilla/sessions v1.1.1 // indirect
github.com/pelletier/go-toml v1.6.0 // indirect github.com/pelletier/go-toml v1.6.0 // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect

6
go.sum
View File

@ -31,6 +31,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -114,6 +117,8 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= 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.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -309,6 +314,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

18
main.go
View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -16,9 +15,6 @@ import (
) )
func main() { func main() {
fs := flag.NewFlagSet("invitea", flag.ExitOnError)
jsonLog := fs.Bool("json", false, "Enable JSON logging")
port := fs.Int("port", 8080, "Port to run on")
level := zerolog.InfoLevel level := zerolog.InfoLevel
fs.Func("level", "Logging level (debug, info, error)", func(s string) error { fs.Func("level", "Logging level (debug, info, error)", func(s string) error {
lvl, err := zerolog.ParseLevel(s) lvl, err := zerolog.ParseLevel(s)
@ -38,12 +34,22 @@ func main() {
} }
zerolog.SetGlobalLevel(level) zerolog.SetGlobalLevel(level)
if !*jsonLog { if !*jsonLog {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
} }
r := router.New() if err := requiredFlags(); err != nil {
log.Fatal().Err(err).Msg("")
}
r := router.New(router.Config{
Domain: *domain,
SessionSecret: *sessionSecret,
GiteaURL: *giteaURL,
GiteaClientKey: *giteaClientKey,
GiteaClientSecret: *giteaClientSecret,
GiteaToken: *giteaToken,
})
go func() { go func() {
log.Debug().Msgf("Listening at http://localhost:%d", *port) log.Debug().Msgf("Listening at http://localhost:%d", *port)

View File

@ -1,33 +1,50 @@
package router package router
import ( import (
"fmt"
"net/http"
"strings"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/gorilla/sessions"
"github.com/markbates/goth" "github.com/markbates/goth"
"github.com/markbates/goth/gothic" "github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/gitea" "github.com/markbates/goth/providers/gitea"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"net/http"
) )
func New() *chi.Mux { type Config struct {
// TODO pass in domain, strip ending slashes Domain string
domain := "https://git.jojodev.com" SessionSecret string
clientKey := "18a5e9c3-fc3e-4d78-8564-74ec36531541"
clientSecret := "SZSd2QgciJL4wmPRxrVPnUwtrcGSn5nEI4QRh1aZM3JT" GiteaURL string
callbackURL := "http://localhost:8080/auth/callback" GiteaClientKey string
GiteaClientSecret string
GiteaToken string
}
func New(cfg Config) *chi.Mux {
cfg.GiteaURL = strings.TrimRight(cfg.GiteaURL, "/")
cfg.Domain = strings.TrimRight(cfg.Domain, "/")
callbackURL := fmt.Sprintf("%s/auth/callback", cfg.Domain)
goth.UseProviders( goth.UseProviders(
gitea.NewCustomisedURL(clientKey, clientSecret, callbackURL, gitea.NewCustomisedURL(cfg.GiteaClientKey, cfg.GiteaClientSecret, callbackURL,
domain+"/login/oauth/authorize", fmt.Sprintf("%s/login/oauth/authorize", cfg.GiteaURL),
domain+"/login/oauth/access_token", fmt.Sprintf("%s/login/oauth/access_token", cfg.GiteaURL),
domain+"/api/v1/user", fmt.Sprintf("%s/api/v1/user", cfg.GiteaURL),
), ),
) )
gothStore := sessions.NewCookieStore([]byte(cfg.GiteaClientSecret))
gothStore.Options.HttpOnly = true
gothic.Store = gothStore
gothic.GetProviderName = func(_ *http.Request) (string, error) { gothic.GetProviderName = func(_ *http.Request) (string, error) {
return "gitea", nil return "gitea", nil
} }
store := NewSessionStore(cfg.SessionSecret, cfg.GiteaURL)
r := chi.NewMux() r := chi.NewMux()
r.Use(middleware.Logger) r.Use(middleware.Logger)
r.Use(middleware.Recoverer) r.Use(middleware.Recoverer)
@ -37,26 +54,38 @@ func New() *chi.Mux {
NoColor: true, NoColor: true,
}) })
r.Route("/", func(r chi.Router) {
r.Use(store.Middleware)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf("isAdmin: %t", r.Context().Value("isAdmin").(bool))))
})
})
r.Route("/auth", func(r chi.Router) { r.Route("/auth", func(r chi.Router) {
r.Get("/login", func(w http.ResponseWriter, r *http.Request) { r.Get("/login", func(w http.ResponseWriter, r *http.Request) {
_, err := gothic.CompleteUserAuth(w, r) user, err := gothic.CompleteUserAuth(w, r)
if err != nil { if err != nil {
gothic.BeginAuthHandler(w, r) gothic.BeginAuthHandler(w, r)
return return
} }
if err := store.Auth(w, r, user.AccessToken); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, cfg.Domain, http.StatusFound)
}) })
r.Get("/callback", func(w http.ResponseWriter, r *http.Request) { r.Get("/callback", func(w http.ResponseWriter, r *http.Request) {
_, err := gothic.CompleteUserAuth(w, r) user, err := gothic.CompleteUserAuth(w, r)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized) http.Error(w, err.Error(), http.StatusUnauthorized)
return return
} }
}) if err := store.Auth(w, r, user.AccessToken); err != nil {
r.Get("/logout", func(w http.ResponseWriter, r *http.Request) {
if err := gothic.Logout(w, r); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
}
return return
}
http.Redirect(w, r, cfg.Domain, http.StatusFound)
}) })
}) })

View File

@ -1 +1,63 @@
package router package router
import (
"context"
"net/http"
"code.gitea.io/sdk/gitea"
"github.com/gorilla/sessions"
"github.com/markbates/goth/gothic"
)
const sessionCookie = "_invitea_session"
type SessionStore struct {
Store sessions.Store
GiteaURL string
}
func (s *SessionStore) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sess, err := s.Store.Get(r, sessionCookie)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, ok := sess.Values["authenticated"]; !ok {
gothic.BeginAuthHandler(w, r)
return
}
r = r.WithContext(context.WithValue(r.Context(), "isAdmin", sess.Values["isAdmin"]))
next.ServeHTTP(w, r)
})
}
func (s *SessionStore) Auth(w http.ResponseWriter, r *http.Request, token string) error {
client, err := gitea.NewClient(s.GiteaURL, gitea.SetToken(token))
if err != nil {
return err
}
profile, _, err := client.GetMyUserInfo()
if err != nil {
return err
}
sess, err := s.Store.New(r, sessionCookie)
if err != nil {
return err
}
sess.Values["authenticated"] = true
sess.Values["isAdmin"] = profile.IsAdmin
return s.Store.Save(r, w, sess)
}
func NewSessionStore(sessionSecret, giteURL string) *SessionStore {
store := sessions.NewCookieStore([]byte(sessionSecret))
return &SessionStore{
Store: store,
GiteaURL: giteURL,
}
}