Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions efile_app/efile/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,9 @@ def logout(request):
session_data = {k: v for k, v in request.session.items() if k in session_keys_to_keep}
request.session.clear()
request.session.update(session_data)

@staticmethod
def password_reset(request):
"""Calls the efile-proxy's "reset password" API, which
sends an email from Tyler to reset the password.
"""
4 changes: 4 additions & 0 deletions efile_app/efile/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class EFileLoginForm(forms.Form):
)


class EFilePasswordResetForm(forms.Form):
email = forms.EmailField(widget=forms.EmailInput(attrs={"class": "form-control", "id": "email", "required": True}))


class EFileRegistrationForm(forms.Form):
# Legal Name
first_name = forms.CharField(
Expand Down
21 changes: 12 additions & 9 deletions efile_app/efile/static/css/login.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ body {
}

.btn-sign-in,
.btn-register {
.btn-register,
.btn-password-reset
{
background: #1e3a5f;
border: none;
border-radius: 25px;
Expand All @@ -87,18 +89,12 @@ body {
font-size: 1.1rem;
color: white;
transition: all 0.3s ease;
}

.btn-sign-in {
width: 200px;
}

.btn-register {
width: 200px;
}

.btn-sign-in:hover,
.btn-register:hover {
.btn-register:hover,
.btn-password-reset:hover {
background: #1e3a5f;
filter: brightness(1.5);
transform: translateY(-1px);
Expand Down Expand Up @@ -165,3 +161,10 @@ body {
.login-form.hide {
display: none;
}

.alert-error {
--bs-alert-color: var(--bs-danger-text-emphasis);
--bs-alert-bg: var(--bs-danger-bg-subtle);
--bs-alert-border-color: var(--bs-danger-border-subtle);
--bs-alert-link-color: var(--bs-danger-text-emphasis);
}
2 changes: 1 addition & 1 deletion efile_app/efile/templates/efile/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ <h1 class="brand-title">{{ config.jurisdiction.name }}</h1>
</div>

<div class="text-center">
<a href="#" class="forgot-password">Forgot Password?</a>
<a href="{% url 'efile_password_reset' jurisdiction %}" class="forgot-password">Forgot Password?</a>
</div>
</form>

Expand Down
104 changes: 104 additions & 0 deletions efile_app/efile/templates/efile/password_reset.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ config.jurisdiction.name }} - Forgot Password</title>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
/>
<link rel="stylesheet" href="{% static 'css/login.css' %}" />
</head>
<body>
<div class="main-container">
<!-- Main Forgot Password Card -->
<div class="login-card">
<!-- Brand Header -->
<div class="brand-header">
<div class="d-flex align-items-center">
<i class="fas fa-balance-scale scales-icon"></i>
<h1 class="brand-title">{{ config.jurisdiction.name }}</h1>
</div>
</div>

<!-- Django Messages -->
{% if messages %} {% for message in messages %}
<div
class="alert alert-{{ message.tags }} alert-dismissible fade show"
role="alert"
>
{{ message }}
<button
type="button"
class="btn-close"
data-bs-dismiss="alert"
></button>
</div>
{% endfor %} {% endif %}

<!-- Sign In Form -->
<div class="reset-form">
<h2
class="mb-4"
style="color: #374151; font-weight: 700; font-size: 1.8rem"
>
Reset your eFile password
</h2>

<p class="reset-password-text">
For security, a link will be sent to your email from "efilingmail.tylertech.cloud". Visit that link
to reset your password.
</p>

<form method="post">
{% csrf_token %}
<div class="mb-3">
<label
for="{{ reset_form.email.id_for_label }}"
class="form-label"
>Email address</label
>
{{ reset_form.email }} {% if reset_form.email.errors %}
<div class="text-danger mt-1">
{% for error in reset_form.email.errors %}
<small>{{ error }}</small>
{% endfor %}
</div>
{% endif %}
</div>

<div class="text-center mb-3">
<button type="submit" name="reset_submit" class="btn btn-password-reset">
Send Reset Link
</button>
</div>
</form>
</div>
</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script>
// Add some interactive behavior
document.addEventListener("DOMContentLoaded", function () {
const inputs = document.querySelectorAll(".form-control");

inputs.forEach((input) => {
input.addEventListener("focus", function () {
this.parentElement.style.transform = "translateY(-2px)";
});

input.addEventListener("blur", function () {
this.parentElement.style.transform = "translateY(0)";
});
});
});
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion efile_app/efile/tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_settings_are_wired(settings):


def test_login_page_renders(client):
"""Basic smoke test: GET /login/ should render the login page (200)."""
"""Basic smoke test: GET "login" should render the login page (200)."""
url = reverse("efile_login", kwargs={"jurisdiction": "illinois"})
resp = client.get(url)
assert resp.status_code == 200
Expand Down
3 changes: 2 additions & 1 deletion efile_app/efile/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .views.choose_jurisdiction import choose_jurisdiction
from .views.confirmation import filing_confirmation
from .views.expert_form import efile_expert_form
from .views.login import efile_login, efile_logout
from .views.login import efile_login, efile_logout, efile_password_reset
from .views.options import efile_options
from .views.register import efile_register
from .views.review import case_review
Expand Down Expand Up @@ -39,6 +39,7 @@ def jurisdiction_homepage(request, jurisdiction):
path("jurisdiction/<jurisdiction>/login/", efile_login, name="efile_login"),
path("jurisdiction/<jurisdiction>/logout/", efile_logout, name="efile_logout"),
path("jurisdiction/<jurisdiction>/register/", efile_register, name="efile_register"),
path("jurisdiction/<jurisdiction>/password_reset/", efile_password_reset, name="efile_password_reset"),
path("jurisdiction/<jurisdiction>/options/", efile_options, name="efile_options"),
path("jurisdiction/<jurisdiction>/expert_form/", efile_expert_form, name="expert_form"),
path("jurisdiction/<jurisdiction>/upload_first/", efile_upload_first, name="upload_first"),
Expand Down
12 changes: 12 additions & 0 deletions efile_app/efile/utils/proxy_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,15 @@ def get_headers():
"User-Agent": "LITEfile-Client/1.0",
"X-API-Key": getattr(settings, "SUFFOLK_EFILE_API_KEY", None),
}


def tyler_password_reset(username, jurisdiction):
url = f"{settings.EFSP_URL}/jurisdictions/{jurisdiction}/adminusers/user/password/reset"
try:
api_key = getattr(settings, "SUFFOLK_EFILE_API_KEY", None)
headers = {"X-API-Key": api_key, "User-Agent": f"{jurisdiction.title()}-eFile-Client/1.0"}

response = requests.post(url, data=username, headers=headers, timeout=10)
return response
except Exception:
logger.exception("Auth password reset failed: %s", url)
30 changes: 29 additions & 1 deletion efile_app/efile/views/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from django.shortcuts import redirect, render

from efile.utils.config_loader import config_loader
from efile.utils.proxy_connection import tyler_password_reset

from ..forms import EFileLoginForm
from ..forms import EFileLoginForm, EFilePasswordResetForm

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -46,6 +47,33 @@ def efile_login(request, jurisdiction):
return render(request, "efile/login.html", context)


def efile_password_reset(request, jurisdiction):
if jurisdiction not in config_loader.get_available_jurisdictions():
# TODO(brycew): better prediction of spell correction? (closest juris?)
return redirect("password_reset", jurisdiction="illinois")

if request.method == "POST" and "reset_submit" in request.POST:
reset_form = EFilePasswordResetForm(request.POST)
if reset_form.is_valid():
logger.info("Actually acting on the POST")
email = reset_form.cleaned_data["email"]
result = tyler_password_reset(email, jurisdiction)
logger.info("Password reset result: %s", result)
if result.ok:
messages.success(request, "Password reset link sent to email!")
return redirect(f"/jurisdiction/{jurisdiction}/login")
else:
messages.error(request, f"Password reset failed: {result.text}")
else:
messages.error(request, "Please correct the errors below.")
else:
reset_form = EFilePasswordResetForm()

logger.info("Sending the main page")
context = {"reset_form": reset_form}
return render(request, "efile/password_reset.html", context)


def efile_logout(request, jurisdiction):
"""
Custom logout view
Expand Down