summaryrefslogtreecommitdiff
path: root/internal/api/security
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/security')
-rw-r--r--internal/api/security/extraheaders.go26
-rw-r--r--internal/api/security/flocblock.go31
-rw-r--r--internal/api/security/ratelimit.go70
-rw-r--r--internal/api/security/robots.go57
-rw-r--r--internal/api/security/security.go65
-rw-r--r--internal/api/security/signaturecheck.go51
-rw-r--r--internal/api/security/tokencheck.go120
-rw-r--r--internal/api/security/useragentblock.go35
8 files changed, 0 insertions, 455 deletions
diff --git a/internal/api/security/extraheaders.go b/internal/api/security/extraheaders.go
deleted file mode 100644
index f66ac43b4..000000000
--- a/internal/api/security/extraheaders.go
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- 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 security
-
-import "github.com/gin-gonic/gin"
-
-// ExtraHeaders adds any additional required headers to the response
-func (m *Module) ExtraHeaders(c *gin.Context) {
- c.Header("Server", "gotosocial")
-}
diff --git a/internal/api/security/flocblock.go b/internal/api/security/flocblock.go
deleted file mode 100644
index 0b61f4ef5..000000000
--- a/internal/api/security/flocblock.go
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- 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 security
-
-import "github.com/gin-gonic/gin"
-
-// FlocBlock is a middleware that prevents google chrome cohort tracking by
-// writing the Permissions-Policy header after all other parts of the request
-// have been completed. Floc was replaced by Topics in 2022 and the spec says
-// that interest-cohort will also block Topics (as of 2022-Nov).
-// See: https://smartframe.io/blog/google-topics-api-everything-you-need-to-know
-// See: https://github.com/patcg-individual-drafts/topics
-func (m *Module) FlocBlock(c *gin.Context) {
- c.Header("Permissions-Policy", "browsing-topics=()")
-}
diff --git a/internal/api/security/ratelimit.go b/internal/api/security/ratelimit.go
deleted file mode 100644
index 3c0d078c7..000000000
--- a/internal/api/security/ratelimit.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- 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 security
-
-import (
- "net"
- "net/http"
- "time"
-
- "github.com/gin-gonic/gin"
- limiter "github.com/ulule/limiter/v3"
- mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
- memory "github.com/ulule/limiter/v3/drivers/store/memory"
-)
-
-type RateLimitOptions struct {
- Period time.Duration
- Limit int64
-}
-
-func (m *Module) LimitReachedHandler(c *gin.Context) {
- code := http.StatusTooManyRequests
- c.AbortWithStatusJSON(code, gin.H{"error": "rate limit reached"})
-}
-
-// returns a gin middleware that will automatically rate limit caller (by IP address)
-// and enrich the response header with the following headers:
-// - `x-ratelimit-limit` maximum number of requests allowed per time period (fixed)
-// - `x-ratelimit-remaining` number of remaining requests that can still be performed
-// - `x-ratelimit-reset` unix timestamp when the rate limit will reset
-// if `x-ratelimit-limit` is exceeded an HTTP 429 error is returned
-func (m *Module) RateLimit(rateOptions RateLimitOptions) func(c *gin.Context) {
- rate := limiter.Rate{
- Period: rateOptions.Period,
- Limit: rateOptions.Limit,
- }
-
- store := memory.NewStore()
-
- limiterInstance := limiter.New(
- store,
- rate,
- // apply /64 mask to IPv6 addresses
- limiter.WithIPv6Mask(net.CIDRMask(64, 128)),
- )
-
- middleware := mgin.NewMiddleware(
- limiterInstance,
- // use custom rate limit reached error
- mgin.WithLimitReachedHandler(m.LimitReachedHandler),
- )
-
- return middleware
-}
diff --git a/internal/api/security/robots.go b/internal/api/security/robots.go
deleted file mode 100644
index 5b8ba3c05..000000000
--- a/internal/api/security/robots.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- 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 security
-
-import (
- "net/http"
-
- "github.com/gin-gonic/gin"
-)
-
-const robotsString = `User-agent: *
-Crawl-delay: 500
-# api stuff
-Disallow: /api/
-# auth/login stuff
-Disallow: /auth/
-Disallow: /oauth/
-Disallow: /check_your_email
-Disallow: /wait_for_approval
-Disallow: /account_disabled
-# well known stuff
-Disallow: /.well-known/
-# files
-Disallow: /fileserver/
-# s2s AP stuff
-Disallow: /users/
-Disallow: /emoji/
-# panels
-Disallow: /admin
-Disallow: /user
-Disallow: /settings/
-`
-
-// RobotsGETHandler returns a decent robots.txt that prevents crawling
-// the api, auth pages, settings pages, etc.
-//
-// More granular robots meta tags are then applied for web pages
-// depending on user preferences (see internal/web).
-func (m *Module) RobotsGETHandler(c *gin.Context) {
- c.String(http.StatusOK, robotsString)
-}
diff --git a/internal/api/security/security.go b/internal/api/security/security.go
deleted file mode 100644
index 1dce111d3..000000000
--- a/internal/api/security/security.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- 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 security
-
-import (
- "net/http"
- "time"
-
- "github.com/superseriousbusiness/gotosocial/internal/api"
- "github.com/superseriousbusiness/gotosocial/internal/config"
- "github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
- "github.com/superseriousbusiness/gotosocial/internal/router"
-)
-
-const robotsPath = "/robots.txt"
-
-// Module implements the ClientAPIModule interface for security middleware
-type Module struct {
- db db.DB
- server oauth.Server
-}
-
-// New returns a new security module
-func New(db db.DB, server oauth.Server) api.ClientModule {
- return &Module{
- db: db,
- server: server,
- }
-}
-
-// Route attaches security middleware to the given router
-func (m *Module) Route(s router.Router) error {
- // only enable rate limit middleware if configured
- // advanced-rate-limit-requests is greater than 0
- if rateLimitRequests := config.GetAdvancedRateLimitRequests(); rateLimitRequests > 0 {
- s.AttachMiddleware(m.RateLimit(RateLimitOptions{
- Period: 5 * time.Minute,
- Limit: int64(rateLimitRequests),
- }))
- }
- s.AttachMiddleware(m.SignatureCheck)
- s.AttachMiddleware(m.FlocBlock)
- s.AttachMiddleware(m.ExtraHeaders)
- s.AttachMiddleware(m.UserAgentBlock)
- s.AttachMiddleware(m.TokenCheck)
- s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler)
- return nil
-}
diff --git a/internal/api/security/signaturecheck.go b/internal/api/security/signaturecheck.go
deleted file mode 100644
index 1c117cd1b..000000000
--- a/internal/api/security/signaturecheck.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package security
-
-import (
- "net/http"
- "net/url"
-
- "github.com/superseriousbusiness/gotosocial/internal/ap"
- "github.com/superseriousbusiness/gotosocial/internal/log"
-
- "github.com/gin-gonic/gin"
- "github.com/go-fed/httpsig"
-)
-
-// SignatureCheck checks whether an incoming http request has been signed. If so, it will check if the domain
-// that signed the request is permitted to access the server. If it is permitted, the handler will set the key
-// verifier and the signature in the gin context for use down the line.
-func (m *Module) SignatureCheck(c *gin.Context) {
- // create the verifier from the request
- // if the request is signed, it will have a signature header
- verifier, err := httpsig.NewVerifier(c.Request)
- if err == nil {
- // the request was signed!
-
- // The key ID should be given in the signature so that we know where to fetch it from the remote server.
- // This will be something like https://example.org/users/whatever_requesting_user#main-key
- requestingPublicKeyID, err := url.Parse(verifier.KeyId())
- if err == nil && requestingPublicKeyID != nil {
- // we managed to parse the url!
-
- // if the domain is blocked we want to bail as early as possible
- blocked, err := m.db.IsURIBlocked(c.Request.Context(), requestingPublicKeyID)
- if err != nil {
- log.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err)
- c.AbortWithStatus(http.StatusInternalServerError)
- return
- }
- if blocked {
- log.Infof("domain %s is blocked", requestingPublicKeyID.Host)
- c.AbortWithStatus(http.StatusForbidden)
- return
- }
-
- // set the verifier and signature on the context here to save some work further down the line
- c.Set(string(ap.ContextRequestingPublicKeyVerifier), verifier)
- signature := c.GetHeader("Signature")
- if signature != "" {
- c.Set(string(ap.ContextRequestingPublicKeySignature), signature)
- }
- }
- }
-}
diff --git a/internal/api/security/tokencheck.go b/internal/api/security/tokencheck.go
deleted file mode 100644
index 9f2b7f36e..000000000
--- a/internal/api/security/tokencheck.go
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- 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 security
-
-import (
- "github.com/gin-gonic/gin"
- "github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
- "github.com/superseriousbusiness/gotosocial/internal/log"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
-)
-
-// TokenCheck checks if the client has presented a valid oauth Bearer token.
-// If so, it will check the User that the token belongs to, and set that in the context of
-// the request. Then, it will look up the account for that user, and set that in the request too.
-// If user or account can't be found, then the handler won't *fail*, in case the server wants to allow
-// public requests that don't have a Bearer token set (eg., for public instance information and so on).
-func (m *Module) TokenCheck(c *gin.Context) {
- ctx := c.Request.Context()
- defer c.Next()
-
- if c.Request.Header.Get("Authorization") == "" {
- // no token set in the header, we can just bail
- return
- }
-
- ti, err := m.server.ValidationBearerToken(c.Copy().Request)
- if err != nil {
- log.Infof("token was passed in Authorization header but we could not validate it: %s", err)
- return
- }
- c.Set(oauth.SessionAuthorizedToken, ti)
-
- // check for user-level token
- if userID := ti.GetUserID(); userID != "" {
- log.Tracef("authenticated user %s with bearer token, scope is %s", userID, ti.GetScope())
-
- // fetch user for this token
- user, err := m.db.GetUserByID(ctx, userID)
- if err != nil {
- if err != db.ErrNoEntries {
- log.Errorf("database error looking for user with id %s: %s", userID, err)
- return
- }
- log.Warnf("no user found for userID %s", userID)
- return
- }
-
- if user.ConfirmedAt.IsZero() {
- log.Warnf("authenticated user %s has never confirmed thier email address", userID)
- return
- }
-
- if !*user.Approved {
- log.Warnf("authenticated user %s's account was never approved by an admin", userID)
- return
- }
-
- if *user.Disabled {
- log.Warnf("authenticated user %s's account was disabled'", userID)
- return
- }
-
- c.Set(oauth.SessionAuthorizedUser, user)
-
- // fetch account for this token
- if user.Account == nil {
- acct, err := m.db.GetAccountByID(ctx, user.AccountID)
- if err != nil {
- if err != db.ErrNoEntries {
- log.Errorf("database error looking for account with id %s: %s", user.AccountID, err)
- return
- }
- log.Warnf("no account found for userID %s", userID)
- return
- }
- user.Account = acct
- }
-
- if !user.Account.SuspendedAt.IsZero() {
- log.Warnf("authenticated user %s's account (accountId=%s) has been suspended", userID, user.AccountID)
- return
- }
-
- c.Set(oauth.SessionAuthorizedAccount, user.Account)
- }
-
- // check for application token
- if clientID := ti.GetClientID(); clientID != "" {
- log.Tracef("authenticated client %s with bearer token, scope is %s", clientID, ti.GetScope())
-
- // fetch app for this token
- app := &gtsmodel.Application{}
- if err := m.db.GetWhere(ctx, []db.Where{{Key: "client_id", Value: clientID}}, app); err != nil {
- if err != db.ErrNoEntries {
- log.Errorf("database error looking for application with clientID %s: %s", clientID, err)
- return
- }
- log.Warnf("no app found for client %s", clientID)
- return
- }
- c.Set(oauth.SessionAuthorizedApplication, app)
- }
-}
diff --git a/internal/api/security/useragentblock.go b/internal/api/security/useragentblock.go
deleted file mode 100644
index b117e8608..000000000
--- a/internal/api/security/useragentblock.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- 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 security
-
-import (
- "errors"
- "net/http"
-
- "github.com/gin-gonic/gin"
-)
-
-// UserAgentBlock aborts requests with empty user agent strings.
-func (m *Module) UserAgentBlock(c *gin.Context) {
- if ua := c.Request.UserAgent(); ua == "" {
- code := http.StatusTeapot
- err := errors.New(http.StatusText(code) + ": no user-agent sent with request")
- c.AbortWithStatusJSON(code, gin.H{"error": err.Error()})
- }
-}