diff options
Diffstat (limited to 'internal/oauth/oauth.go')
-rw-r--r-- | internal/oauth/oauth.go | 110 |
1 files changed, 66 insertions, 44 deletions
diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go index 97913549a..c6d596761 100644 --- a/internal/oauth/oauth.go +++ b/internal/oauth/oauth.go @@ -27,6 +27,7 @@ import ( "github.com/go-pg/pg/v10" "github.com/gotosocial/gotosocial/internal/api" "github.com/gotosocial/gotosocial/internal/gtsmodel" + "github.com/gotosocial/gotosocial/pkg/mastotypes" "github.com/gotosocial/oauth2/v4" "github.com/gotosocial/oauth2/v4/errors" "github.com/gotosocial/oauth2/v4/manage" @@ -45,16 +46,12 @@ type API struct { } type login struct { - Email string `form:"username"` + Email string `form:"username"` Password string `form:"password"` } -type authorize struct { - ForceLogin string `form:"force_login,omitempty"` - ResponseType string `form:"response_type"` - ClientID string `form:"client_id"` - RedirectURI string `form:"redirect_uri"` - Scope string `form:"scope,omitempty"` +type code struct { + Code string `form:"code"` } func New(ts oauth2.TokenStore, cs oauth2.ClientStore, conn *pg.DB, log *logrus.Logger) *API { @@ -79,6 +76,9 @@ func New(ts oauth2.TokenStore, cs oauth2.ClientStore, conn *pg.DB, log *logrus.L oauth2.AuthorizationCode, oauth2.Refreshing, }, + AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{ + oauth2.CodeChallengePlain, + }, } srv := server.NewServer(sc, manager) @@ -106,9 +106,13 @@ func New(ts oauth2.TokenStore, cs oauth2.ClientStore, conn *pg.DB, log *logrus.L func (a *API) AddRoutes(s api.Server) error { s.AttachHandler(http.MethodGet, "/auth/sign_in", a.SignInGETHandler) s.AttachHandler(http.MethodPost, "/auth/sign_in", a.SignInPOSTHandler) + s.AttachHandler(http.MethodPost, "/oauth/token", a.TokenHandler) + s.AttachHandler(http.MethodGet, "/oauth/authorize", a.AuthorizeGETHandler) - s.AttachHandler(methodAny, "/auth", a.AuthHandler) + s.AttachHandler(http.MethodPost, "/oauth/authorize", a.AuthorizePOSTHandler) + + // s.AttachHandler(http.MethodGet, "/auth", a.AuthGETHandler) return nil } @@ -125,7 +129,7 @@ func incorrectPassword() (string, error) { // The form will then POST to the sign in page, which will be handled by SignInPOSTHandler func (a *API) SignInGETHandler(c *gin.Context) { a.log.WithField("func", "SignInGETHandler").Trace("serving sign in html") - c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(signInHTML)) + c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{}) } // SignInPOSTHandler should be served at https://example.org/auth/sign_in. @@ -135,15 +139,16 @@ func (a *API) SignInPOSTHandler(c *gin.Context) { l := a.log.WithField("func", "SignInPOSTHandler") s := sessions.Default(c) form := &login{} - if err := c.ShouldBind(form); err != nil || form.Email == "" || form.Password == "" { + if err := c.ShouldBind(form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } l.Tracef("parsed form: %+v", form) - userid, err := a.ValidatePassword(form.Email, form.Password); + userid, err := a.ValidatePassword(form.Email, form.Password) if err != nil { c.String(http.StatusForbidden, err.Error()) + return } s.Set("username", userid) @@ -151,11 +156,12 @@ func (a *API) SignInPOSTHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } + l.Trace("redirecting to auth page") - c.Redirect(http.StatusFound, "/auth") + c.Redirect(http.StatusFound, "/oauth/authorize") } -// TokenHandler should be served at https://example.org/oauth/token +// TokenHandler should be served as a POST at https://example.org/oauth/token // The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs. // See https://docs.joinmastodon.org/methods/apps/oauth/#obtain-a-token func (a *API) TokenHandler(c *gin.Context) { @@ -166,50 +172,61 @@ func (a *API) TokenHandler(c *gin.Context) { } } -// AuthorizeHandler should be served as GET at https://example.org/oauth/authorize +// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize // The idea here is to present an oauth authorize page to the user, with a button // that they have to click to accept. See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user func (a *API) AuthorizeGETHandler(c *gin.Context) { - l := a.log.WithField("func", "AuthorizeHandler") + l := a.log.WithField("func", "AuthorizeGETHandler") s := sessions.Default(c) - form := &authorize{} - - if err := c.ShouldBind(form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - l.Tracef("parsed form: %+v", form) - - if form.ResponseType == "" || form.ClientID == "" || form.RedirectURI == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "missing one of: response_type, client_id or redirect_uri"}) - return - } - - s.Set("force_login", form.ForceLogin) - s.Set("response_type", form.ResponseType) - s.Set("client_id", form.ClientID) - s.Set("redirect_uri", form.RedirectURI) - s.Set("scope", form.Scope) - if err := s.Save(); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - } v := s.Get("username") if username, ok := v.(string); !ok || username == "" { - l.Trace("username was empty, redirecting to sign in page") + l.Trace("username was empty, parsing form then redirecting to sign in page") + + form := &mastotypes.OAuthAuthorize{} + + if err := c.ShouldBind(form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + l.Tracef("parsed form: %+v", form) + + if form.ResponseType == "" || form.ClientID == "" || form.RedirectURI == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing one of: response_type, client_id or redirect_uri"}) + return + } + + // save these values from the form so we can use them elsewhere in the session + s.Set("force_login", form.ForceLogin) + s.Set("response_type", form.ResponseType) + s.Set("client_id", form.ClientID) + s.Set("redirect_uri", form.RedirectURI) + s.Set("scope", form.Scope) + if err := s.Save(); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Redirect(http.StatusFound, "/auth/sign_in") return } l.Trace("serving authorize html") - c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(authorizeHTML)) + c.HTML(http.StatusOK, "authorize.tmpl", gin.H{}) } -// AuthHandler should be served at https://example.org/auth -func (a *API) AuthHandler(c *gin.Context) { - l := a.log.WithField("func", "AuthHandler") +// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize +// The idea here is to present an oauth authorize page to the user, with a button +// that they have to click to accept. See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user +func (a *API) AuthorizePOSTHandler(c *gin.Context) { + l := a.log.WithField("func", "AuthorizePOSTHandler") s := sessions.Default(c) + v := s.Get("username") + if username, ok := v.(string); !ok || username == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "you are not signed in"}) + } + values := url.Values{} if v, ok := s.Get("force_login").(string); !ok { @@ -277,7 +294,13 @@ func (a *API) AuthHandler(c *gin.Context) { // so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db. func (a *API) ValidatePassword(email string, password string) (userid string, err error) { l := a.log.WithField("func", "PasswordAuthorizationHandler") - l.Tracef("email %s password %s", email, password) + + // make sure an email/password was provided and bail if not + if email == "" || password == "" { + l.Debug("email or password was not provided") + return incorrectPassword() + } + // first we select the user from the database based on email address, bail if no user found for that email gtsUser := >smodel.User{} if err := a.conn.Model(gtsUser).Where("email = ?", email).Select(); err != nil { @@ -297,8 +320,7 @@ func (a *API) ValidatePassword(email string, password string) (userid string, er return incorrectPassword() } - // If we've made it this far the email/password is correct so we need the oauth client-id of the user - // This is, conveniently, the same as the user ID, so we can just return it. + // If we've made it this far the email/password is correct, so we can just return the id of the user. userid = gtsUser.ID l.Tracef("returning (%s, %s)", userid, err) return |