2022-12-03 03:20:49 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-12-04 05:19:41 +00:00
|
|
|
"crypto/rand"
|
2022-12-03 03:20:49 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
mastodon "github.com/mattn/go-mastodon"
|
2022-12-04 05:19:41 +00:00
|
|
|
"github.com/oklog/ulid/v2"
|
2022-12-03 03:20:49 +00:00
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
)
|
|
|
|
|
2022-12-05 07:45:51 +00:00
|
|
|
func verifyTokenInSession(c echo.Context) (bool, *mastodon.Account, error) {
|
2022-12-04 19:50:55 +00:00
|
|
|
data, err := getSessionData(c)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
2022-12-05 07:45:51 +00:00
|
|
|
return false, nil, err
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
if data.MastodonConfig.AccessToken == "" {
|
2022-12-05 07:45:51 +00:00
|
|
|
return false, nil, nil
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
2022-12-04 17:52:44 +00:00
|
|
|
mastoClient := mastodon.NewClient(data.MastodonConfig)
|
2022-12-03 03:20:49 +00:00
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
acc, err := mastoClient.GetAccountCurrentUser(c.Request().Context())
|
|
|
|
user, dbErr := findUserByID(c.Request().Context(), data.AudonID)
|
2022-12-03 03:20:49 +00:00
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
if err != nil || dbErr != nil || string(acc.ID) != user.RemoteID {
|
2022-12-05 07:45:51 +00:00
|
|
|
return false, nil, err
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-05 07:45:51 +00:00
|
|
|
return true, acc, nil
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-06 08:57:20 +00:00
|
|
|
type LoginRequest struct {
|
|
|
|
ServerHost string `validate:"required,hostname,fqdn" form:"server"`
|
|
|
|
Redirect string `validate:"url_encoded" form:"redir"`
|
|
|
|
}
|
|
|
|
|
2022-12-04 19:50:55 +00:00
|
|
|
// handler for POST to /app/login
|
2022-12-03 03:20:49 +00:00
|
|
|
func loginHandler(c echo.Context) (err error) {
|
2022-12-06 08:57:20 +00:00
|
|
|
req := new(LoginRequest)
|
2022-12-03 03:20:49 +00:00
|
|
|
|
2022-12-06 08:57:20 +00:00
|
|
|
if err = c.Bind(req); err != nil {
|
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
|
|
|
if err = mainValidator.Struct(req); err != nil {
|
2022-12-03 03:20:49 +00:00
|
|
|
return wrapValidationError(err)
|
|
|
|
}
|
|
|
|
|
2022-12-05 07:45:51 +00:00
|
|
|
valid, _, _ := verifyTokenInSession(c)
|
2022-12-03 03:20:49 +00:00
|
|
|
if !valid {
|
|
|
|
serverURL := &url.URL{
|
2022-12-06 08:57:20 +00:00
|
|
|
Host: req.ServerHost,
|
2022-12-03 03:20:49 +00:00
|
|
|
Scheme: "https",
|
|
|
|
Path: "/",
|
|
|
|
}
|
2022-12-10 03:16:43 +00:00
|
|
|
if req.Redirect == "" {
|
|
|
|
req.Redirect = "/"
|
|
|
|
}
|
2022-12-03 03:20:49 +00:00
|
|
|
|
2022-12-06 08:57:20 +00:00
|
|
|
appConfig, err := getAppConfig(serverURL.String(), req.Redirect)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
|
|
|
mastApp, err := mastodon.RegisterApp(c.Request().Context(), appConfig)
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusNotFound, "server_not_found")
|
|
|
|
}
|
|
|
|
|
|
|
|
userSession := &SessionData{
|
|
|
|
MastodonConfig: &mastodon.Config{
|
|
|
|
Server: serverURL.String(),
|
|
|
|
ClientID: mastApp.ClientID,
|
|
|
|
ClientSecret: mastApp.ClientSecret,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if err = writeSessionData(c, userSession); err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.String(http.StatusCreated, mastApp.AuthURI)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2022-12-06 13:20:36 +00:00
|
|
|
type OAuthRequest struct {
|
|
|
|
Code string `query:"code"`
|
|
|
|
Redirect string `query:"redir"`
|
|
|
|
}
|
|
|
|
|
2022-12-04 19:50:55 +00:00
|
|
|
// handler for GET to /app/oauth?code=****
|
2022-12-03 03:20:49 +00:00
|
|
|
func oauthHandler(c echo.Context) (err error) {
|
2022-12-06 13:20:36 +00:00
|
|
|
req := new(OAuthRequest)
|
|
|
|
|
|
|
|
if err = c.Bind(req); err != nil {
|
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
2022-12-06 08:57:20 +00:00
|
|
|
|
2022-12-06 13:20:36 +00:00
|
|
|
if req.Code == "" {
|
2022-12-03 03:20:49 +00:00
|
|
|
if errMsg := c.QueryParam("error"); errMsg == "access_denied" {
|
|
|
|
return c.Redirect(http.StatusFound, "/login")
|
|
|
|
}
|
2022-12-05 01:11:44 +00:00
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, "auth_code_required")
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
2022-12-10 03:16:43 +00:00
|
|
|
// if req.Redirect == "" {
|
|
|
|
// req.Redirect = "/"
|
|
|
|
// }
|
2022-12-03 03:20:49 +00:00
|
|
|
|
2022-12-04 19:50:55 +00:00
|
|
|
data, err := getSessionData(c)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
2022-12-04 19:50:55 +00:00
|
|
|
return err
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
2022-12-10 03:16:43 +00:00
|
|
|
appConf, err := getAppConfig(data.MastodonConfig.Server, req.Redirect)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
|
2022-12-06 13:20:36 +00:00
|
|
|
data.AuthCode = req.Code
|
2022-12-03 03:20:49 +00:00
|
|
|
client := mastodon.NewClient(data.MastodonConfig)
|
2022-12-06 13:20:36 +00:00
|
|
|
err = client.AuthenticateToken(c.Request().Context(), req.Code, appConf.RedirectURIs)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusForbidden, err.Error())
|
|
|
|
}
|
|
|
|
data.MastodonConfig = client.Config
|
|
|
|
|
|
|
|
acc, err := client.GetAccountCurrentUser(c.Request().Context())
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusForbidden, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
coll := mainDB.Collection(COLLECTION_USER)
|
|
|
|
if result, dbErr := findUserByRemote(c.Request().Context(), string(acc.ID), acc.URL); dbErr == mongo.ErrNoDocuments {
|
2022-12-04 05:19:41 +00:00
|
|
|
entropy := ulid.Monotonic(rand.Reader, 0)
|
2022-12-04 17:52:44 +00:00
|
|
|
id, err := ulid.New(ulid.Timestamp(time.Now().UTC()), entropy)
|
2022-12-03 03:20:49 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
2022-12-04 05:19:41 +00:00
|
|
|
data.AudonID = id.String()
|
2022-12-03 03:20:49 +00:00
|
|
|
newUser := AudonUser{
|
|
|
|
AudonID: data.AudonID,
|
|
|
|
RemoteID: string(acc.ID),
|
|
|
|
RemoteURL: acc.URL,
|
2022-12-04 17:52:44 +00:00
|
|
|
CreatedAt: time.Now().UTC(),
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
if _, insertErr := coll.InsertOne(c.Request().Context(), newUser); insertErr != nil {
|
|
|
|
c.Logger().Error(insertErr)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
} else if dbErr != nil {
|
|
|
|
c.Logger().Error(dbErr)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
} else if result != nil {
|
2022-12-03 17:44:11 +00:00
|
|
|
// Set setssion's Audon ID if already registered
|
2022-12-03 03:20:49 +00:00
|
|
|
data.AudonID = result.AudonID
|
|
|
|
}
|
|
|
|
|
|
|
|
err = writeSessionData(c, data)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2022-12-06 13:20:36 +00:00
|
|
|
return c.Redirect(http.StatusFound, req.Redirect)
|
2022-12-04 05:19:41 +00:00
|
|
|
// return c.Redirect(http.StatusFound, "http://localhost:5173")
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
2022-12-04 19:50:55 +00:00
|
|
|
|
2022-12-06 08:57:20 +00:00
|
|
|
func getOAuthTokenHandler(c echo.Context) (err error) {
|
2022-12-06 13:20:36 +00:00
|
|
|
data, ok := c.Get("data").(*SessionData)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidSession
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(http.StatusOK, &TokenResponse{
|
2022-12-09 00:21:17 +00:00
|
|
|
Url: data.MastodonConfig.Server,
|
|
|
|
Token: data.MastodonConfig.AccessToken,
|
|
|
|
AudonID: data.AudonID,
|
2022-12-06 13:20:36 +00:00
|
|
|
})
|
2022-12-06 08:57:20 +00:00
|
|
|
}
|
|
|
|
|
2022-12-09 00:21:17 +00:00
|
|
|
func logoutHandler(c echo.Context) (err error) {
|
|
|
|
data, err := getSessionData(c)
|
|
|
|
if err == nil && data.AudonID != "" {
|
|
|
|
mastoURL, err := url.Parse(data.MastodonConfig.Server)
|
|
|
|
if err != nil {
|
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
|
|
|
mastoURL = mastoURL.JoinPath("oauth", "revoke")
|
|
|
|
formValues := url.Values{}
|
|
|
|
formValues.Add("client_id", data.MastodonConfig.ClientID)
|
|
|
|
formValues.Add("client_secret", data.MastodonConfig.ClientSecret)
|
|
|
|
formValues.Add("token", data.MastodonConfig.AccessToken)
|
|
|
|
resp, err := http.PostForm(mastoURL.String(), formValues)
|
|
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
|
|
}
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "login_required")
|
|
|
|
}
|
|
|
|
|
2022-12-04 19:50:55 +00:00
|
|
|
func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
|
|
return func(c echo.Context) error {
|
|
|
|
data, err := getSessionData(c)
|
2022-12-06 08:57:20 +00:00
|
|
|
if err == nil && data.AudonID != "" {
|
2022-12-05 01:11:44 +00:00
|
|
|
if user, err := findUserByID(c.Request().Context(), data.AudonID); err == nil {
|
|
|
|
c.Set("user", user)
|
2022-12-06 13:20:36 +00:00
|
|
|
c.Set("data", data)
|
2022-12-04 19:50:55 +00:00
|
|
|
return next(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "login_required")
|
|
|
|
}
|
|
|
|
}
|