コード例 #1
0
ファイル: login.py プロジェクト: DoTamKma/debug-edx-platform
def _check_shib_redirect(user):
    """
    See if the user has a linked shibboleth account, if so, redirect the user to shib-login.
    This behavior is pretty much like what gmail does for shibboleth.  Try entering some @stanford.edu
    address into the Gmail login.
    """
    if settings.FEATURES.get('AUTH_USE_SHIB') and user:
        try:
            eamap = ExternalAuthMap.objects.get(user=user)
            if eamap.external_domain.startswith(
                    openedx.core.djangoapps.external_auth.views.
                    SHIBBOLETH_DOMAIN_PREFIX):
                raise AuthFailedError('', redirect=reverse('shib-login'))
        except ExternalAuthMap.DoesNotExist:
            # This is actually the common case, logging in user without external linked login
            AUDIT_LOG.info(u"User %s w/o external auth attempting login", user)
コード例 #2
0
def _get_user_by_email(request):
    """
    Finds a user object in the database based on the given request, ignores all fields except for email.
    """
    if 'email' not in request.POST or 'password' not in request.POST:
        raise AuthFailedError(_('There was an error receiving your login information. Please email us.'))

    email = request.POST['email']

    try:
        return User.objects.get(email=email)
    except User.DoesNotExist:
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            AUDIT_LOG.warning(u"Login failed - Unknown user email")
        else:
            AUDIT_LOG.warning(u"Login failed - Unknown user email: {0}".format(email))
コード例 #3
0
ファイル: cookies.py プロジェクト: IamHDT/thenextdev-edx
def refresh_jwt_cookies(request, response):
    """
    Resets the JWT related cookies in the response, while expecting a refresh
    cookie in the request.
    """
    try:
        refresh_token = request.COOKIES[jwt_cookies.jwt_refresh_cookie_name()]
    except KeyError:
        raise AuthFailedError(u"JWT Refresh Cookie not found in request.")

    # TODO don't extend the cookie expiration - reuse value from existing cookie
    cookie_settings = standard_cookie_settings(request)
    _create_and_set_jwt_cookies(response,
                                request,
                                cookie_settings,
                                refresh_token=refresh_token)
    return response
コード例 #4
0
def _enforce_password_policy_compliance(request, user):  # lint-amnesty, pylint: disable=missing-function-docstring
    try:
        password_policy_compliance.enforce_compliance_on_login(user, request.POST.get('password'))
    except password_policy_compliance.NonCompliantPasswordWarning as e:
        # Allow login, but warn the user that they will be required to reset their password soon.
        PageLevelMessages.register_warning_message(request, str(e))
    except password_policy_compliance.NonCompliantPasswordException as e:
        # Increment the lockout counter to safguard from further brute force requests
        # if user's password has been compromised.
        if LoginFailures.is_feature_enabled():
            LoginFailures.increment_lockout_counter(user)

        AUDIT_LOG.info("Password reset initiated for email %s.", user.email)
        send_password_reset_email_for_user(user, request)

        # Prevent the login attempt.
        raise AuthFailedError(HTML(str(e)), error_code=e.__class__.__name__)  # lint-amnesty, pylint: disable=raise-missing-from
コード例 #5
0
ファイル: login.py プロジェクト: dcadams/cisco_edx-platform
def _log_and_raise_inactive_user_auth_error(unauthenticated_user):
    """
    Depending on Django version we can get here a couple of ways, but this takes care of logging an auth attempt
    by an inactive user, re-sending the activation email, and raising an error with the correct message.
    """
    if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user.id: {0}, resending activation"
            .format(unauthenticated_user.id))
    else:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user {0}, resending activation"
            .format(unauthenticated_user.username))

    send_reactivation_email_for_user(unauthenticated_user)
    raise AuthFailedError(
        _generate_not_activated_message(unauthenticated_user))
