|
|
|
@ -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
|
|
|
|
|
}
|
|
|
|
|