Я не могу повторно аутентифицировать / отозвать токен доступа пользователя в firebase - PullRequest
3 голосов
/ 20 апреля 2020

Требование

1 . Я хочу дать пользователю возможность удалить свою учетную запись, где на данный момент пользователь может войти в систему с помощью Google и телефона.

Я прочитал некоторую документацию, и оказалось, что я могу легко удалить учетную запись, если я могу повторно -Аутентифицировать пользователя, но я не смог этого сделать

Это код, который я использую для повторной аутентификации аккаунта

в настоящее время просто пытаюсь с Google

 final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
    GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(getActivity());
    if(account != null && user != null) {
        AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(),null);
        user.reauthenticate(credential)
                .addOnSuccessListener(new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        Log.d(TAG,"reauthenticated");
                    }
                })

НО вызывает ошибку, т.е. com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The supplied auth credential is malformed or has expired. [ ID Token issued at 1587271042 is stale to sign-in.

Читая некоторые документы, я также понимаю, если я не ошибаюсь, это потому, что токен действителен в течение одного часа, и я пытаюсь Доступ к нему через час, поэтому я получаю эту ошибку, я включил этот код, чтобы вы могли сказать мне альтернативный способ

Я также знаю альтернативный способ, я попытался

Нажав кнопку «Удалить учетную запись», я могу запустить процесс входа в Google, открыв диалоговое окно учетной записи Google, чтобы пользователь мог снова войти в систему, и поскольку это будет бесплатный вход sh, тогда я могу просто скажи user.delete() и это ш удалить аккаунт, но это не очень хорошая альтернатива по трем причинам

1 Пользователь будет думать, зачем ему снова выбирать аккаунт

2 Я не могу изменить заголовок этого диалогового окна, в нем всегда будет заголовок «Выберите учетную запись», чтобы продолжить «имя моего приложения», которое не отражает мое намерение удалить учетную запись.

3 пользователь не знает, что ему нужно выбрать зарегистрированную учетную запись, он может выбрать другую учетную запись

Я не хочу беспокоить пользователя, приняв его в поток входа, могу я просто refre sh токен и сразу же удалить учетную запись?

Или, если нет способа и пользователь должен снова войти в систему, могу ли я просто сделать это как-нибудь с AuthUI, потому что это будет более удобным для пользователя и для меня тоже, так как мне не придется реализовывать пользовательский интерфейс для всех провайдеров?

Есть много вопросов, связанных с этим с нулевыми ответами, я надеюсь, что этот не попадет в эту категорию

1 Ответ

3 голосов
/ 25 апреля 2020

Использование маркера аутентификации с истекшим сроком действия не позволит вам пройти аутентификацию с помощью Firebase. Поэтому сначала вы должны получить токен fre sh ID.

Если GoogleSignInAccount, сохраненный на вашем устройстве, поддерживает его (у вас есть сохраненный refre sh токен), вы сможете использовать silentSignIn() чтобы получить токен fre sh ID, который вы затем можете передать в Firebase.

Нижеприведенный поток грубо исключен из Javascript. Ожидайте опечаток и ошибок, но это должно указать вам (или кому-то еще) в правильном направлении.

public void deleteCurrentFirebaseUser() {
  final FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
  if (user == null) {
    // TODO: Throw error or show message to user
    return;
  }

  // STEP 1: Get a new ID token (using cached user info)
  Task<GoogleSignInAccount> task = mGoogleSignInClient.silentSignIn();
  task
    .continueWithTask(Continuation<GoogleSignInAccount, Task<AuthResult>>() {
      @Override
      public void then(Task<GoogleSignInAccount> silentSignInTask) {
        GoogleSignInAccount acct = silentSignInTask.getResult();
        // STEP 2: Use the new token to reauthenticate with Firebase
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        return mAuth.reauthenticate(credential);
      }
    })
    .continueWithTask(Continuation<AuthResult, Task<Void>>() {
      @Override
      public void then(Task<AuthResult> firebaseSignInTask) {
        AuthResult result = firebaseSignInTask.getResult();
        // STEP 3: If successful, delete the user
        FirebaseUser user = result.getUser();
        return user.delete();
      }
    })
    .addOnCompleteListener(this, new OnCompleteListener<Void>() {
      @Override
      public void onComplete(@NonNull Task<Void> deleteUserTask) {
        // STEP 4: Handle success/errors
        if (task.isSuccessful()) {
          // User was successfully deleted
          Log.d(TAG, "deleteCurrentFirebaseUser:success");
          // TODO: Go to sign-in screen
        } else {
          // User was not deleted
          // Google sign in, Firebase sign in or Firebase delete user operation failed.
          Log.w(TAG, "deleteCurrentFirebaseUser:failure", task.getException());
          Snackbar.make(mBinding.mainLayout, "Failed to delete user.", Snackbar.LENGTH_SHORT).show();

          final Exception taskEx = task.getException();
          if (taskEx instanceof ApiException) {
            ApiException apiEx = (ApiException) taskEx;
            int googleSignInStatusCode = apiEx.getStatusCode();
            // TODO: Handle Google sign-in exception based on googleSignInStatusCode
            // e.g. GoogleSignInStatusCodes.SIGN_IN_REQUIRED means the user needs to do something to allow background sign-in.
          } else if (taskEx instanceof FirebaseAuthException) {
            // One of:
            //  - FirebaseAuthInvalidUserException (disabled/deleted user)
            //  - FirebaseAuthInvalidCredentialsException (token revoked/stale)
            //  - FirebaseAuthUserCollisionException (user already exists? - likely that Google Sign In wasn't originally used to create the matching account)
            //  - FirebaseAuthRecentLoginRequiredException (need to reauthenticate user - shouldn't occur with this flow)

            FirebaseAuthException firebaseAuthEx = (FirebaseAuthException) taskEx;
            String errorCode = firebaseAuthEx.getErrorCode(); // contains the reason for the exception
            String message = firebaseAuthEx.getMessage();
            // TODO: Handle Firebase Auth exception based on errorCode or more instanceof checks
          } else {
            // TODO: Handle unexpected exception
          }
        }
      }
    });
}

Альтернативой вышеупомянутому было бы использование Функция вызываемого облака , которая использует функция удаления пользователя Admin SDK, как прокомментировано @ example . Вот базовая реализация этого (без какого-либо шага подтверждения):

exports.deleteMe = functions.https.onCall((data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
  }

  const uid = context.auth.uid;

  return admin.auth().deleteUser(uid)
    .then(() => {
      console.log('Successfully deleted user');
      return 'Success!';
    })
    .catch(error => {
      console.error('Error deleting user: ', error);
      throw new functions.https.HttpsError('internal', 'Failed to delete user.', error.code);
    });
});