コード例 #6
0
ファイル: login.py プロジェクト: REDHOUSEVE/edx-platform
def _do_third_party_auth(request):
    """
    User is already authenticated via 3rd party, now try to find and return their associated Django user.
    """
    running_pipeline = pipeline.get(request)
    username = running_pipeline['kwargs'].get('username')
    backend_name = running_pipeline['backend']
    third_party_uid = running_pipeline['kwargs']['uid']
    requested_provider = provider.Registry.get_from_pipeline(running_pipeline)
    platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)

    try:
        return pipeline.get_authenticated_user(requested_provider, username, third_party_uid)
    except User.DoesNotExist:
        AUDIT_LOG.info(
            u"Login failed - user with username {username} has no social auth "
            "with backend_name {backend_name}".format(
                username=username, backend_name=backend_name)
        )
        message = _(
            "You've successfully logged into your {provider_name} account, "
            "but this account isn't linked with an {platform_name} account yet."
        ).format(
            platform_name=platform_name,
            provider_name=requested_provider.name,
        )
        message += "<br/><br/>"
        message += _(
            "Use your {platform_name} username and password to log into {platform_name} below, "
            "and then link your {platform_name} account with {provider_name} from your dashboard."
        ).format(
            platform_name=platform_name,
            provider_name=requested_provider.name,
        )
        message += "<br/><br/>"
        message += Text(_(
            "If you don't have an {platform_name} account yet, "
            "click {register_label_strong} at the top of the page."
        )).format(
            platform_name=platform_name,
            register_label_strong=HTML('<strong>{register_text}</strong>').format(
                register_text=_('Register')
            )
        )

        raise AuthFailedError(message)
コード例 #7
0
def _get_user_by_email_or_username(request):
    """
    Finds a user object in the database based on the given request, ignores all fields except for email and username.
    """
    if 'email_or_username' not in request.POST or 'password' not in request.POST:
        raise AuthFailedError(
            _('There was an error receiving your login information. Please email us.'
              ))

    email_or_username = request.POST.get('email_or_username', None)
    try:
        return USER_MODEL.objects.get(
            Q(username=email_or_username) | Q(email=email_or_username))
    except USER_MODEL.DoesNotExist:
        digest = hashlib.shake_128(
            email_or_username.encode('utf-8')).hexdigest(16)  # pylint: disable=too-many-function-args
        AUDIT_LOG.warning(
            f"Login failed - Unknown user username/email {digest}")
コード例 #8
0
ファイル: login.py プロジェクト: ye-lin-aung/edx-platform
def _authenticate_first_party(request, unauthenticated_user):
    """
    Use Django authentication on the given request, using rate limiting if configured
    """

    # If the user doesn't exist, we want to set the username to an invalid username so that authentication is guaranteed
    # to fail and we can take advantage of the ratelimited backend
    username = unauthenticated_user.username if unauthenticated_user else ""

    try:
        return authenticate(
            username=username,
            password=request.POST['password'],
            request=request)

    # This occurs when there are too many attempts from the same IP address
    except RateLimitException:
        raise AuthFailedError(_('Too many failed login attempts. Try again later.'))
コード例 #9
0
def _log_and_raise_inactive_user_auth_error(unauthenticated_user):
    """
    Depending on Django version we can get here a couple of ways, but this takes care of logging an auth attempt
    by an inactive user, re-sending the activation email, and raising an error with the correct message.
    """
    if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user.id: {0}, resending activation"
            .format(unauthenticated_user.id))
    else:
        AUDIT_LOG.warning(
            u"Login failed - Account not active for user {0}, resending activation"
            .format(unauthenticated_user.username))

    profile = UserProfile.objects.get(user=unauthenticated_user)
    compose_and_send_activation_email(unauthenticated_user, profile)

    raise AuthFailedError(error_code='inactive-user')
