使用过期的身份验证令牌将不允许您通过 Firebase 进行身份验证。所以首先你必须获得一个新的 ID 令牌。
如果您的设备上存储的GoogleSignInAccount 支持它(您有一个存储的刷新令牌),您应该能够使用silentSignIn() 获取一个新的 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()) {
// The user was successfully deleted
Log.d(TAG, "deleteCurrentFirebaseUser:success");
// TODO: Go to sign-in screen
} else {
// The 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 (does the user already exist? - it is likely that Google Sign In wasn't originally used to create the matching account)
// - FirebaseAuthRecentLoginRequiredException (need to reauthenticate user - it 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
}
}
}
});
}
上述方法的替代方法是使用Callable Cloud Function,它使用Admin SDK's Delete User function,正如@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!
}
}
});
如果您使用 Cloud Functions 方法,我强烈建议您在删除他们的帐户之前向用户的链接电子邮件地址发送一封确认电子邮件,以确保它不是坏人。以下是实现该目标所需的粗略草案:
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 the UID and token in the 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!');
}
});
}