diff options
Diffstat (limited to 'internal/api/util/auth.go')
| -rw-r--r-- | internal/api/util/auth.go | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/internal/api/util/auth.go b/internal/api/util/auth.go new file mode 100644 index 000000000..5c6afb306 --- /dev/null +++ b/internal/api/util/auth.go @@ -0,0 +1,152 @@ +// 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/>. + +package util + +import ( + "errors" + "slices" + "strings" + + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/oauth2/v4" +) + +// Auth wraps an authorized token, application, user, and account. +// It is used in the functions GetAuthed and MustAuth. +// Because the user might *not* be authed, any of the fields in this struct +// might be nil, so make sure to check that when you're using this struct anywhere. +type Auth struct { + Token oauth2.TokenInfo + Application *gtsmodel.Application + User *gtsmodel.User + Account *gtsmodel.Account +} + +// TokenAuth is a convenience function for returning an TokenAuth 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 TokenAuth 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). +// TokenAuth is like GetAuthed, but will fail if one of the requirements is not met. +func TokenAuth( + c *gin.Context, + requireToken bool, + requireApp bool, + requireUser bool, + requireAccount bool, + requireScope ...Scope, +) (*Auth, gtserror.WithCode) { + var ( + ctx = c.Copy() + a = &Auth{} + i interface{} + ok bool + ) + + i, ok = ctx.Get(oauth.SessionAuthorizedToken) + if ok { + parsed, ok := i.(oauth2.TokenInfo) + if !ok { + const errText = "could not parse token from session context" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + a.Token = parsed + } + + i, ok = ctx.Get(oauth.SessionAuthorizedApplication) + if ok { + parsed, ok := i.(*gtsmodel.Application) + if !ok { + const errText = "could not parse application from session context" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + a.Application = parsed + } + + i, ok = ctx.Get(oauth.SessionAuthorizedUser) + if ok { + parsed, ok := i.(*gtsmodel.User) + if !ok { + const errText = "could not parse user from session context" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + a.User = parsed + } + + i, ok = ctx.Get(oauth.SessionAuthorizedAccount) + if ok { + parsed, ok := i.(*gtsmodel.Account) + if !ok { + const errText = "could not parse account from session context" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + a.Account = parsed + } + + if requireToken && a.Token == nil { + const errText = "token not supplied" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + + if requireApp && a.Application == nil { + const errText = "application not supplied" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + + if requireUser && a.User == nil { + const errText = "user not supplied or not authorized" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + + if requireAccount && a.Account == nil { + const errText = "account not supplied or not authorized" + return nil, gtserror.NewErrorUnauthorized(errors.New(errText), errText) + } + + if len(requireScope) != 0 { + // We need to match one of the + // required scopes, check if we can. + hasScopes := strings.Split(a.Token.GetScope(), " ") + scopeOK := slices.ContainsFunc( + hasScopes, + func(hasScope string) bool { + for _, requiredScope := range requireScope { + if Scope(hasScope).Permits(requiredScope) { + // Got it. + return true + } + } + return false + }, + ) + + if !scopeOK { + const errText = "token has insufficient scope permission" + return nil, gtserror.NewErrorForbidden(errors.New(errText), errText) + } + } + + return a, nil +} |