コード例 #10
0
def _check_user_auth_flow(site, user):
    """
    Check if user belongs to an allowed domain and not whitelisted
    then ask user to login through allowed domain SSO provider.
    """
    if user and authn_waffle.is_enabled(ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY):
        allowed_domain = site.configuration.get_value('THIRD_PARTY_AUTH_ONLY_DOMAIN', '').lower()
        user_domain = user.email.split('@')[1].strip().lower()

        # If user belongs to allowed domain and not whitelisted then user must login through allowed domain SSO
        if user_domain == allowed_domain and not AllowedAuthUser.objects.filter(site=site, email=user.email).exists():
            msg = _(
                u'As an {allowed_domain} user, You must login with your {allowed_domain} {provider} account.'
            ).format(
                allowed_domain=allowed_domain,
                provider=site.configuration.get_value('THIRD_PARTY_AUTH_ONLY_PROVIDER')
            )
            raise AuthFailedError(msg)
コード例 #11
0
def _log_and_raise_inactive_user_auth_error(unauthenticated_user):
    """
    Depending on Django version we can get here a couple of ways, but this takes care of logging an auth attempt
    by an inactive user, re-sending the activation email, and raising an error with the correct message.
    """
    AUDIT_LOG.warning(
        f"Login failed - Account not active for user.id: {unauthenticated_user.id}, resending activation"
    )

    profile = UserProfile.objects.get(user=unauthenticated_user)
    compose_and_send_activation_email(unauthenticated_user, profile)

    raise AuthFailedError(
        error_code='inactive-user',
        context={
            'platformName': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
            'supportLink': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK)
        }
    )
コード例 #12
0
def _handle_failed_authentication(user, authenticated_user):
    """
    Handles updating the failed login count, inactive user notifications, and logging failed authentications.
    """
    if user:
        if LoginFailures.is_feature_enabled():
            LoginFailures.increment_lockout_counter(user)

        if authenticated_user and not user.is_active:
            _log_and_raise_inactive_user_auth_error(user)

        # if we didn't find this username earlier, the account for this email
        # doesn't exist, and doesn't have a corresponding password
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            loggable_id = user.id if user else "<unknown>"
            AUDIT_LOG.warning(u"Login failed - password for user.id: {0} is invalid".format(loggable_id))
        else:
            AUDIT_LOG.warning(u"Login failed - password for {0} is invalid".format(user.email))

    raise AuthFailedError(_('Email or password is incorrect.'))
コード例 #13
0
ファイル: login.py プロジェクト: mayukhpsm/edx-platform
def _check_user_auth_flow(site, user):
    """
    Check if user belongs to an allowed domain and not whitelisted
    then ask user to login through allowed domain SSO provider.
    """
    if user and ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY.is_enabled():
        allowed_domain = site.configuration.get_value(
            'THIRD_PARTY_AUTH_ONLY_DOMAIN', '').lower()
        email_parts = user.email.split('@')
        if len(email_parts) != 2:
            # User has a nonstandard email so we record their id.
            # we don't record their e-mail in case there is sensitive info accidentally
            # in there.
            set_custom_attribute('login_tpa_domain_shortcircuit_user_id',
                                 user.id)
            log.warn(
                "User %s has nonstandard e-mail. Shortcircuiting THIRD_PART_AUTH_ONLY_DOMAIN check.",
                user.id)
            return
        user_domain = email_parts[1].strip().lower()

        # If user belongs to allowed domain and not whitelisted then user must login through allowed domain SSO
        if user_domain == allowed_domain and not AllowedAuthUser.objects.filter(
                site=site, email=user.email).exists():
            msg = Text(
                _(u'As {allowed_domain} user, You must login with your {allowed_domain} '
                  u'{link_start}{provider} account{link_end}.')
            ).format(
                allowed_domain=allowed_domain,
                link_start=HTML("<a href='{tpa_provider_link}'>").format(
                    tpa_provider_link='{dashboard_url}?tpa_hint={tpa_hint}'.
                    format(
                        dashboard_url=reverse('dashboard'),
                        tpa_hint=site.configuration.get_value(
                            'THIRD_PARTY_AUTH_ONLY_HINT'),
                    )),
                provider=site.configuration.get_value(
                    'THIRD_PARTY_AUTH_ONLY_PROVIDER'),
                link_end=HTML("</a>"))
            raise AuthFailedError(msg)
コード例 #14
0
def _authenticate_first_party(request, unauthenticated_user,
                              third_party_auth_requested):
    """
    Use Django authentication on the given request, using rate limiting if configured
    """
    should_be_rate_limited = getattr(request, 'limited', False)
    if should_be_rate_limited:
        raise AuthFailedError(
            _('Too many failed login attempts. Try again later.'))  # lint-amnesty, pylint: disable=raise-missing-from

    # If the user doesn't exist, we want to set the username to an invalid username so that authentication is guaranteed
    # to fail and we can take advantage of the ratelimited backend
    username = unauthenticated_user.username if unauthenticated_user else ""

    # First time when a user login through third_party_auth account then user needs to link
    # third_party account with the platform account by login through email and password that's
    # why we need to by-pass this check when user is already authenticated by third_party_auth.
    if not third_party_auth_requested:
        _check_user_auth_flow(request.site, unauthenticated_user)

    password = normalize_password(request.POST['password'])
    return authenticate(username=username, password=password, request=request)
コード例 #15
0
def _generate_locked_out_error_message():
    """
    Helper function to generate error message for users consumed all
    login attempts.
    """

    locked_out_period_in_sec = settings.MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS
    error_message = Text(
        _('To protect your account, it’s been temporarily '
          'locked. Try again in {locked_out_period} minutes.'
          '{li_start}To be on the safe side, you can reset your '
          'password {link_start}here{link_end} before you try again.')
    ).format(link_start=HTML(
        '<a http="#login" class="form-toggle" data-type="password-reset">'),
             link_end=HTML('</a>'),
             li_start=HTML('<li>'),
             li_end=HTML('</li>'),
             locked_out_period=int(locked_out_period_in_sec / 60))
    raise AuthFailedError(
        error_message,
        error_code='account-locked-out',
        context={'locked_out_period': int(locked_out_period_in_sec / 60)})
コード例 #16
0
def _get_user_by_email_or_username(request, api_version):
    """
    Finds a user object in the database based on the given request, ignores all fields except for email and username.
    """
    is_api_v2 = api_version != API_V1
    login_fields = ['email', 'password']
    if is_api_v2:
        login_fields = ['email_or_username', 'password']

    if any(f not in request.POST.keys() for f in login_fields):
        raise AuthFailedError(_('There was an error receiving your login information. Please email us.'))

    email_or_username = request.POST.get('email', None) or request.POST.get('email_or_username', None)
    user = _get_user_by_email(email_or_username)

    if not user and is_api_v2:
        # If user not found with email and API_V2, try username lookup
        user = _get_user_by_username(email_or_username)

    if not user:
        digest = hashlib.shake_128(email_or_username.encode('utf-8')).hexdigest(16)  # pylint: disable=too-many-function-args
        AUDIT_LOG.warning(f"Login failed - Unknown user email or username {digest}")

    return user
コード例 #17
0
def _check_excessive_login_attempts_viatris(user,site):
    """
    See if account has been locked out due to excessive login failures
    """
    #log.info('site--> %s', site)
    if 'viatris-via' not in site:
        if user and LoginFailures.is_feature_enabled():
            if LoginFailures.is_user_locked_out(user):
                if 'viatris-via' in site:
                    raise AuthFailedError(_('The account has been temporarily locked due to excessive login failures. Try again after 5 mins. or Sign In using the One Time Password(OTP) option.By entering you email address you will receive a OTP (One Time Password) by email that will be valid for 2 minutes and you can use it to sign-in into the VIA platform and join lectures.'))
                elif 'viatris-kreon' in site or 'viatris-farmaciaformacion' in site:
                    #log.info('site3--> %s', site)
                    raise AuthFailedError(_('La cuenta se ha bloqueado temporalmente debido a un numero excesivo de errores de inicio de sesion. Intentalo de nuevo despus de 5 minutos.'))
                elif 'viatris-pvp-i' in site or 'viatris-multimodal' in site:
                    raise AuthFailedError(_('The account has been temporarily locked due to excessive login failures. Try again after 5 mins.'))
                elif 'viatris-atpon' in site:
                    raise AuthFailedError(_('Konto zostalo tymczasowo zablokowane z powodu nadmiernych niepowodzen logowania. Sprobuj ponownie za 5 minut.'))
                elif 'viatris-norge' in site:
                    raise AuthFailedError(_('Kontoen er midlertidig last pa grunn av store paloggingsfeil. Prov pa nytt etter 5 minutter.'))
                else:
                    raise AuthFailedError(_('The account has been temporarily locked due to excessive login failures. Try again after 5 mins. or Sign In using the One Time Password(OTP) option.'))
