mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 00:32:24 -06:00 
			
		
		
		
	
		
			
	
	
		
			213 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			213 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								/*
							 | 
						||
| 
								 | 
							
								   GoToSocial
							 | 
						||
| 
								 | 
							
								   Copyright (C) 2021 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 oauth
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"context"
							 | 
						||
| 
								 | 
							
									"net/http"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"github.com/gin-gonic/gin"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/gotosocial/internal/db"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/gotosocial/internal/db/model"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/oauth2/v4"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/oauth2/v4/errors"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/oauth2/v4/manage"
							 | 
						||
| 
								 | 
							
									"github.com/gotosocial/oauth2/v4/server"
							 | 
						||
| 
								 | 
							
									"github.com/sirupsen/logrus"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const (
							 | 
						||
| 
								 | 
							
									SessionAuthorizedToken = "authorized_token"
							 | 
						||
| 
								 | 
							
									// SessionAuthorizedUser is the key set in the gin context for the id of
							 | 
						||
| 
								 | 
							
									// a User who has successfully passed Bearer token authorization.
							 | 
						||
| 
								 | 
							
									// The interface returned from grabbing this key should be parsed as a *gtsmodel.User
							 | 
						||
| 
								 | 
							
									SessionAuthorizedUser = "authorized_user"
							 | 
						||
| 
								 | 
							
									// SessionAuthorizedAccount is the key set in the gin context for the Account
							 | 
						||
| 
								 | 
							
									// of a User who has successfully passed Bearer token authorization.
							 | 
						||
| 
								 | 
							
									// The interface returned from grabbing this key should be parsed as a *gtsmodel.Account
							 | 
						||
| 
								 | 
							
									SessionAuthorizedAccount = "authorized_account"
							 | 
						||
| 
								 | 
							
									// SessionAuthorizedAccount is the key set in the gin context for the Application
							 | 
						||
| 
								 | 
							
									// of a Client who has successfully passed Bearer token authorization.
							 | 
						||
| 
								 | 
							
									// The interface returned from grabbing this key should be parsed as a *gtsmodel.Application
							 | 
						||
| 
								 | 
							
									SessionAuthorizedApplication = "authorized_app"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Server wraps some oauth2 server functions in an interface, exposing only what is needed
							 | 
						||
| 
								 | 
							
								type Server interface {
							 | 
						||
| 
								 | 
							
									HandleTokenRequest(w http.ResponseWriter, r *http.Request) error
							 | 
						||
| 
								 | 
							
									HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error
							 | 
						||
| 
								 | 
							
									ValidationBearerToken(r *http.Request) (oauth2.TokenInfo, error)
							 | 
						||
| 
								 | 
							
									GenerateUserAccessToken(ti oauth2.TokenInfo, clientSecret string, userID string) (accessToken oauth2.TokenInfo, err error)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// s fulfils the Server interface using the underlying oauth2 server
							 | 
						||
| 
								 | 
							
								type s struct {
							 | 
						||
| 
								 | 
							
									server *server.Server
							 | 
						||
| 
								 | 
							
									log    *logrus.Logger
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type Authed struct {
							 | 
						||
| 
								 | 
							
									Token       oauth2.TokenInfo
							 | 
						||
| 
								 | 
							
									Application *model.Application
							 | 
						||
| 
								 | 
							
									User        *model.User
							 | 
						||
| 
								 | 
							
									Account     *model.Account
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// GetAuthed is a convenience function for returning an Authed struct from a gin context.
							 | 
						||
| 
								 | 
							
								// In essence, it tries to extract a token, application, user, and account from the context,
							 | 
						||
| 
								 | 
							
								// and then sets them on a struct for convenience.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If any are not present in the context, they will be set to nil on the returned Authed struct.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If *ALL* are not present, then nil and an error will be returned.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If something goes wrong during parsing, then nil and an error will be returned (consider this not authed).
							 | 
						||
| 
								 | 
							
								func GetAuthed(c *gin.Context) (*Authed, error) {
							 | 
						||
| 
								 | 
							
									ctx := c.Copy()
							 | 
						||
| 
								 | 
							
									a := &Authed{}
							 | 
						||
| 
								 | 
							
									var i interface{}
							 | 
						||
| 
								 | 
							
									var ok bool
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									i, ok = ctx.Get(SessionAuthorizedToken)
							 | 
						||
| 
								 | 
							
									if ok {
							 | 
						||
| 
								 | 
							
										parsed, ok := i.(oauth2.TokenInfo)
							 | 
						||
| 
								 | 
							
										if !ok {
							 | 
						||
| 
								 | 
							
											return nil, errors.New("could not parse token from session context")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										a.Token = parsed
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									i, ok = ctx.Get(SessionAuthorizedApplication)
							 | 
						||
| 
								 | 
							
									if ok {
							 | 
						||
| 
								 | 
							
										parsed, ok := i.(*model.Application)
							 | 
						||
| 
								 | 
							
										if !ok {
							 | 
						||
| 
								 | 
							
											return nil, errors.New("could not parse application from session context")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										a.Application = parsed
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									i, ok = ctx.Get(SessionAuthorizedUser)
							 | 
						||
| 
								 | 
							
									if ok {
							 | 
						||
| 
								 | 
							
										parsed, ok := i.(*model.User)
							 | 
						||
| 
								 | 
							
										if !ok {
							 | 
						||
| 
								 | 
							
											return nil, errors.New("could not parse user from session context")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										a.User = parsed
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									i, ok = ctx.Get(SessionAuthorizedAccount)
							 | 
						||
| 
								 | 
							
									if ok {
							 | 
						||
| 
								 | 
							
										parsed, ok := i.(*model.Account)
							 | 
						||
| 
								 | 
							
										if !ok {
							 | 
						||
| 
								 | 
							
											return nil, errors.New("could not parse account from session context")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										a.Account = parsed
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if a.Token == nil && a.Application == nil && a.User == nil && a.Account == nil {
							 | 
						||
| 
								 | 
							
										return nil, errors.New("not authorized")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return a, nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// HandleTokenRequest wraps the oauth2 library's HandleTokenRequest function
							 | 
						||
| 
								 | 
							
								func (s *s) HandleTokenRequest(w http.ResponseWriter, r *http.Request) error {
							 | 
						||
| 
								 | 
							
									return s.server.HandleTokenRequest(w, r)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// HandleAuthorizeRequest wraps the oauth2 library's HandleAuthorizeRequest function
							 | 
						||
| 
								 | 
							
								func (s *s) HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error {
							 | 
						||
| 
								 | 
							
									return s.server.HandleAuthorizeRequest(w, r)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// ValidationBearerToken wraps the oauth2 library's ValidationBearerToken function
							 | 
						||
| 
								 | 
							
								func (s *s) ValidationBearerToken(r *http.Request) (oauth2.TokenInfo, error) {
							 | 
						||
| 
								 | 
							
									return s.server.ValidationBearerToken(r)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// GenerateUserAccessToken shortcuts the normal oauth flow to create an user-level
							 | 
						||
| 
								 | 
							
								// bearer token *without* requiring that user to log in. This is useful when we
							 | 
						||
| 
								 | 
							
								// need to create a token for new users who haven't validated their email or logged in yet.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The ti parameter refers to an existing Application token that was used to make the upstream
							 | 
						||
| 
								 | 
							
								// request. This token needs to be validated and exist in database in order to create a new token.
							 | 
						||
| 
								 | 
							
								func (s *s) GenerateUserAccessToken(ti oauth2.TokenInfo, clientSecret string, userID string) (accessToken oauth2.TokenInfo, err error) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									tgr := &oauth2.TokenGenerateRequest{
							 | 
						||
| 
								 | 
							
										ClientID:            ti.GetClientID(),
							 | 
						||
| 
								 | 
							
										ClientSecret:        clientSecret,
							 | 
						||
| 
								 | 
							
										UserID:              userID,
							 | 
						||
| 
								 | 
							
										RedirectURI:         ti.GetRedirectURI(),
							 | 
						||
| 
								 | 
							
										Scope:               ti.GetScope(),
							 | 
						||
| 
								 | 
							
										Code:                ti.GetCode(),
							 | 
						||
| 
								 | 
							
										CodeChallenge:       ti.GetCodeChallenge(),
							 | 
						||
| 
								 | 
							
										CodeChallengeMethod: ti.GetCodeChallengeMethod(),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return s.server.Manager.GenerateAccessToken(context.Background(), oauth2.AuthorizationCode, tgr)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func New(database db.DB, log *logrus.Logger) Server {
							 | 
						||
| 
								 | 
							
									ts := newTokenStore(context.Background(), database, log)
							 | 
						||
| 
								 | 
							
									cs := newClientStore(database)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									manager := manage.NewDefaultManager()
							 | 
						||
| 
								 | 
							
									manager.MapTokenStorage(ts)
							 | 
						||
| 
								 | 
							
									manager.MapClientStorage(cs)
							 | 
						||
| 
								 | 
							
									manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
							 | 
						||
| 
								 | 
							
									sc := &server.Config{
							 | 
						||
| 
								 | 
							
										TokenType: "Bearer",
							 | 
						||
| 
								 | 
							
										// Must follow the spec.
							 | 
						||
| 
								 | 
							
										AllowGetAccessRequest: false,
							 | 
						||
| 
								 | 
							
										// Support only the non-implicit flow.
							 | 
						||
| 
								 | 
							
										AllowedResponseTypes: []oauth2.ResponseType{oauth2.Code},
							 | 
						||
| 
								 | 
							
										// Allow:
							 | 
						||
| 
								 | 
							
										// - Authorization Code (for first & third parties)
							 | 
						||
| 
								 | 
							
										// - Client Credentials (for applications)
							 | 
						||
| 
								 | 
							
										AllowedGrantTypes: []oauth2.GrantType{
							 | 
						||
| 
								 | 
							
											oauth2.AuthorizationCode,
							 | 
						||
| 
								 | 
							
											oauth2.ClientCredentials,
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{oauth2.CodeChallengePlain},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									srv := server.NewServer(sc, manager)
							 | 
						||
| 
								 | 
							
									srv.SetInternalErrorHandler(func(err error) *errors.Response {
							 | 
						||
| 
								 | 
							
										log.Errorf("internal oauth error: %s", err)
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									srv.SetResponseErrorHandler(func(re *errors.Response) {
							 | 
						||
| 
								 | 
							
										log.Errorf("internal response error: %s", re.Error)
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									srv.SetUserAuthorizationHandler(func(w http.ResponseWriter, r *http.Request) (string, error) {
							 | 
						||
| 
								 | 
							
										userID := r.FormValue("userid")
							 | 
						||
| 
								 | 
							
										if userID == "" {
							 | 
						||
| 
								 | 
							
											return "", errors.New("userid was empty")
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return userID, nil
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
									srv.SetClientInfoHandler(server.ClientFormHandler)
							 | 
						||
| 
								 | 
							
									return &s{
							 | 
						||
| 
								 | 
							
										server: srv,
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |