Réorganisation des packages + renommage du projet en gitea-kan
This commit is contained in:
72
internal/config/config.go
Normal file
72
internal/config/config.go
Normal file
@ -0,0 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
ini "gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Debug bool
|
||||
HTTP HTTPConfig
|
||||
Gitea GiteaConfig
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Address string
|
||||
PublicDir string
|
||||
}
|
||||
|
||||
type GiteaConfig struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
RedirectURL string
|
||||
LogoutURL string
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
Scopes []string
|
||||
APIBaseURL string
|
||||
}
|
||||
|
||||
// NewFromFile retrieves the configuration from the given file
|
||||
func NewFromFile(filepath string) (*Config, error) {
|
||||
config := NewDefault()
|
||||
|
||||
cfg, err := ini.Load(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.ValueMapper = os.ExpandEnv
|
||||
|
||||
if err := cfg.MapTo(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func NewDefault() *Config {
|
||||
return &Config{
|
||||
Debug: false,
|
||||
HTTP: HTTPConfig{
|
||||
Address: ":3000",
|
||||
PublicDir: "${GITEA_APP_PUBDIR}",
|
||||
},
|
||||
Gitea: GiteaConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
func Dump(config *Config, w io.Writer) error {
|
||||
cfg := ini.Empty()
|
||||
if err := cfg.ReflectFrom(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := cfg.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
9
internal/config/provider.go
Normal file
9
internal/config/provider.go
Normal file
@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
import "gitlab.com/wpetit/goweb/service"
|
||||
|
||||
func ServiceProvider(config *Config) service.Provider {
|
||||
return func(ctn *service.Container) (interface{}, error) {
|
||||
return config, nil
|
||||
}
|
||||
}
|
33
internal/config/service.go
Normal file
33
internal/config/service.go
Normal file
@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
const ServiceName service.Name = "config"
|
||||
|
||||
// From retrieves the config service in the given container
|
||||
func From(container *service.Container) (*Config, error) {
|
||||
service, err := container.Service(ServiceName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
srv, ok := service.(*Config)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Must retrieves the config service in the given container or panic otherwise
|
||||
func Must(container *service.Container) *Config {
|
||||
srv, err := From(container)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return srv
|
||||
}
|
65
internal/middleware/auth.go
Normal file
65
internal/middleware/auth.go
Normal file
@ -0,0 +1,65 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
"gitlab.com/wpetit/goweb/service/session"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionOAuth2AccessToken = "accessToken"
|
||||
SessionOAuth2State = "oauth2State"
|
||||
)
|
||||
|
||||
func Authenticate(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
|
||||
sess, err := session.Must(ctn).Get(w, r)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve session"))
|
||||
}
|
||||
|
||||
accessToken, ok := sess.Get(SessionOAuth2AccessToken).(string)
|
||||
|
||||
if !ok || accessToken == "" {
|
||||
|
||||
state := uuid.New()
|
||||
sess.Set(SessionOAuth2State, state)
|
||||
if err := sess.Save(w, r); err != nil {
|
||||
panic(errors.Wrap(err, "could not save session"))
|
||||
}
|
||||
|
||||
giteaOAuth2Config := GiteaOAuth2Config(ctn)
|
||||
url := giteaOAuth2Config.AuthCodeURL(state)
|
||||
http.Redirect(w, r, url, http.StatusSeeOther)
|
||||
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func GiteaOAuth2Config(ctn *service.Container) *oauth2.Config {
|
||||
conf := config.Must(ctn)
|
||||
return &oauth2.Config{
|
||||
RedirectURL: conf.Gitea.RedirectURL,
|
||||
ClientID: conf.Gitea.ClientID,
|
||||
ClientSecret: conf.Gitea.ClientSecret,
|
||||
Scopes: conf.Gitea.Scopes,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: conf.Gitea.AuthURL,
|
||||
TokenURL: conf.Gitea.TokenURL,
|
||||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
},
|
||||
}
|
||||
}
|
31
internal/route/helper.go
Normal file
31
internal/route/helper.go
Normal file
@ -0,0 +1,31 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/template"
|
||||
"gitlab.com/wpetit/goweb/template/html"
|
||||
)
|
||||
|
||||
func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Data) template.Data {
|
||||
ctn := container.Must(r.Context())
|
||||
data, err := template.Extend(data,
|
||||
html.WithFlashes(w, r, ctn),
|
||||
)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error while extending template data"))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func generateRandomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
23
internal/route/logout.go
Normal file
23
internal/route/logout.go
Normal file
@ -0,0 +1,23 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/session"
|
||||
)
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
conf := config.Must(ctn)
|
||||
sess, err := session.Must(ctn).Get(w, r)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve session"))
|
||||
}
|
||||
if err := sess.Delete(w, r); err != nil {
|
||||
panic(errors.Wrap(err, "could not delete session"))
|
||||
}
|
||||
http.Redirect(w, r, conf.Gitea.LogoutURL, http.StatusSeeOther)
|
||||
}
|
46
internal/route/oauth2.go
Normal file
46
internal/route/oauth2.go
Normal file
@ -0,0 +1,46 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/session"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctn := container.Must(r.Context())
|
||||
sess, err := session.Must(ctn).Get(w, r)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve session"))
|
||||
}
|
||||
|
||||
expectedState := sess.Get(middleware.SessionOAuth2State)
|
||||
state := r.FormValue("state")
|
||||
|
||||
if state != expectedState {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
giteaOAuth2Config := middleware.GiteaOAuth2Config(ctn)
|
||||
|
||||
code := r.FormValue("code")
|
||||
token, err := giteaOAuth2Config.Exchange(oauth2.NoContext, code)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not exchange oauth2 token"))
|
||||
}
|
||||
|
||||
sess.Set(middleware.SessionOAuth2AccessToken, token.AccessToken)
|
||||
sess.Set(middleware.SessionOAuth2State, "")
|
||||
|
||||
if err := sess.Save(w, r); err != nil {
|
||||
panic(errors.Wrap(err, "could not save session"))
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
|
||||
}
|
44
internal/route/proxy.go
Normal file
44
internal/route/proxy.go
Normal file
@ -0,0 +1,44 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/service/session"
|
||||
)
|
||||
|
||||
func proxyAPIRequest(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
conf := config.Must(ctn)
|
||||
|
||||
apiBaseURL, err := url.Parse(conf.Gitea.APIBaseURL)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not parse api base url"))
|
||||
}
|
||||
|
||||
sess, err := session.Must(ctn).Get(w, r)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "could not retrieve session"))
|
||||
}
|
||||
|
||||
accessToken := sess.Get(middleware.SessionOAuth2AccessToken)
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(apiBaseURL)
|
||||
proxy.Director = func(r *http.Request) {
|
||||
r.Host = apiBaseURL.Host
|
||||
r.URL.Scheme = apiBaseURL.Scheme
|
||||
r.URL.Host = apiBaseURL.Host
|
||||
r.Header.Add("Authorization", fmt.Sprintf("token %s", accessToken))
|
||||
spew.Dump(r)
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
33
internal/route/route.go
Normal file
33
internal/route/route.go
Normal file
@ -0,0 +1,33 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
|
||||
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware"
|
||||
"github.com/go-chi/chi"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
"gitlab.com/wpetit/goweb/static"
|
||||
)
|
||||
|
||||
func Mount(r *chi.Mux, config *config.Config) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get("/callback", handleOAuth2Callback)
|
||||
|
||||
// Authenticated routes
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.Authenticate)
|
||||
r.Get("/logout", handleLogout)
|
||||
r.Get("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest)).ServeHTTP)
|
||||
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func html5PushStateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctn := container.Must(r.Context())
|
||||
conf := config.Must(ctn)
|
||||
indexFile := path.Join(conf.HTTP.PublicDir, "index.html")
|
||||
http.ServeFile(w, r, indexFile)
|
||||
}
|
Reference in New Issue
Block a user