From ff75075b81b79f02de0520254b99b27d5cf90f07 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 30 Mar 2023 16:58:35 +0200 Subject: [PATCH] feat: restrict certain endpoints from API tokens and/or read-only tokens --- backend/routes/auth/discord.go | 8 ++++---- backend/routes/auth/fedi_mastodon.go | 8 ++++---- backend/routes/auth/fedi_misskey.go | 4 ++-- backend/routes/auth/invite.go | 8 ++++++++ backend/routes/auth/tokens.go | 8 ++++++-- backend/routes/member/create_member.go | 4 ++++ backend/routes/member/delete_member.go | 4 ++++ backend/routes/member/patch_member.go | 4 ++++ backend/routes/mod/create_report.go | 8 ++++++++ backend/routes/mod/warnings.go | 4 ++++ backend/routes/user/delete_user.go | 4 ++-- backend/routes/user/export.go | 8 ++++++++ backend/routes/user/patch_user.go | 4 ++++ 13 files changed, 62 insertions(+), 14 deletions(-) diff --git a/backend/routes/auth/discord.go b/backend/routes/auth/discord.go index 369cc38..975accf 100644 --- a/backend/routes/auth/discord.go +++ b/backend/routes/auth/discord.go @@ -167,8 +167,8 @@ func (s *Server) discordLink(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) // only site tokens can be used for this endpoint - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrInvalidToken} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } req, err := Decode[linkRequest](r) @@ -213,8 +213,8 @@ func (s *Server) discordUnlink(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) // only site tokens can be used for this endpoint - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrInvalidToken} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } u, err := s.DB.User(ctx, claims.UserID) diff --git a/backend/routes/auth/fedi_mastodon.go b/backend/routes/auth/fedi_mastodon.go index ef2a81d..aece379 100644 --- a/backend/routes/auth/fedi_mastodon.go +++ b/backend/routes/auth/fedi_mastodon.go @@ -189,8 +189,8 @@ func (s *Server) mastodonLink(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) // only site tokens can be used for this endpoint - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrInvalidToken} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } req, err := Decode[fediLinkRequest](r) @@ -240,8 +240,8 @@ func (s *Server) mastodonUnlink(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) // only site tokens can be used for this endpoint - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrInvalidToken} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } u, err := s.DB.User(ctx, claims.UserID) diff --git a/backend/routes/auth/fedi_misskey.go b/backend/routes/auth/fedi_misskey.go index 6331def..9d869ad 100644 --- a/backend/routes/auth/fedi_misskey.go +++ b/backend/routes/auth/fedi_misskey.go @@ -164,8 +164,8 @@ func (s *Server) misskeyLink(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) // only site tokens can be used for this endpoint - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrInvalidToken} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } req, err := Decode[fediLinkRequest](r) diff --git a/backend/routes/auth/invite.go b/backend/routes/auth/invite.go index c71e24f..cb9e661 100644 --- a/backend/routes/auth/invite.go +++ b/backend/routes/auth/invite.go @@ -32,6 +32,10 @@ func (s *Server) getInvites(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + is, err := s.DB.UserInvites(ctx, claims.UserID) if err != nil { return errors.Wrap(err, "getting user invites") @@ -54,6 +58,10 @@ func (s *Server) createInvite(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + inv, err := s.DB.CreateInvite(ctx, claims.UserID) if err != nil { if err == db.ErrTooManyInvites { diff --git a/backend/routes/auth/tokens.go b/backend/routes/auth/tokens.go index 5a79042..211126d 100644 --- a/backend/routes/auth/tokens.go +++ b/backend/routes/auth/tokens.go @@ -33,6 +33,10 @@ func (s *Server) getTokens(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + tokens, err := s.DB.Tokens(ctx, claims.UserID) if err != nil { return errors.Wrap(err, "getting tokens") @@ -52,7 +56,7 @@ func (s *Server) deleteToken(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) if claims.APIToken { - return server.APIError{Code: server.ErrInvalidToken} + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } tx, err := s.DB.Begin(ctx) @@ -89,7 +93,7 @@ func (s *Server) createToken(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) if claims.APIToken { - return server.APIError{Code: server.ErrInvalidToken} + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } readOnly := r.FormValue("read_only") == "true" diff --git a/backend/routes/member/create_member.go b/backend/routes/member/create_member.go index d72d75f..fdce8ad 100644 --- a/backend/routes/member/create_member.go +++ b/backend/routes/member/create_member.go @@ -27,6 +27,10 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This token is read-only"} + } + u, err := s.DB.User(ctx, claims.UserID) if err != nil { return errors.Wrap(err, "getting user") diff --git a/backend/routes/member/delete_member.go b/backend/routes/member/delete_member.go index 672d3e6..f02cbd3 100644 --- a/backend/routes/member/delete_member.go +++ b/backend/routes/member/delete_member.go @@ -17,6 +17,10 @@ func (s *Server) deleteMember(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "this token is read-only"} + } + id, err := xid.FromString(chi.URLParam(r, "memberRef")) if err != nil { return server.APIError{Code: server.ErrMemberNotFound} diff --git a/backend/routes/member/patch_member.go b/backend/routes/member/patch_member.go index 2956114..c83746d 100644 --- a/backend/routes/member/patch_member.go +++ b/backend/routes/member/patch_member.go @@ -30,6 +30,10 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This token is read-only"} + } + id, err := xid.FromString(chi.URLParam(r, "memberRef")) if err != nil { return server.APIError{Code: server.ErrMemberNotFound} diff --git a/backend/routes/mod/create_report.go b/backend/routes/mod/create_report.go index 1b4b505..a516bc0 100644 --- a/backend/routes/mod/create_report.go +++ b/backend/routes/mod/create_report.go @@ -22,6 +22,10 @@ func (s *Server) createUserReport(w http.ResponseWriter, r *http.Request) error ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This token is read-only"} + } + userID, err := xid.FromString(chi.URLParam(r, "id")) if err != nil { return server.APIError{Code: server.ErrBadRequest, Details: "Invalid user ID"} @@ -65,6 +69,10 @@ func (s *Server) createMemberReport(w http.ResponseWriter, r *http.Request) erro ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This token is read-only"} + } + memberID, err := xid.FromString(chi.URLParam(r, "id")) if err != nil { return server.APIError{Code: server.ErrBadRequest, Details: "Invalid member ID"} diff --git a/backend/routes/mod/warnings.go b/backend/routes/mod/warnings.go index 6419d78..d97fd6c 100644 --- a/backend/routes/mod/warnings.go +++ b/backend/routes/mod/warnings.go @@ -44,6 +44,10 @@ func (s *Server) ackWarning(w http.ResponseWriter, r *http.Request) (err error) ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if !claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { return server.APIError{Code: server.ErrBadRequest} diff --git a/backend/routes/user/delete_user.go b/backend/routes/user/delete_user.go index 80b498a..49a2143 100644 --- a/backend/routes/user/delete_user.go +++ b/backend/routes/user/delete_user.go @@ -12,8 +12,8 @@ func (s *Server) deleteUser(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) - if claims.APIToken || !claims.TokenWrite { - return server.APIError{Code: server.ErrMissingPermissions} + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} } tx, err := s.DB.Begin(ctx) diff --git a/backend/routes/user/export.go b/backend/routes/user/export.go index 32f8a3f..4748258 100644 --- a/backend/routes/user/export.go +++ b/backend/routes/user/export.go @@ -14,6 +14,10 @@ func (s *Server) startExport(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + hasExport, err := s.DB.HasRecentExport(ctx, claims.UserID) if err != nil { log.Errorf("checking if user has recent export: %v", err) @@ -56,6 +60,10 @@ func (s *Server) getExport(w http.ResponseWriter, r *http.Request) error { ctx := r.Context() claims, _ := server.ClaimsFromContext(ctx) + if claims.APIToken { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"} + } + de, err := s.DB.UserExport(ctx, claims.UserID) if err != nil { if err == db.ErrNoExport { diff --git a/backend/routes/user/patch_user.go b/backend/routes/user/patch_user.go index b83bcf1..622621d 100644 --- a/backend/routes/user/patch_user.go +++ b/backend/routes/user/patch_user.go @@ -28,6 +28,10 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error { claims, _ := server.ClaimsFromContext(ctx) + if !claims.TokenWrite { + return server.APIError{Code: server.ErrMissingPermissions, Details: "This token is read-only"} + } + var req PatchUserRequest err := render.Decode(r, &req) if err != nil {