resetAuthPasswordWithToken function
Future<AuthPasswordResetResult>
resetAuthPasswordWithToken({
- required AuthStore store,
- required PasswordHasher passwordHasher,
- required String token,
- required String newPassword,
- AuthTwoFactorTrustedDeviceStore? trustedDeviceStore,
- PasswordPolicy passwordPolicy = const PasswordPolicy(),
- DateTime? now,
Consumes a reset token, replaces the user's password, and revokes sessions.
Password policy validation runs before token consumption so a caller can correct weak input without burning a valid reset link. Once a valid token is consumed, failures are deliberately fail-closed: the token cannot be replayed to retry a potentially partially completed reset.
Implementation
Future<AuthPasswordResetResult> resetAuthPasswordWithToken({
required AuthStore store,
required PasswordHasher passwordHasher,
required String token,
required String newPassword,
AuthTwoFactorTrustedDeviceStore? trustedDeviceStore,
PasswordPolicy passwordPolicy = const PasswordPolicy(),
DateTime? now,
}) async {
final passwordError = passwordPolicy.validateRegistration(newPassword);
if (passwordError != null) {
throw AuthFlowException(passwordError);
}
final consumed = await consumeAuthPasswordResetToken(
store: store.passwordResetTokens,
token: token,
);
if (consumed == null) {
throw AuthFlowException('invalid_password_reset_token');
}
final user = await Future.sync(() => store.users.findById(consumed.userId));
if (user == null || user.id.trim().isEmpty) {
throw AuthFlowException('invalid_password_reset_token');
}
final changedAt = (now ?? DateTime.now()).toUtc();
await trustedDeviceStore?.revokeAll(user.id, now: changedAt);
// Fail closed before changing credentials: invalidate JWTs and revoke every
// server-side session first. If either operation fails, the password remains
// unchanged. Durable adapters may additionally wrap these mutations and the
// credential replacement in their own transaction.
await Future.sync(() => store.jwtVersions.rotate(user.id));
final sessionsRevoked = await Future.sync(
() => store.sessions.revokeAllForUser(user.id, revokedAt: changedAt),
);
final passwordHash = passwordHasher.hash(newPassword);
if (passwordHash.trim().isEmpty) {
throw AuthFlowException('password_reset_failed');
}
final credentialsUpdated = await Future.sync(
() => store.credentials.updatePasswordForUser(
userId: user.id,
passwordHash: passwordHash,
updatedAt: changedAt,
),
);
if (credentialsUpdated <= 0) {
throw AuthFlowException('password_reset_failed');
}
await Future.sync(() => store.passwordResetTokens.deleteForUser(user.id));
return AuthPasswordResetResult(
user: user,
credentialsUpdated: credentialsUpdated,
sessionsRevoked: sessionsRevoked,
);
}