audon/auth.go

251 wiersze
6.8 KiB
Go
Czysty Zwykły widok Historia

2022-12-03 03:20:49 +00:00
package main
import (
2022-12-04 05:19:41 +00:00
"crypto/rand"
2022-12-15 20:28:41 +00:00
"fmt"
2022-12-03 03:20:49 +00:00
"net/http"
"net/url"
2022-12-13 18:02:25 +00:00
"strings"
2022-12-03 03:20:49 +00:00
"time"
2023-01-30 02:30:10 +00:00
"github.com/jellydator/ttlcache/v3"
2022-12-03 03:20:49 +00:00
"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
}
2023-01-23 12:10:21 +00:00
mastoClient := getMastodonClient(data)
2022-12-15 20:28:41 +00:00
if mastoClient == nil {
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
acc, err := mastoClient.GetAccountCurrentUser(c.Request().Context())
2023-01-27 06:22:18 +00:00
acctUrl, _ := url.Parse(acc.URL)
finger := strings.Split(acc.Username, "@")
webfinger := fmt.Sprintf("%s@%s", finger[0], acctUrl.Host)
2022-12-04 17:52:44 +00:00
user, dbErr := findUserByID(c.Request().Context(), data.AudonID)
2022-12-03 03:20:49 +00:00
2023-01-27 06:22:18 +00:00
if err != nil || dbErr != nil || webfinger != user.Webfinger {
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,fqdn" form:"server"`
2022-12-06 08:57:20 +00:00
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
2023-03-22 14:14:00 +00:00
appConfig, err := getAppConfig(serverURL.String())
2022-12-03 03:20:49 +00:00
if err != nil {
return ErrInvalidRequestFormat
}
2022-12-11 05:15:03 +00:00
// mastApp, err := mastodon.RegisterApp(c.Request().Context(), appConfig)
mastApp, err := registerApp(c.Request().Context(), appConfig)
2022-12-03 03:20:49 +00:00
if err != nil {
2023-01-23 12:10:21 +00:00
c.Logger().Warn(err)
2022-12-03 03:20:49 +00:00
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)
}
2023-03-22 14:14:00 +00:00
redirURL, err := url.Parse(mastApp.AuthURI)
if err != nil {
c.Logger().Warn(err)
return echo.NewHTTPError(http.StatusInternalServerError, "invalid_auth_uri")
}
q := redirURL.Query()
q.Add("state", req.Redirect)
redirURL.RawQuery = q.Encode()
2023-03-22 14:45:00 +00:00
return c.String(http.StatusCreated, redirURL.String())
2022-12-03 03:20:49 +00:00
}
return c.NoContent(http.StatusNoContent)
}
2022-12-06 13:20:36 +00:00
type OAuthRequest struct {
2023-03-22 14:14:00 +00:00
Code string `query:"code"`
State string `query:"state"`
2022-12-06 13:20:36 +00:00
}
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
}
2023-03-22 14:14:00 +00:00
appConf, err := getAppConfig(data.MastodonConfig.Server)
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-11 05:15:03 +00:00
client.UserAgent = USER_AGENT
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)
acctUrl, _ := url.Parse(acc.URL)
finger := strings.Split(acc.Username, "@")
webfinger := fmt.Sprintf("%s@%s", finger[0], acctUrl.Host)
if result, dbErr := findUserByWebfinger(c.Request().Context(), webfinger); 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,
Webfinger: webfinger,
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 {
// 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)
}
2023-03-22 14:14:00 +00:00
return c.Redirect(http.StatusFound, req.State)
2022-12-03 03:20:49 +00:00
}
2022-12-04 19:50:55 +00:00
2023-01-25 06:37:31 +00:00
func getUserTokenHandler(c echo.Context) (err error) {
2022-12-06 13:20:36 +00:00
data, ok := c.Get("data").(*SessionData)
if !ok {
return ErrInvalidSession
}
2023-01-23 12:10:21 +00:00
user, ok := c.Get("user").(*AudonUser)
if !ok {
return ErrInvalidSession
}
2022-12-06 13:20:36 +00:00
return c.JSON(http.StatusOK, &TokenResponse{
2023-01-23 12:10:21 +00:00
Url: data.MastodonConfig.Server,
Token: data.MastodonConfig.AccessToken,
Audon: user,
2022-12-06 13:20:36 +00:00
})
2022-12-06 08:57:20 +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)
2022-12-13 18:02:25 +00:00
request, err := http.NewRequest(http.MethodPost, mastoURL.String(), strings.NewReader(formValues.Encode()))
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
request.Header.Add("User-Agent", USER_AGENT)
2023-01-19 02:22:20 +00:00
http.DefaultClient.Do(request) // don't care even if revoking failed
writeSessionData(c, nil) // to reset, write nil to user's session
return c.NoContent(http.StatusOK)
}
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 {
2023-01-30 02:30:10 +00:00
userSessionCache.Set(data.AudonID, data, ttlcache.DefaultTTL)
2022-12-05 01:11:44 +00:00
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")
}
}