chatai/auth_v2.169.0/internal/api/identity.go

156 lines
5.3 KiB
Go

package api
import (
"context"
"net/http"
"github.com/fatih/structs"
"github.com/go-chi/chi/v5"
"github.com/gofrs/uuid"
"github.com/supabase/auth/internal/api/provider"
"github.com/supabase/auth/internal/models"
"github.com/supabase/auth/internal/storage"
)
func (a *API) DeleteIdentity(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims := getClaims(ctx)
if claims == nil {
return internalServerError("Could not read claims")
}
identityID, err := uuid.FromString(chi.URLParam(r, "identity_id"))
if err != nil {
return notFoundError(ErrorCodeValidationFailed, "identity_id must be an UUID")
}
aud := a.requestAud(ctx, r)
audienceFromClaims, _ := claims.GetAudience()
if len(audienceFromClaims) == 0 || aud != audienceFromClaims[0] {
return forbiddenError(ErrorCodeUnexpectedAudience, "Token audience doesn't match request audience")
}
user := getUser(ctx)
if len(user.Identities) <= 1 {
return unprocessableEntityError(ErrorCodeSingleIdentityNotDeletable, "User must have at least 1 identity after unlinking")
}
var identityToBeDeleted *models.Identity
for i := range user.Identities {
identity := user.Identities[i]
if identity.ID == identityID {
identityToBeDeleted = &identity
break
}
}
if identityToBeDeleted == nil {
return unprocessableEntityError(ErrorCodeIdentityNotFound, "Identity doesn't exist")
}
err = a.db.Transaction(func(tx *storage.Connection) error {
if terr := models.NewAuditLogEntry(r, tx, user, models.IdentityUnlinkAction, "", map[string]interface{}{
"identity_id": identityToBeDeleted.ID,
"provider": identityToBeDeleted.Provider,
"provider_id": identityToBeDeleted.ProviderID,
}); terr != nil {
return internalServerError("Error recording audit log entry").WithInternalError(terr)
}
if terr := tx.Destroy(identityToBeDeleted); terr != nil {
return internalServerError("Database error deleting identity").WithInternalError(terr)
}
switch identityToBeDeleted.Provider {
case "phone":
user.PhoneConfirmedAt = nil
if terr := user.SetPhone(tx, ""); terr != nil {
return internalServerError("Database error updating user phone").WithInternalError(terr)
}
if terr := tx.UpdateOnly(user, "phone_confirmed_at"); terr != nil {
return internalServerError("Database error updating user phone").WithInternalError(terr)
}
default:
if terr := user.UpdateUserEmailFromIdentities(tx); terr != nil {
if models.IsUniqueConstraintViolatedError(terr) {
return unprocessableEntityError(ErrorCodeEmailConflictIdentityNotDeletable, "Unable to unlink identity due to email conflict").WithInternalError(terr)
}
return internalServerError("Database error updating user email").WithInternalError(terr)
}
}
if terr := user.UpdateAppMetaDataProviders(tx); terr != nil {
return internalServerError("Database error updating user providers").WithInternalError(terr)
}
return nil
})
if err != nil {
return err
}
return sendJSON(w, http.StatusOK, map[string]interface{}{})
}
func (a *API) LinkIdentity(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
user := getUser(ctx)
rurl, err := a.GetExternalProviderRedirectURL(w, r, user)
if err != nil {
return err
}
skipHTTPRedirect := r.URL.Query().Get("skip_http_redirect") == "true"
if skipHTTPRedirect {
return sendJSON(w, http.StatusOK, map[string]interface{}{
"url": rurl,
})
}
http.Redirect(w, r, rurl, http.StatusFound)
return nil
}
func (a *API) linkIdentityToUser(r *http.Request, ctx context.Context, tx *storage.Connection, userData *provider.UserProvidedData, providerType string) (*models.User, error) {
targetUser := getTargetUser(ctx)
identity, terr := models.FindIdentityByIdAndProvider(tx, userData.Metadata.Subject, providerType)
if terr != nil {
if !models.IsNotFoundError(terr) {
return nil, internalServerError("Database error finding identity for linking").WithInternalError(terr)
}
}
if identity != nil {
if identity.UserID == targetUser.ID {
return nil, unprocessableEntityError(ErrorCodeIdentityAlreadyExists, "Identity is already linked")
}
return nil, unprocessableEntityError(ErrorCodeIdentityAlreadyExists, "Identity is already linked to another user")
}
if _, terr := a.createNewIdentity(tx, targetUser, providerType, structs.Map(userData.Metadata)); terr != nil {
return nil, terr
}
if targetUser.GetEmail() == "" {
if terr := targetUser.UpdateUserEmailFromIdentities(tx); terr != nil {
if models.IsUniqueConstraintViolatedError(terr) {
return nil, badRequestError(ErrorCodeEmailExists, DuplicateEmailMsg)
}
return nil, terr
}
if !userData.Metadata.EmailVerified {
if terr := a.sendConfirmation(r, tx, targetUser, models.ImplicitFlow); terr != nil {
return nil, terr
}
return nil, storage.NewCommitWithError(unprocessableEntityError(ErrorCodeEmailNotConfirmed, "Unverified email with %v. A confirmation email has been sent to your %v email", providerType, providerType))
}
if terr := targetUser.Confirm(tx); terr != nil {
return nil, terr
}
if targetUser.IsAnonymous {
targetUser.IsAnonymous = false
if terr := tx.UpdateOnly(targetUser, "is_anonymous"); terr != nil {
return nil, terr
}
}
}
if terr := targetUser.UpdateAppMetaDataProviders(tx); terr != nil {
return nil, terr
}
return targetUser, nil
}