コード例 #18
0
def _handle_failed_authentication(user, authenticated_user):
    """
    Handles updating the failed login count, inactive user notifications, and logging failed authentications.
    """
    if user:
        if LoginFailures.is_feature_enabled():
            LoginFailures.increment_lockout_counter(user)

        if authenticated_user and not user.is_active:
            _log_and_raise_inactive_user_auth_error(user)

        # if we didn't find this username earlier, the account for this email
        # doesn't exist, and doesn't have a corresponding password
        if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
            loggable_id = user.id if user else "<unknown>"
            AUDIT_LOG.warning(
                u"Login failed - password for user.id: {0} is invalid".format(
                    loggable_id))
        else:
            AUDIT_LOG.warning(
                u"Login failed - password for {0} is invalid".format(
                    user.email))

    if user and LoginFailures.is_feature_enabled():
        blocked_threshold, failure_count = LoginFailures.check_user_reset_password_threshold(
            user)
        if blocked_threshold:
            if not LoginFailures.is_user_locked_out(user):
                max_failures_allowed = settings.MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED
                remaining_attempts = max_failures_allowed - failure_count
                if not should_redirect_to_logistration_mircrofrontend:  # pylint: disable=no-else-raise
                    raise AuthFailedError(
                        Text(
                            _('Email or password is incorrect.'
                              '{li_start}You have {remaining_attempts} more sign-in '
                              'attempts before your account is temporarily locked.{li_end}'
                              '{li_start}If you\'ve forgotten your password, click '
                              '{link_start}here{link_end} to reset.{li_end}')).
                        format(link_start=HTML(
                            '<a http="#login" class="form-toggle" data-type="password-reset">'
                        ),
                               link_end=HTML('</a>'),
                               li_start=HTML('<li>'),
                               li_end=HTML('</li>'),
                               remaining_attempts=remaining_attempts))
                else:
                    raise AuthFailedError(
                        Text(
                            _('Email or password is incorrect.\n'
                              'You have {remaining_attempts} more sign-in '
                              'attempts before your account is temporarily locked.\n'
                              'If you{quote}ve forgotten your password, click '
                              '{link_start}here{link_end} to reset.\n')).
                        format(quote=HTML("'"),
                               link_start=HTML('<a href="/reset" >'),
                               link_end=HTML('</a>'),
                               remaining_attempts=remaining_attempts))
            else:
                _generate_locked_out_error_message()

    raise AuthFailedError(_('Email or password is incorrect.'))
コード例 #19
0
def login_user(request):
    """
    AJAX request to log in the user.
    """
    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    first_party_auth_requested = bool(request.POST.get('email')) or bool(
        request.POST.get('password'))
    is_user_third_party_authenticated = False

    try:
        if third_party_auth_requested and not first_party_auth_requested:
            # The user has already authenticated via third-party auth and has not
            # asked to do first party auth by supplying a username or password. We
            # now want to put them through the same logging and cookie calculation
            # logic as with first-party auth.

            # This nested try is due to us only returning an HttpResponse in this
            # one case vs. JsonResponse everywhere else.
            try:
                user = _do_third_party_auth(request)
                is_user_third_party_authenticated = True
            except AuthFailedError as e:
                return HttpResponse(e.value,
                                    content_type="text/plain",
                                    status=403)
        else:
            user = _get_user_by_email(request)

        _check_shib_redirect(user)
        _check_excessive_login_attempts(user)

        possibly_authenticated_user = user

        if not is_user_third_party_authenticated:
            possibly_authenticated_user = _authenticate_first_party(
                request, user)
            if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login(
            ):
                # Important: This call must be made AFTER the user was successfully authenticated.
                _enforce_password_policy_compliance(
                    request, possibly_authenticated_user)

        if possibly_authenticated_user is None or not possibly_authenticated_user.is_active:
            _handle_failed_authentication(user, possibly_authenticated_user)

        if not is_edly_user_allowed_to_login(request,
                                             possibly_authenticated_user):
            if user_can_login_on_requested_edly_organization(
                    request, possibly_authenticated_user):
                create_user_link_with_edly_sub_organization(
                    request, possibly_authenticated_user)
            else:
                raise AuthFailedError(
                    _('You are not allowed to login on this site.'))

        _handle_successful_authentication_and_login(
            possibly_authenticated_user, request)

        redirect_url = None  # The AJAX method calling should know the default destination upon success
        if is_user_third_party_authenticated:
            running_pipeline = pipeline.get(request)
            redirect_url = pipeline.get_complete_url(
                backend_name=running_pipeline['backend'])

        response = JsonResponse({
            'success': True,
            'redirect_url': redirect_url,
        })

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        return set_logged_in_cookies(request, response,
                                     possibly_authenticated_user)
    except AuthFailedError as error:
        log.exception(error.get_response())
        return JsonResponse(error.get_response())
