Skip to content

Commit

Permalink
[feature] oob oauth token support (#889)
Browse files Browse the repository at this point in the history
* move helpful advice into oauth server

* rewrite HandleAuthorizeRequest to allow oob
  • Loading branch information
tsmethurst authored Oct 8, 2022
1 parent 5cf0f99 commit 3bb45b7
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 42 deletions.
3 changes: 3 additions & 0 deletions internal/api/client/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router"
Expand Down Expand Up @@ -92,5 +93,7 @@ func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler)

s.AttachHandler(http.MethodGet, CallbackPath, m.CallbackGETHandler)

s.AttachHandler(http.MethodGet, oauth.OOBTokenPath, m.OobHandler)
return nil
}
55 changes: 27 additions & 28 deletions internal/api/client/auth/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)

// helpfulAdvice is a handy hint to users;
// particularly important during the login flow
var helpfulAdvice = "If you arrived at this error during a login/oauth flow, please try clearing your session cookies and logging in again; if problems persist, make sure you're using the correct credentials"

// 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.
Expand All @@ -57,7 +54,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
form := &model.OAuthAuthorize{}
if err := c.ShouldBind(form); err != nil {
m.clearSession(s)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

Expand All @@ -76,7 +73,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
if !ok || clientID == "" {
m.clearSession(s)
err := fmt.Errorf("key %s was not found in session", sessionClientID)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

Expand All @@ -86,9 +83,9 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand All @@ -100,9 +97,9 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
safe := fmt.Sprintf("user with id %s could not be retrieved", userID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand All @@ -114,9 +111,9 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand All @@ -131,15 +128,15 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
if !ok || redirect == "" {
m.clearSession(s)
err := fmt.Errorf("key %s was not found in session", sessionRedirectURI)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

scope, ok := s.Get(sessionScope).(string)
if !ok || scope == "" {
m.clearSession(s)
err := fmt.Errorf("key %s was not found in session", sessionScope)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

Expand Down Expand Up @@ -208,7 +205,7 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
}

if len(errs) != 0 {
errs = append(errs, helpfulAdvice)
errs = append(errs, oauth.HelpfulAdvice)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during AuthorizePOSTHandler"), errs...), m.processor.InstanceGet)
return
}
Expand All @@ -219,9 +216,9 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
safe := fmt.Sprintf("user with id %s could not be retrieved", userID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand All @@ -233,9 +230,9 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand All @@ -245,8 +242,10 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
return
}

// we're done with the session now, so just clear it out
m.clearSession(s)
if redirectURI != oauth.OOBURI {
// we're done with the session now, so just clear it out
m.clearSession(s)
}

// we have to set the values on the request form
// so that they're picked up by the oauth server
Expand All @@ -263,8 +262,8 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
c.Request.Form.Set("state", clientState)
}

if err := m.processor.OAuthHandleAuthorizeRequest(c.Writer, c.Request); err != nil {
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice), m.processor.InstanceGet)
if errWithCode := m.processor.OAuthHandleAuthorizeRequest(c.Writer, c.Request); errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
}
}

Expand All @@ -273,22 +272,22 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtserror.WithCode {
if form == nil {
err := errors.New("OAuthAuthorize form was nil")
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice)
}

if form.ResponseType == "" {
err := errors.New("field response_type was not set on OAuthAuthorize form")
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice)
}

if form.ClientID == "" {
err := errors.New("field client_id was not set on OAuthAuthorize form")
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice)
}

if form.RedirectURI == "" {
err := errors.New("field redirect_uri was not set on OAuthAuthorize form")
return gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice)
return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice)
}

// set default scope to read
Expand All @@ -307,7 +306,7 @@ func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtser

if err := s.Save(); err != nil {
err := fmt.Errorf("error saving form values onto session: %s", err)
return gtserror.NewErrorInternalError(err, helpfulAdvice)
return gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice)
}

return nil
Expand Down
7 changes: 4 additions & 3 deletions internal/api/client/auth/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/validate"
)
Expand Down Expand Up @@ -91,7 +92,7 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
if !ok || clientID == "" {
m.clearSession(s)
err := fmt.Errorf("key %s was not found in session", sessionClientID)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

Expand All @@ -101,9 +102,9 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
safe := fmt.Sprintf("application for %s %s could not be retrieved", sessionClientID, clientID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, helpfulAdvice)
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
Expand Down
111 changes: 111 additions & 0 deletions internal/api/client/auth/oob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package auth

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)

func (m *Module) OobHandler(c *gin.Context) {
host := config.GetHost()
instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), host)
if errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return
}

instanceGet := func(ctx context.Context, domain string) (*model.Instance, gtserror.WithCode) { return instance, nil }

oobToken := c.Query("code")
if oobToken == "" {
err := errors.New("no 'code' query value provided in callback redirect")
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice), instanceGet)
return
}

s := sessions.Default(c)

errs := []string{}

scope, ok := s.Get(sessionScope).(string)
if !ok {
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionScope))
}

userID, ok := s.Get(sessionUserID).(string)
if !ok {
errs = append(errs, fmt.Sprintf("key %s was not found in session", sessionUserID))
}

if len(errs) != 0 {
errs = append(errs, oauth.HelpfulAdvice)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during OobHandler"), errs...), m.processor.InstanceGet)
return
}

user, err := m.db.GetUserByID(c.Request.Context(), userID)
if err != nil {
m.clearSession(s)
safe := fmt.Sprintf("user with id %s could not be retrieved", userID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, instanceGet)
return
}

acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
if err != nil {
m.clearSession(s)
safe := fmt.Sprintf("account with id %s could not be retrieved", user.AccountID)
var errWithCode gtserror.WithCode
if err == db.ErrNoEntries {
errWithCode = gtserror.NewErrorBadRequest(err, safe, oauth.HelpfulAdvice)
} else {
errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)
}
api.ErrorHandler(c, errWithCode, instanceGet)
return
}

// we're done with the session now, so just clear it out
m.clearSession(s)

c.HTML(http.StatusOK, "oob.tmpl", gin.H{
"instance": instance,
"user": acct.Username,
"oobToken": oobToken,
"scope": scope,
})
}
7 changes: 4 additions & 3 deletions internal/api/client/auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"golang.org/x/crypto/bcrypt"
)

Expand Down Expand Up @@ -86,7 +87,7 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {
form := &login{}
if err := c.ShouldBind(form); err != nil {
m.clearSession(s)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
return
}

Expand All @@ -101,7 +102,7 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {
s.Set(sessionUserID, userid)
if err := s.Save(); err != nil {
err := fmt.Errorf("error saving user id onto session: %s", err)
api.ErrorHandler(c, gtserror.NewErrorInternalError(err, helpfulAdvice), m.processor.InstanceGet)
api.ErrorHandler(c, gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice), m.processor.InstanceGet)
}

c.Redirect(http.StatusFound, OauthAuthorizePath)
Expand Down Expand Up @@ -140,5 +141,5 @@ func (m *Module) ValidatePassword(ctx context.Context, email string, password st
// only a generic 'safe' error message to the user, to not give any info away.
func incorrectPassword(err error) (string, gtserror.WithCode) {
safeErr := fmt.Errorf("password/email combination was incorrect")
return "", gtserror.NewErrorUnauthorized(err, safeErr.Error(), helpfulAdvice)
return "", gtserror.NewErrorUnauthorized(err, safeErr.Error(), oauth.HelpfulAdvice)
}
Loading

0 comments on commit 3bb45b7

Please sign in to comment.