| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/gob" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"html/template" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"log" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/go-playground/validator/v10" | 
					
						
							|  |  |  | 	"github.com/gorilla/sessions" | 
					
						
							|  |  |  | 	"github.com/labstack/echo-contrib/session" | 
					
						
							|  |  |  | 	"github.com/labstack/echo/v4" | 
					
						
							|  |  |  | 	"github.com/mattn/go-mastodon" | 
					
						
							|  |  |  | 	"go.mongodb.org/mongo-driver/mongo" | 
					
						
							|  |  |  | 	"go.mongodb.org/mongo-driver/mongo/options" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type ( | 
					
						
							|  |  |  | 	Template struct { | 
					
						
							|  |  |  | 		templates *template.Template | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	CustomValidator struct { | 
					
						
							|  |  |  | 		validator *validator.Validate | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	M map[string]interface{} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	err_invalid_cookie error               = errors.New("invalid cookie") | 
					
						
							|  |  |  | 	mastAppConfigBase  *mastodon.AppConfig = nil | 
					
						
							|  |  |  | 	mainDB             *mongo.Database     = nil | 
					
						
							|  |  |  | 	mainValidator                          = validator.New() | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	mainConfig         *AppConfig | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	gob.Register(&SessionData{}) | 
					
						
							|  |  |  | 	gob.Register(&M{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func main() { | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	var err error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Load config from environment variables and .env
 | 
					
						
							|  |  |  | 	mainConfig, err = loadConfig(os.Getenv("AUDON_ENV")) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Fatalln(err) | 
					
						
							|  |  |  | 		os.Exit(1) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	dbContext, cancel := context.WithTimeout(context.Background(), 10*time.Second) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	dbClient, err := mongo.Connect(dbContext, options.Client().ApplyURI(mainConfig.MongoURL.String())) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Fatalln(err) | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 		os.Exit(2) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	mainDB = dbClient.Database(mainConfig.DBName) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	err = createIndexes(dbContext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Fatalln(err) | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 		os.Exit(3) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e := echo.New() | 
					
						
							|  |  |  | 	defer e.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t := &Template{ | 
					
						
							|  |  |  | 		templates: template.Must(template.ParseFiles("audon-fe/index.html", "audon-fe/dist/index.html")), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	e.Renderer = t | 
					
						
							|  |  |  | 	e.Validator = &CustomValidator{validator: mainValidator} | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	e.Use(session.Middleware(sessions.NewCookieStore([]byte(mainConfig.SeesionSecret)))) | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	// e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
 | 
					
						
							|  |  |  | 	// 	CookiePath:  "/",
 | 
					
						
							|  |  |  | 	// 	TokenLookup: "header:X-CSRF-Token,form:csrf",
 | 
					
						
							|  |  |  | 	// }))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e.GET("/api/v1/verify", verifyHandler) | 
					
						
							|  |  |  | 	e.POST("/app/login", loginHandler) | 
					
						
							|  |  |  | 	e.GET("/app/oauth", oauthHandler) | 
					
						
							|  |  |  | 	e.Static("/assets", "audon-fe/dist/assets") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e.Logger.Debug(e.Start(":1323")) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // handler for GET to /api/v1/verify
 | 
					
						
							|  |  |  | func verifyHandler(c echo.Context) (err error) { | 
					
						
							|  |  |  | 	sess, err := getSession(c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.Logger().Error(err) | 
					
						
							|  |  |  | 		return echo.NewHTTPError(http.StatusInternalServerError) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	valid, _ := verifyTokenInSession(c, sess) | 
					
						
							|  |  |  | 	if !valid { | 
					
						
							|  |  |  | 		return c.NoContent(http.StatusUnauthorized) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return c.NoContent(http.StatusOK) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { | 
					
						
							|  |  |  | 	return t.templates.ExecuteTemplate(w, name, data) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (cv *CustomValidator) Validate(i interface{}) error { | 
					
						
							|  |  |  | 	if err := cv.validator.Struct(i); err != nil { | 
					
						
							|  |  |  | 		return wrapValidationError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func getAppConfig(server string) (*mastodon.AppConfig, error) { | 
					
						
							|  |  |  | 	if mastAppConfigBase != nil { | 
					
						
							|  |  |  | 		return &mastodon.AppConfig{ | 
					
						
							|  |  |  | 			Server:       server, | 
					
						
							|  |  |  | 			ClientName:   mastAppConfigBase.ClientName, | 
					
						
							|  |  |  | 			Scopes:       mastAppConfigBase.Scopes, | 
					
						
							|  |  |  | 			Website:      mastAppConfigBase.Website, | 
					
						
							|  |  |  | 			RedirectURIs: mastAppConfigBase.RedirectURIs, | 
					
						
							|  |  |  | 		}, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	redirectURI := "urn:ietf:wg:oauth:2.0:oob" | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	u := &url.URL{ | 
					
						
							|  |  |  | 		Host:   mainConfig.LocalDomain, | 
					
						
							|  |  |  | 		Scheme: "http", | 
					
						
							|  |  |  | 		Path:   "/", | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-12-03 17:44:11 +00:00
										 |  |  | 	u = u.JoinPath("app", "oauth") | 
					
						
							|  |  |  | 	redirectURI = u.String() | 
					
						
							| 
									
										
										
										
											2022-12-03 03:20:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	conf := &mastodon.AppConfig{ | 
					
						
							|  |  |  | 		ClientName:   "Audon", | 
					
						
							|  |  |  | 		Scopes:       "read:accounts read:follows", | 
					
						
							|  |  |  | 		Website:      "https://github.com/nmkj-io/audon", | 
					
						
							|  |  |  | 		RedirectURIs: redirectURI, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mastAppConfigBase = conf | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &mastodon.AppConfig{ | 
					
						
							|  |  |  | 		Server:       server, | 
					
						
							|  |  |  | 		ClientName:   conf.ClientName, | 
					
						
							|  |  |  | 		Scopes:       conf.Scopes, | 
					
						
							|  |  |  | 		Website:      conf.Website, | 
					
						
							|  |  |  | 		RedirectURIs: conf.RedirectURIs, | 
					
						
							|  |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func getSession(c echo.Context) (sess *sessions.Session, err error) { | 
					
						
							|  |  |  | 	sess, err = session.Get(SESSION_NAME, c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sess.Options = &sessions.Options{ | 
					
						
							|  |  |  | 		Path:     "/", | 
					
						
							|  |  |  | 		MaxAge:   0, | 
					
						
							|  |  |  | 		HttpOnly: true, | 
					
						
							|  |  |  | 		// SameSite: http.SameSiteStrictMode,
 | 
					
						
							|  |  |  | 		// Secure:   true,
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sess, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // retrieve user's session, returns invalid cookie error if failed
 | 
					
						
							|  |  |  | func getSessionData(sess *sessions.Session) (data *SessionData, err error) { | 
					
						
							|  |  |  | 	val := sess.Values[SESSION_DATASTORE_NAME] | 
					
						
							|  |  |  | 	data, ok := val.(*SessionData) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return nil, err_invalid_cookie | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return data, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // write user's session, returns error if failed
 | 
					
						
							|  |  |  | func writeSessionData(c echo.Context, data *SessionData) error { | 
					
						
							|  |  |  | 	sess, err := getSession(c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sess.Values[SESSION_DATASTORE_NAME] = data | 
					
						
							|  |  |  | 	sess.Save(c.Request(), c.Response()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |