loginWithPassword method

Future<AuthLoginResult> loginWithPassword({
  1. required Dio apiDio,
  2. required AuthRestClient auth,
  3. required String email,
  4. required String password,
  5. required String keyName,
})

Order matters: verification is checked, then mustChangePassword, before anything destructive — an unverified or must-change-password account must never lose a working key on its way to being rejected. Rotation follows: the existing cli:<hostname> key is revoked before the new one is minted, because a duplicate active label 400s the create call.

Implementation

Future<AuthLoginResult> loginWithPassword({
  required Dio apiDio,
  required AuthRestClient auth,
  required String email,
  required String password,
  required String keyName,
}) async {
  final SignInResult signIn;
  try {
    signIn = await auth.signInWithPassword(email, password);
  } on AuthRestException catch (e) {
    throw _mapSignInFailure(e);
  }

  final bool verified;
  try {
    verified = await auth.lookup(signIn.idToken);
  } on AuthRestException catch (e) {
    throw AuthCliFault(
      ExitCodes.usage,
      'Could not verify account status: ${e.message}',
      errorCode: 'unknown',
    );
  }
  if (!verified) {
    throw const AuthCliFault(
      ExitCodes.authRejected,
      _emailNotVerifiedMessage,
      errorCode: 'email_not_verified',
    );
  }

  final options = Options(
    headers: {'Authorization': 'Bearer ${signIn.idToken}'},
  );

  final who = await _whoami(apiDio, options);
  if (who.mustChangePassword) {
    throw const AuthCliFault(
      ExitCodes.authRejected,
      'Set a password in the AppFlight app before using the CLI: '
      'Settings → Account.\n'
      '  Get the app: ${AppLinks.playStore}',
      errorCode: 'must_change_password',
    );
  }

  final existing = await _findKeyByName(apiDio, options, keyName);
  await _revokeIfPresent(apiDio, options, existing);
  final created = await _createKey(apiDio, options, keyName);

  return AuthLoginResult(
    apiKey: created['rawKey'] as String,
    uid: who.uid,
    email: who.email,
    keyId: created['id'] as String,
    keyName: created['name'] as String,
  );
}