createSession method

Future<AuthUserSessionDto> createSession(
  1. CreateSessionRequest request
)

Implementation

Future<AuthUserSessionDto> createSession(CreateSessionRequest request) async {
  _ensureInitialised();

  if (_useMockData) {
    await Future.delayed(const Duration(milliseconds: 500));
    // Return a mock session specifically for the requested user
    return AuthUserSessionDto.fromJson(_generateMockSession(request.userId));
  }

  try {
    final client = await _createClientWithAuthHeaders();
    final response = await client.post(
      url: '$_sessionServiceUrl/userSession/api/session/createUserSession',
      requestData: request.toJson(),
      user: _user,
    );

    final statusCode = response?.statusCode;

    // Handle PIN-related responses
    if (statusCode == 428) {
      logger.info(this, 'PIN required for user ${request.userId}');
      throw PinRequiredException(cause: response?.data);
    }

    if (statusCode == 403) {
      final data = response?.data;
      final error = _extractStringField(data, 'error') ?? 'pin_invalid';
      final locked = _extractBoolField(data, 'locked') ?? false;
      final remainingAttempts = _extractIntField(data, 'remainingAttempts');

      logger.warning(
        this,
        'PIN verification failed for user ${request.userId}: '
        '$error (locked: $locked, remaining: $remainingAttempts)',
      );

      throw PinVerificationException(
        error: error,
        locked: locked,
        remainingAttempts: remainingAttempts,
        cause: data,
      );
    }

    if (statusCode == 200 && response?.data != null) {
      return AuthUserSessionDto.fromJson(response!.data['sessionResult']);
    }

    // Parse structured error from backend
    final data = response?.data;
    final errorCode = _extractStringField(data, 'error');
    final errorMessage = _extractStringField(data, 'message');

    throw AuthException(
      _mapErrorCode(errorCode),
      cause: errorMessage ?? data,
    );
  } on PinRequiredException {
    rethrow;
  } on PinVerificationException {
    rethrow;
  } on AuthException {
    rethrow;
  } catch (e, s) {
    // Check if a DioException carries a PIN-related status code
    if (e is DioException && e.response != null) {
      final statusCode = e.response!.statusCode;
      final data = e.response!.data;

      if (statusCode == 428) {
        throw PinRequiredException(cause: data);
      }

      if (statusCode == 403) {
        final error = _extractStringField(data, 'error');
        if (error == null || error == 'pin_invalid') {
          final locked = _extractBoolField(data, 'locked') ?? false;
          final remainingAttempts =
              _extractIntField(data, 'remainingAttempts');

          throw PinVerificationException(
            error: error ?? 'pin_invalid',
            locked: locked,
            remainingAttempts: remainingAttempts,
            cause: data,
          );
        }

        // Non-PIN 403 — map via structured error code (e.g. user_not_authorized)
        final errorMessage = _extractStringField(data, 'message');
        throw AuthException(
          _mapErrorCode(error),
          cause: errorMessage ?? data,
        );
      }

      // Any other response with a structured error body — map it
      final errorCode = _extractStringField(data, 'error');
      if (errorCode != null) {
        final errorMessage = _extractStringField(data, 'message');
        throw AuthException(
          _mapErrorCode(errorCode),
          cause: errorMessage ?? data,
        );
      }
    }

    logger.error(
      this,
      'Exception while creating session',
      error: e,
      stackTrace: s,
    );
    rethrow;
  }
}