コード例 #20
0
ファイル: login.py プロジェクト: angelapper/edx-platform
def login_user(request, api_version='v1'):  # pylint: disable=too-many-statements
    """
    AJAX request to log in the user.

    Arguments:
        request (HttpRequest)

    Required params:
        email, password

    Optional params:
        analytics: a JSON-encoded object with additional info to include in the login analytics event. The only
            supported field is "enroll_course_id" to indicate that the user logged in while enrolling in a particular
            course.

    Returns:
        HttpResponse: 200 if successful.
            Ex. {'success': true}
        HttpResponse: 400 if the request failed.
            Ex. {'success': false, 'value': '{'success': false, 'value: 'Email or password is incorrect.'}
        HttpResponse: 403 if successful authentication with a third party provider but does not have a linked account.
            Ex. {'success': false, 'error_code': 'third-party-auth-with-no-linked-account'}

    Example Usage:

        POST /login_ajax
        with POST params `email`, `password`

        200 {'success': true}

    """
    _parse_analytics_param_for_course_id(request)

    third_party_auth_requested = third_party_auth.is_enabled(
    ) and pipeline.running(request)
    first_party_auth_requested = bool(request.POST.get('email')) or bool(
        request.POST.get('password'))
    is_user_third_party_authenticated = False

    set_custom_attribute('login_user_course_id', request.POST.get('course_id'))

    if is_require_third_party_auth_enabled(
    ) and not third_party_auth_requested:
        return HttpResponseForbidden(
            "Third party authentication is required to login. Username and password were received instead."
        )
    possibly_authenticated_user = None
    try:
        if third_party_auth_requested and not first_party_auth_requested:
            # The user has already authenticated via third-party auth and has not
            # asked to do first party auth by supplying a username or password. We
            # now want to put them through the same logging and cookie calculation
            # logic as with first-party auth.

            # This nested try is due to us only returning an HttpResponse in this
            # one case vs. JsonResponse everywhere else.
            try:
                user = _do_third_party_auth(request)
                is_user_third_party_authenticated = True
                set_custom_attribute('login_user_tpa_success', True)
            except AuthFailedError as e:
                set_custom_attribute('login_user_tpa_success', False)
                set_custom_attribute('login_user_tpa_failure_msg', e.value)
                if e.error_code:
                    set_custom_attribute('login_error_code', e.error_code)

                # user successfully authenticated with a third party provider, but has no linked Open edX account
                response_content = e.get_response()
                return JsonResponse(response_content, status=403)
        else:
            user = _get_user_by_email_or_username(request, api_version)

        _check_excessive_login_attempts(user)

        possibly_authenticated_user = user

        try:
            possibly_authenticated_user = StudentLoginRequested.run_filter(
                user=possibly_authenticated_user)
        except StudentLoginRequested.PreventLogin as exc:
            raise AuthFailedError(
                str(exc),
                redirect_url=exc.redirect_to,
                error_code=exc.error_code,
                context=exc.context,
            ) from exc

        if not is_user_third_party_authenticated:
            possibly_authenticated_user = _authenticate_first_party(
                request, user, third_party_auth_requested)
            if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login(
            ):
                # Important: This call must be made AFTER the user was successfully authenticated.
                _enforce_password_policy_compliance(
                    request, possibly_authenticated_user)

        if possibly_authenticated_user is None or not (
                possibly_authenticated_user.is_active
                or settings.MARKETING_EMAILS_OPT_IN):
            _handle_failed_authentication(user, possibly_authenticated_user)

        pwned_properties = check_pwned_password_and_send_track_event(
            user.id, request.POST.get('password'),
            user.is_staff) if not is_user_third_party_authenticated else {}
        # Set default for third party login
        password_frequency = pwned_properties.get('frequency', -1)
        if (settings.ENABLE_AUTHN_LOGIN_BLOCK_HIBP_POLICY
                and password_frequency >=
                settings.HIBP_LOGIN_BLOCK_PASSWORD_FREQUENCY_THRESHOLD):
            raise VulnerablePasswordError(
                accounts.AUTHN_LOGIN_BLOCK_HIBP_POLICY_MSG,
                'require-password-change')

        _handle_successful_authentication_and_login(
            possibly_authenticated_user, request)

        # The AJAX method calling should know the default destination upon success
        redirect_url, finish_auth_url = None, ''

        if third_party_auth_requested:
            running_pipeline = pipeline.get(request)
            finish_auth_url = pipeline.get_complete_url(
                backend_name=running_pipeline['backend'])

        if is_user_third_party_authenticated:
            redirect_url = finish_auth_url
        elif should_redirect_to_authn_microfrontend():
            next_url, root_url = get_next_url_for_login_page(request,
                                                             include_host=True)
            redirect_url = get_redirect_url_with_host(
                root_url,
                enterprise_selection_page(request, possibly_authenticated_user,
                                          finish_auth_url or next_url))

        if (settings.ENABLE_AUTHN_LOGIN_NUDGE_HIBP_POLICY
                and 0 <= password_frequency <=
                settings.HIBP_LOGIN_NUDGE_PASSWORD_FREQUENCY_THRESHOLD):
            raise VulnerablePasswordError(
                accounts.AUTHN_LOGIN_NUDGE_HIBP_POLICY_MSG,
                'nudge-password-change', redirect_url)

        response = JsonResponse({
            'success': True,
            'redirect_url': redirect_url,
        })

        # Ensure that the external marketing site can
        # detect that the user is logged in.
        response = set_logged_in_cookies(request, response,
                                         possibly_authenticated_user)
        set_custom_attribute('login_user_auth_failed_error', False)
        set_custom_attribute('login_user_response_status',
                             response.status_code)
        set_custom_attribute('login_user_redirect_url', redirect_url)
        mark_user_change_as_expected(user.id)
        return response
    except AuthFailedError as error:
        response_content = error.get_response()
        log.exception(response_content)

        error_code = response_content.get('error_code')
        if error_code:
            set_custom_attribute('login_error_code', error_code)
        email_or_username_key = 'email' if api_version == API_V1 else 'email_or_username'
        email_or_username = request.POST.get(email_or_username_key, None)
        email_or_username = possibly_authenticated_user.email if possibly_authenticated_user else email_or_username
        response_content['email'] = email_or_username
    except VulnerablePasswordError as error:
        response_content = error.get_response()
        log.exception(response_content)

    response = JsonResponse(response_content, status=400)
    set_custom_attribute('login_user_auth_failed_error', True)
    set_custom_attribute('login_user_response_status', response.status_code)
    return response