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() mainConfig *AppConfig ) func init() { gob.Register(&SessionData{}) gob.Register(&M{}) } func main() { 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) } dbContext, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() dbClient, err := mongo.Connect(dbContext, options.Client().ApplyURI(mainConfig.MongoURL.String())) if err != nil { log.Fatalln(err) os.Exit(2) } mainDB = dbClient.Database(mainConfig.DBName) err = createIndexes(dbContext) if err != nil { log.Fatalln(err) os.Exit(3) } 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} e.Use(session.Middleware(sessions.NewCookieStore([]byte(mainConfig.SeesionSecret)))) // 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" u := &url.URL{ Host: mainConfig.LocalDomain, Scheme: "http", Path: "/", } u = u.JoinPath("app", "oauth") redirectURI = u.String() 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 }