From 33d4b54eefb2d0c59e3d84a8acb009339991de0b Mon Sep 17 00:00:00 2001 From: Hugo Thunnissen Date: Sat, 23 Nov 2019 18:58:30 +0100 Subject: [PATCH] Implement retrieval and storage of oauth_{client,secret} +http redirect --- go.mod | 6 +- go.sum | 15 +++++ main.go | 190 +++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 186 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index f2e2b6b..8230304 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,16 @@ go 1.13 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/dougm/goflymake v0.0.0-20140731161037-3b9634ef394a // indirect github.com/gorilla/websocket v1.4.1 github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 + github.com/nsf/gocode v0.0.0-20190302080247-5bee97b48836 // indirect github.com/oklog/ulid v1.3.1 + github.com/rogpeppe/godef v1.1.1 // indirect github.com/valyala/fasttemplate v1.1.0 // indirect go.etcd.io/bbolt v1.3.3 golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba // indirect - golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect + golang.org/x/tools v0.0.0-20191122232904-2a6ccf25d769 // indirect + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index b4b08bb..d7f2e24 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +9fans.net/go v0.0.0-20181112161441-237454027057 h1:OcHlKWkAMJEF1ndWLGxp5dnJQkYM/YImUOvsBoz6h5E= +9fans.net/go v0.0.0-20181112161441-237454027057/go.mod h1:diCsxrliIURU9xsYtjCp5AbpQKqdhKmf0ujWDUSkfoY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dougm/goflymake v0.0.0-20140731161037-3b9634ef394a h1:S4+OSVOtl5a40p2ZgV7rA6YLl0wcjehR2jQVyWK1/UE= +github.com/dougm/goflymake v0.0.0-20140731161037-3b9634ef394a/go.mod h1:5K1XZIXX56t7gg4jDBoVXBH9ABugPa830vLJiD/XPHI= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= @@ -13,10 +17,14 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/nsf/gocode v0.0.0-20190302080247-5bee97b48836 h1:oc3CL18CoGhyOQJ7HDa9gJAde33bwI8Vi28zLdIzJVc= +github.com/nsf/gocode v0.0.0-20190302080247-5bee97b48836/go.mod h1:6Q8/OMaaKAgTX7/jt2bOXVDrm1eJhoNd+iwzghR7jvs= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 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/rogpeppe/godef v1.1.1 h1:NujOtt9q9vIClRTB3sCZpavac+NMRaIayzrcz1h4fSE= +github.com/rogpeppe/godef v1.1.1/go.mod h1:oEo1eMy1VUEHUzUIX4F7IqvMJRiz9UId44mvnR8oPlQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -34,6 +42,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2eP golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -41,6 +51,11 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20181130195746-895048a75ecf/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191122232904-2a6ccf25d769 h1:nIPDpirk90v9eLG0L8usrehSoJ1rWd6wX7BdjAKhZ4I= +golang.org/x/tools v0.0.0-20191122232904-2a6ccf25d769/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 645eb1a..c0b49ad 100644 --- a/main.go +++ b/main.go @@ -4,16 +4,22 @@ package main // from mastodon instances. import ( + "bytes" "encoding/json" + "errors" "fmt" + "io/ioutil" "net/http" + "net/url" "os" + "strings" "github.com/gorilla/websocket" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/labstack/gommon/log" "go.etcd.io/bbolt" + "gopkg.in/yaml.v2" ) var ( @@ -29,6 +35,16 @@ type App struct { AuthMap *AuthenticationMap Logger *echo.Logger DB *bbolt.DB + Config *AppConfig +} + +type AppConfig struct { + AppName string `yaml:"app_name"` + AppHost string `yaml:"app_host"` + AppScheme string `yaml:"app_scheme"` + DBPath string `yaml:"db_path"` + AppScopes []string `yaml:"app_scopes"` + Website string `yaml:"website"` } func (a *App) authRequestWebSocket(c echo.Context) error { @@ -55,18 +71,11 @@ func (a *App) authRequestWebSocket(c echo.Context) error { return nil } -func (a *App) authRequestRedirect(c echo.Context) error { - requestId := c.Param("request_id") - - authRequest := a.AuthMap.GetRequestByID(requestId) - - if authRequest == nil { - return c.String(http.StatusNotFound, "No authentication request found by this ID") - } - +func (a *App) GetInstanceDataByRequest(request *AuthRequest) (*Instance, error) { var instance *Instance - a.DB.View(func(t *bbolt.Tx) error { - instanceJson := t.Bucket([]byte("instances")).Get([]byte(authRequest.Instance)) + + err := a.DB.View(func(t *bbolt.Tx) error { + instanceJson := t.Bucket([]byte("instances")).Get([]byte(request.Instance)) if instanceJson == nil { instance = nil @@ -80,34 +89,128 @@ func (a *App) authRequestRedirect(c echo.Context) error { return err }) - return c.String(http.StatusOK, "BLAAT") + if instance == nil && err == nil { + (*a.Logger).Infof("No instance data known for host '%s', requesting credentials from host\n", request.Instance) + + instance, err = a.GetCredentialsFromInstanceHost(request.Instance) - //return c.Redirect(http.StatusTemporaryRedirect, ) + if err != nil { + return nil, err + } + + err = a.DB.Update(func(t *bbolt.Tx) error { + instanceJson, err := json.Marshal(instance) + + if err != nil { + return err + } + + err = t.Bucket([]byte("instances")).Put([]byte(request.Instance), instanceJson) + + return err + }) + + return instance, err + } + + return nil, err } -func main() { - arguments := os.Args[1:] +func (a *App) authRequestRedirect(c echo.Context) error { + requestId := c.Param("request_id") - if len(arguments) != 2 { - fmt.Fprintln(os.Stderr, "Expected 2 command line argumens, but got : ", len(arguments)) - fmt.Fprintln(os.Stderr, "Usage: generic-mastodon-authenticator APP_NAME DB_PATH") - os.Exit(1) + authRequest := a.AuthMap.GetRequestByID(requestId) + + if authRequest == nil { + return c.String(http.StatusNotFound, "No authentication request found by this ID") + } + + instance, err := a.GetInstanceDataByRequest(authRequest) + + if err != nil { + (*a.Logger).Error(err) + } + + if instance == nil { + return c.String( + http.StatusInternalServerError, + "Something has gone wrong, please note down the current time and contact the server admin", + ) + } + + values := &url.Values{ + "client_id": []string{instance.ID}, + "redirect_uri": []string{a.Config.AppScheme + "://" + a.Config.AppHost + "/confirm"}, + "scopes": []string{strings.Join(a.Config.AppScopes, " ")}, + "response_type": []string{"code"}, } - database := arguments[1] + return c.Redirect( + http.StatusTemporaryRedirect, + "https://"+authRequest.Instance+"/oauth/authorize?"+values.Encode(), + ) +} - db, err := bbolt.Open(database, 0600, nil) +func (c *AppConfig) FromFile(filePath string) error { + yamlfile, err := ioutil.ReadFile(filePath) if err != nil { - log.Fatal(err) + return err + } + + err = yaml.Unmarshal(yamlfile, c) + + if err != nil { + return err } - db.Update(func(t *bbolt.Tx) error { + if c.AppName == "" || c.AppHost == "" || c.DBPath == "" || c.Website == "" { + return errors.New("app_name, app_host, db_path and website are required config parameters") + } + + if c.AppScheme == "" { + c.AppScheme = "https" + } + + return nil +} + +func (a *App) InitializeDB() error { + (*a.Logger).Info("Initializing DB from file: ", a.Config.DBPath) + + db, err := bbolt.Open(a.Config.DBPath, 0600, nil) + + if err != nil { + return err + } + + err = db.Update(func(t *bbolt.Tx) error { _, err := t.CreateBucketIfNotExists([]byte("instances")) return err }) + a.DB = db + return nil +} + +func main() { + arguments := os.Args[1:] + + if len(arguments) != 1 { + fmt.Fprintln(os.Stderr, "Expected 1 command line argument, but got : ", len(arguments)) + fmt.Fprintln(os.Stderr, "Usage: generic-mastodon-authenticator CONFIG_PATH") + os.Exit(1) + } + + config := &AppConfig{} + + err := config.FromFile(arguments[0]) + + if err != nil { + log.Fatal(err) + } + e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) @@ -117,7 +220,7 @@ func main() { a := &App{ Logger: &e.Logger, - DB: db, + Config: config, } m := &AuthenticationMap{ @@ -128,7 +231,46 @@ func main() { a.AuthMap = m + err = a.InitializeDB() + + if err != nil { + log.Fatal(err) + } + e.GET("/ws", a.authRequestWebSocket) e.GET("/auth/:request_id", a.authRequestRedirect) e.Logger.Fatal(e.Start(":1323")) } + +func (a *App) GetCredentialsFromInstanceHost(host string) (*Instance, error) { + postData := &map[string]string{ + "client_name": a.Config.AppName, + "redirect_uris": a.Config.AppScheme + "://" + a.Config.AppHost + "/confirm", + "scopes": strings.Join(a.Config.AppScopes, " "), + "website": a.Config.Website, + } + + data, err := json.Marshal(postData) + + if err != nil { + return nil, err + } + + response, err := http.Post("https://"+host+"/api/v1/apps", "application/json", bytes.NewBuffer(data)) + + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(response.Body) + + if err != nil { + return nil, err + } + + instance := &Instance{} + + err = json.Unmarshal(body, instance) + + return instance, err +}