2023-03-12 16:00:57 +01:00
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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/>.
2021-04-01 20:46:45 +02:00
package auth
import (
"errors"
"fmt"
"net/http"
"net/url"
2021-12-11 17:50:00 +01:00
2021-04-01 20:46:45 +02:00
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
2021-07-23 10:36:28 +02:00
"github.com/google/uuid"
2023-01-02 13:10:50 +01:00
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
2021-05-21 15:48:26 +02:00
"github.com/superseriousbusiness/gotosocial/internal/db"
2022-06-08 20:38:03 +02:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2021-05-08 14:25:55 +02:00
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
2022-10-08 13:49:56 +02:00
"github.com/superseriousbusiness/gotosocial/internal/oauth"
2021-04-01 20:46:45 +02:00
)
2021-04-20 18:14:23 +02:00
// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize
2021-04-01 20:46:45 +02:00
// The idea here is to present an oauth authorize page to the user, with a button
2021-10-04 15:24:19 +02:00
// that they have to click to accept.
2021-04-20 18:14:23 +02:00
func ( m * Module ) AuthorizeGETHandler ( c * gin . Context ) {
2021-04-01 20:46:45 +02:00
s := sessions . Default ( c )
2023-01-02 13:10:50 +01:00
if _ , err := apiutil . NegotiateAccept ( c , apiutil . HTMLAcceptHeaders ... ) ; err != nil {
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorNotAcceptable ( err , err . Error ( ) ) , m . processor . InstanceGetV1 )
2021-12-11 17:50:00 +01:00
return
}
2021-04-01 20:46:45 +02:00
// UserID will be set in the session by AuthorizePOSTHandler if the caller has already gone through the authentication flow
// If it's not set, then we don't know yet who the user is, so we need to redirect them to the sign in page.
2021-07-08 11:32:31 +02:00
userID , ok := s . Get ( sessionUserID ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || userID == "" {
2023-01-02 13:10:50 +01:00
form := & apimodel . OAuthAuthorize { }
2022-06-08 20:38:03 +02:00
if err := c . ShouldBind ( form ) ; err != nil {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorBadRequest ( err , oauth . HelpfulAdvice ) , m . processor . InstanceGetV1 )
2021-07-08 11:32:31 +02:00
return
}
2022-06-08 20:38:03 +02:00
if errWithCode := saveAuthFormToSession ( s , form ) ; errWithCode != nil {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2021-07-08 11:32:31 +02:00
return
2021-04-01 20:46:45 +02:00
}
2022-06-08 20:38:03 +02:00
2023-01-02 13:10:50 +01:00
c . Redirect ( http . StatusSeeOther , "/auth" + AuthSignInPath )
2021-04-01 20:46:45 +02:00
return
}
2022-06-08 20:38:03 +02:00
// use session information to validate app, user, and account for this request
2021-07-08 11:32:31 +02:00
clientID , ok := s . Get ( sessionClientID ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || clientID == "" {
2022-06-08 20:38:03 +02:00
m . clearSession ( s )
err := fmt . Errorf ( "key %s was not found in session" , sessionClientID )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorBadRequest ( err , oauth . HelpfulAdvice ) , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
return
}
2022-06-08 20:38:03 +02:00
2023-08-10 16:08:41 +02:00
app , err := m . db . GetApplicationByClientID ( c . Request . Context ( ) , clientID )
if err != nil {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
safe := fmt . Sprintf ( "application for %s %s could not be retrieved" , sessionClientID , clientID )
var errWithCode gtserror . WithCode
if err == db . ErrNoEntries {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorBadRequest ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
} else {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorInternalError ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
return
}
2022-10-03 10:46:11 +02:00
user , err := m . db . GetUserByID ( c . Request . Context ( ) , userID )
if err != nil {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
safe := fmt . Sprintf ( "user with id %s could not be retrieved" , userID )
var errWithCode gtserror . WithCode
if err == db . ErrNoEntries {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorBadRequest ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
} else {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorInternalError ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
return
}
2022-06-08 20:38:03 +02:00
2021-08-25 15:34:33 +02:00
acct , err := m . db . GetAccountByID ( c . Request . Context ( ) , user . AccountID )
if err != nil {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
safe := fmt . Sprintf ( "account with id %s could not be retrieved" , user . AccountID )
var errWithCode gtserror . WithCode
if err == db . ErrNoEntries {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorBadRequest ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
} else {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorInternalError ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2022-02-07 12:04:31 +01:00
return
}
2022-06-08 20:38:03 +02:00
if ensureUserIsAuthorizedOrRedirect ( c , user , acct ) {
2021-04-01 20:46:45 +02:00
return
}
// Finally we should also get the redirect and scope of this particular request, as stored in the session.
2021-07-08 11:32:31 +02:00
redirect , ok := s . Get ( sessionRedirectURI ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || redirect == "" {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
err := fmt . Errorf ( "key %s was not found in session" , sessionRedirectURI )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorBadRequest ( err , oauth . HelpfulAdvice ) , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
return
}
2022-06-08 20:38:03 +02:00
2021-07-08 11:32:31 +02:00
scope , ok := s . Get ( sessionScope ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || scope == "" {
2021-07-23 10:36:28 +02:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
err := fmt . Errorf ( "key %s was not found in session" , sessionScope )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorBadRequest ( err , oauth . HelpfulAdvice ) , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
return
}
2023-02-02 14:08:13 +01:00
instance , errWithCode := m . processor . InstanceGetV1 ( c . Request . Context ( ) )
2022-08-08 10:40:51 +02:00
if errWithCode != nil {
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2022-08-08 10:40:51 +02:00
return
}
2021-04-01 20:46:45 +02:00
// the authorize template will display a form to the user where they can get some information
// about the app that's trying to authorize, and the scope of the request.
// They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler
c . HTML ( http . StatusOK , "authorize.tmpl" , gin . H {
"appname" : app . Name ,
"appwebsite" : app . Website ,
"redirect" : redirect ,
2022-06-08 20:38:03 +02:00
"scope" : scope ,
2021-04-01 20:46:45 +02:00
"user" : acct . Username ,
2022-08-08 10:40:51 +02:00
"instance" : instance ,
2021-04-01 20:46:45 +02:00
} )
}
2021-04-20 18:14:23 +02:00
// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
2021-04-01 20:46:45 +02:00
// At this point we assume that the user has A) logged in and B) accepted that the app should act for them,
// so we should proceed with the authentication flow and generate an oauth token for them if we can.
2021-04-20 18:14:23 +02:00
func ( m * Module ) AuthorizePOSTHandler ( c * gin . Context ) {
2021-04-01 20:46:45 +02:00
s := sessions . Default ( c )
// We need to retrieve the original form submitted to the authorizeGEThandler, and
// recreate it on the request so that it can be used further by the oauth2 library.
2021-07-23 10:36:28 +02:00
errs := [ ] string { }
2021-07-08 11:32:31 +02:00
forceLogin , ok := s . Get ( sessionForceLogin ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok {
2021-07-23 10:36:28 +02:00
forceLogin = "false"
2021-04-01 20:46:45 +02:00
}
2021-07-08 11:32:31 +02:00
responseType , ok := s . Get ( sessionResponseType ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || responseType == "" {
2022-06-08 20:38:03 +02:00
errs = append ( errs , fmt . Sprintf ( "key %s was not found in session" , sessionResponseType ) )
2021-04-01 20:46:45 +02:00
}
2021-07-08 11:32:31 +02:00
clientID , ok := s . Get ( sessionClientID ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || clientID == "" {
2022-06-08 20:38:03 +02:00
errs = append ( errs , fmt . Sprintf ( "key %s was not found in session" , sessionClientID ) )
2021-04-01 20:46:45 +02:00
}
2021-07-08 11:32:31 +02:00
redirectURI , ok := s . Get ( sessionRedirectURI ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok || redirectURI == "" {
2022-06-08 20:38:03 +02:00
errs = append ( errs , fmt . Sprintf ( "key %s was not found in session" , sessionRedirectURI ) )
2021-04-01 20:46:45 +02:00
}
2021-07-08 11:32:31 +02:00
scope , ok := s . Get ( sessionScope ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok {
2022-06-08 20:38:03 +02:00
errs = append ( errs , fmt . Sprintf ( "key %s was not found in session" , sessionScope ) )
2021-04-01 20:46:45 +02:00
}
2021-07-08 11:32:31 +02:00
2022-07-28 16:43:27 +02:00
var clientState string
if s , ok := s . Get ( sessionClientState ) . ( string ) ; ok {
clientState = s
}
2021-07-08 11:32:31 +02:00
userID , ok := s . Get ( sessionUserID ) . ( string )
2021-04-01 20:46:45 +02:00
if ! ok {
2022-06-08 20:38:03 +02:00
errs = append ( errs , fmt . Sprintf ( "key %s was not found in session" , sessionUserID ) )
}
if len ( errs ) != 0 {
2022-10-08 13:49:56 +02:00
errs = append ( errs , oauth . HelpfulAdvice )
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , gtserror . NewErrorBadRequest ( errors . New ( "one or more missing keys on session during AuthorizePOSTHandler" ) , errs ... ) , m . processor . InstanceGetV1 )
2022-06-08 20:38:03 +02:00
return
2021-04-01 20:46:45 +02:00
}
2021-07-07 15:46:42 +02:00
2022-10-03 10:46:11 +02:00
user , err := m . db . GetUserByID ( c . Request . Context ( ) , userID )
if err != nil {
2022-02-07 12:04:31 +01:00
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
safe := fmt . Sprintf ( "user with id %s could not be retrieved" , userID )
var errWithCode gtserror . WithCode
if err == db . ErrNoEntries {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorBadRequest ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
} else {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorInternalError ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2022-02-07 12:04:31 +01:00
return
}
2022-06-08 20:38:03 +02:00
2022-02-07 12:04:31 +01:00
acct , err := m . db . GetAccountByID ( c . Request . Context ( ) , user . AccountID )
if err != nil {
m . clearSession ( s )
2022-06-08 20:38:03 +02:00
safe := fmt . Sprintf ( "account with id %s could not be retrieved" , user . AccountID )
var errWithCode gtserror . WithCode
if err == db . ErrNoEntries {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorBadRequest ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
} else {
2022-10-08 13:49:56 +02:00
errWithCode = gtserror . NewErrorInternalError ( err , safe , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2022-02-07 12:04:31 +01:00
return
}
2022-06-08 20:38:03 +02:00
if ensureUserIsAuthorizedOrRedirect ( c , user , acct ) {
2022-02-07 12:04:31 +01:00
return
}
2022-10-08 13:49:56 +02:00
if redirectURI != oauth . OOBURI {
// we're done with the session now, so just clear it out
m . clearSession ( s )
}
2021-07-23 10:36:28 +02:00
2022-06-08 20:38:03 +02:00
// we have to set the values on the request form
// so that they're picked up by the oauth server
c . Request . Form = url . Values {
sessionForceLogin : { forceLogin } ,
sessionResponseType : { responseType } ,
sessionClientID : { clientID } ,
sessionRedirectURI : { redirectURI } ,
sessionScope : { scope } ,
sessionUserID : { userID } ,
2021-07-07 15:46:42 +02:00
}
2021-04-01 20:46:45 +02:00
2022-07-28 16:43:27 +02:00
if clientState != "" {
c . Request . Form . Set ( "state" , clientState )
}
2022-10-08 13:49:56 +02:00
if errWithCode := m . processor . OAuthHandleAuthorizeRequest ( c . Writer , c . Request ) ; errWithCode != nil {
2023-02-02 14:08:13 +01:00
apiutil . ErrorHandler ( c , errWithCode , m . processor . InstanceGetV1 )
2021-04-01 20:46:45 +02:00
}
}
2022-06-08 20:38:03 +02:00
// saveAuthFormToSession checks the given OAuthAuthorize form,
// and stores the values in the form into the session.
2023-01-02 13:10:50 +01:00
func saveAuthFormToSession ( s sessions . Session , form * apimodel . OAuthAuthorize ) gtserror . WithCode {
2022-06-08 20:38:03 +02:00
if form == nil {
err := errors . New ( "OAuthAuthorize form was nil" )
2022-10-08 13:49:56 +02:00
return gtserror . NewErrorBadRequest ( err , err . Error ( ) , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
if form . ResponseType == "" {
err := errors . New ( "field response_type was not set on OAuthAuthorize form" )
2022-10-08 13:49:56 +02:00
return gtserror . NewErrorBadRequest ( err , err . Error ( ) , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
if form . ClientID == "" {
err := errors . New ( "field client_id was not set on OAuthAuthorize form" )
2022-10-08 13:49:56 +02:00
return gtserror . NewErrorBadRequest ( err , err . Error ( ) , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
if form . RedirectURI == "" {
err := errors . New ( "field redirect_uri was not set on OAuthAuthorize form" )
2022-10-08 13:49:56 +02:00
return gtserror . NewErrorBadRequest ( err , err . Error ( ) , oauth . HelpfulAdvice )
2021-04-01 20:46:45 +02:00
}
// set default scope to read
if form . Scope == "" {
form . Scope = "read"
}
// save these values from the form so we can use them elsewhere in the session
2021-07-08 11:32:31 +02:00
s . Set ( sessionForceLogin , form . ForceLogin )
s . Set ( sessionResponseType , form . ResponseType )
s . Set ( sessionClientID , form . ClientID )
s . Set ( sessionRedirectURI , form . RedirectURI )
s . Set ( sessionScope , form . Scope )
2022-07-28 16:43:27 +02:00
s . Set ( sessionInternalState , uuid . NewString ( ) )
s . Set ( sessionClientState , form . State )
2022-06-08 20:38:03 +02:00
if err := s . Save ( ) ; err != nil {
err := fmt . Errorf ( "error saving form values onto session: %s" , err )
2022-10-08 13:49:56 +02:00
return gtserror . NewErrorInternalError ( err , oauth . HelpfulAdvice )
2022-06-08 20:38:03 +02:00
}
return nil
2021-04-01 20:46:45 +02:00
}
2022-02-07 12:04:31 +01:00
2022-06-08 20:38:03 +02:00
func ensureUserIsAuthorizedOrRedirect ( ctx * gin . Context , user * gtsmodel . User , account * gtsmodel . Account ) ( redirected bool ) {
2022-02-07 12:04:31 +01:00
if user . ConfirmedAt . IsZero ( ) {
2023-01-02 13:10:50 +01:00
ctx . Redirect ( http . StatusSeeOther , "/auth" + AuthCheckYourEmailPath )
2022-06-08 20:38:03 +02:00
redirected = true
return
2022-02-07 12:04:31 +01:00
}
2022-08-15 12:35:05 +02:00
if ! * user . Approved {
2023-01-02 13:10:50 +01:00
ctx . Redirect ( http . StatusSeeOther , "/auth" + AuthWaitForApprovalPath )
2022-06-08 20:38:03 +02:00
redirected = true
return
2022-02-07 12:04:31 +01:00
}
2022-08-15 12:35:05 +02:00
if * user . Disabled || ! account . SuspendedAt . IsZero ( ) {
2023-01-02 13:10:50 +01:00
ctx . Redirect ( http . StatusSeeOther , "/auth" + AuthAccountDisabledPath )
2022-06-08 20:38:03 +02:00
redirected = true
return
2022-02-07 12:04:31 +01:00
}
2022-06-08 20:38:03 +02:00
return
2022-02-07 12:04:31 +01:00
}