, который будет вызываться с использованием:

FirebaseFunctions.getInstance()
  .getHttpsCallable("deleteMe")
  .call()
  .continueWith(new Continuation<HttpsCallableResult, Void>() {
    @Override
    public void then(@NonNull Task<HttpsCallableResult> task) {
      if (task.isSuccessful()) {
        // deleted user!
      } else {
        // failed!
      }
    }
  });

Если вы используете подход облачных функций, я настоятельно рекомендую отправить подтверждение удаления на связанный адрес электронной почты пользователя перед удалением его учетной записи, просто чтобы убедиться, что это не плохой актер. Вот черновик того, что вам нужно для этого:

exports.deleteMe = functions.https.onCall((data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
  }

  const uid = context.auth.uid;

  return getEmailsForUser(context.auth)
    .then(userEmails => {
      if (data.email) { // if an email was provided, use that
        if (!userEmails.all.includes(data.email)) { // throw an error if the provided email isn't linked to this user
          throw new functions.https.HttpsError('failed-precondition', 'User is not linked to provided email.');
        }
        return sendAccountDeletionConfirmationEmail(uid, data.email);
      } else if (userEmails.primary) { // if available, send confirmation to primary email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.primary);
      } else if (userEmails.token) { // if not present, try the authentication token's email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.token);
      } else if (userEmails.all.length == 1) { // if not present but the user has only one linked email, try that
        // if not present, send confirmation to the authentication token's email
        return sendAccountDeletionConfirmationEmail(uid, userEmails.all[0]);
      } else {
        throw new functions.https.HttpsError('internal', 'User has multiple emails linked to their account. Please provide an email to use.');
      }
    })
    .then(destEmail => {
      return {message: 'Email was sent successfully!', email: email}
    });
});

exports.confirmDelete = functions.https.onRequest((req, res) => {
  const uid = request.params.uid;
  const token = request.params.token;
  const nextPath = request.params.next;

  if (!uid) {
    res.status(400).json({error: 'Missing uid parameter'});
    return;
  }

  if (!token) {
    res.status(400).json({error: 'Missing token parameter'});
    return;
  }

  return validateToken(uid, token)
    .then(() => admin.auth().deleteUser(uid))
    .then(() => {
      console.log('Successfully deleted user');
      res.redirect('https://your-app.firebaseapp.com' + (nextPath ? decodeURIComponent(nextPath) : ''));
    })
    .catch(error => {
      console.error('Error deleting user: ', error);
      res.json({error: 'Failed to delete user'});
    });
});

function getEmailsForUser(auth) {
  return admin.auth().getUser(auth.uid)
    .then(record => {
      // used to create array of unique emails
      const linkedEmailsMap = {};

      record.providerData.forEach(provider => {
        if (provider.email) {
          linkedEmailsMap[provider.email] = true;
        }
      });

      return {
        primary: record.email,
        token: auth.token.email || undefined,
        all: Object.keys(linkedEmailsMap);
      }
    });
}

function sendAccountDeletionConfirmationEmail(uid, destEmail) {
  const token = 'oauhdfaskljfnasoildfma'; // TODO: Create URL SAFE token generation logic

  // 'confirmation-tokens' should have the rules: { ".read": false, ".write": false }
  return admin.database().ref('confirmation-tokens/'+uid).set(token)
    .then(() => {
      // place uid and token in URL, and redirect to "/" when finished (next=%2F).
      const url = `https://your-app.firebaseapp.com/api/confirmDelete?uid=${uid}&${token}&next=%2F`;

      const emailBody = 'Please click <a href="' + url + '">here</a> to confirm account deletion.<br/><br/>Or you can copy "'+url+'" to your browser manually.';

      return sendEmail(destEmail, emailBody); // TODO: Create sendEmail
    })
    .then(() => destEmail);
}

function validateToken(uid, token) {
  return admin.database().ref('confirmation-tokens/'+uid).once('value')
    .then((snapshot) => {
      if (snapshot.val() !== token) {
        throw new Error('Token mismatch!');
      }
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...