diff --git a/backend/db/user.go b/backend/db/user.go index 20af570..6e288c7 100644 --- a/backend/db/user.go +++ b/backend/db/user.go @@ -40,6 +40,16 @@ type User struct { DeleteReason *string } +func (u User) NumProviders() (numProviders int) { + if u.Discord != nil { + numProviders++ + } + if u.Fediverse != nil { + numProviders++ + } + return numProviders +} + // usernames must match this regex var usernameRegex = regexp.MustCompile(`^[\w-.]{2,40}$`) diff --git a/backend/routes/auth/discord.go b/backend/routes/auth/discord.go index 466bb4b..7bccfcc 100644 --- a/backend/routes/auth/discord.go +++ b/backend/routes/auth/discord.go @@ -222,6 +222,11 @@ func (s *Server) discordUnlink(w http.ResponseWriter, r *http.Request) error { return server.APIError{Code: server.ErrNotLinked} } + // cannot unlink last auth provider + if u.NumProviders() <= 1 { + return server.APIError{Code: server.ErrLastProvider} + } + err = u.UnlinkDiscord(ctx, s.DB) if err != nil { return errors.Wrap(err, "updating user in db") diff --git a/backend/routes/auth/fedi_mastodon.go b/backend/routes/auth/fedi_mastodon.go index 02f6523..7a29a1a 100644 --- a/backend/routes/auth/fedi_mastodon.go +++ b/backend/routes/auth/fedi_mastodon.go @@ -249,6 +249,11 @@ func (s *Server) mastodonUnlink(w http.ResponseWriter, r *http.Request) error { return server.APIError{Code: server.ErrNotLinked} } + // cannot unlink last auth provider + if u.NumProviders() <= 1 { + return server.APIError{Code: server.ErrLastProvider} + } + err = u.UnlinkFedi(ctx, s.DB) if err != nil { return errors.Wrap(err, "updating user in db") diff --git a/backend/server/errors.go b/backend/server/errors.go index df31ca2..6ef68a8 100644 --- a/backend/server/errors.go +++ b/backend/server/errors.go @@ -96,6 +96,7 @@ const ( ErrUnsupportedInstance = 1013 // unsupported fediverse software ErrAlreadyLinked = 1014 // user already has linked account of the same type ErrNotLinked = 1015 // user already doesn't have a linked account + ErrLastProvider = 1016 // unlinking provider would leave account with no authentication method // User-related error codes ErrUserNotFound = 2001 @@ -134,6 +135,7 @@ var errCodeMessages = map[int]string{ ErrUnsupportedInstance: "Unsupported instance software", ErrAlreadyLinked: "Your account is already linked to an account of this type", ErrNotLinked: "Your account is already not linked to an account of this type", + ErrLastProvider: "This is your account's only authentication provider", ErrUserNotFound: "User not found", @@ -169,6 +171,7 @@ var errCodeStatuses = map[int]int{ ErrUnsupportedInstance: http.StatusBadRequest, ErrAlreadyLinked: http.StatusBadRequest, ErrNotLinked: http.StatusBadRequest, + ErrLastProvider: http.StatusBadRequest, ErrUserNotFound: http.StatusNotFound, diff --git a/frontend/src/lib/api/entities.ts b/frontend/src/lib/api/entities.ts index e45ebf3..2117de8 100644 --- a/frontend/src/lib/api/entities.ts +++ b/frontend/src/lib/api/entities.ts @@ -109,6 +109,7 @@ export enum ErrorCode { UnsupportedInstance = 1013, AlreadyLinked = 1014, NotLinked = 1015, + LastProvider = 1016, UserNotFound = 2001,