Skip to content

Commit

Permalink
Added optional track user last login (#1294)
Browse files Browse the repository at this point in the history
  • Loading branch information
daveoconnor committed Oct 4, 2024
1 parent 852cefc commit 8afad74
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 20 deletions.
13 changes: 3 additions & 10 deletions templates/account/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@

{% block content %}

<div class="py-0 px-3 mb-3 md:py-6 md:px-0"
x-data="{ loginMethod: localStorage.getItem('_x_boostlogin') }"
>
<div class="py-0 px-3 mb-3 md:py-6 md:px-0">

<div class="md:pt-11 md:mt-11 w-full bg-white dark:bg-charcoal mx-auto rounded py-6 px-3">
<div class="md:w-full">
Expand Down Expand Up @@ -57,13 +55,9 @@ <h2 class="text-xl text-center mb-11">Log in with one of your existing third par

<div class="w-full md:w-1/2">
<h2 class="text-xl text-center items-center">
Or Log In with Email <span x-cloak class="text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show='loginMethod === "\"email\""'>Last Log In</span>
Or Log In with Email <span x-cloak class="text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('email')">Last Log In</span>
</h2>
<form class="login"
method="POST"
action="{% url 'account_login' %}"
x-data="{ boostlogin: $persist('email') }"
>
<form class="login" method="POST" action="{% url 'account_login' %}">
<div class="mx-auto space-y-4 w-2/3" id="signup_form">
{% csrf_token %}

Expand Down Expand Up @@ -94,7 +88,6 @@ <h2 class="text-xl text-center items-center">
<div class="flex justify-between mb-4">
<a class="text-sky-600 dark:text-sky-300 hover:text-orange dark:hover:text-orange" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
<button type="submit"
x-on:click="boostlogin = 'email'"
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md">{% trans "Log in" %}</button>
</div>
</div>
Expand Down
36 changes: 36 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@

<script>
var r = document.querySelector(':root');
const messageVisibilitySeconds = 6;
const opacityTransitionTime = 0.15; // matches the tailwind transition-opacity time
const delay = ms => new Promise(res => setTimeout(res, ms));

function modalSize(h,w) {
var rs = getComputedStyle(r);
Expand All @@ -96,6 +99,39 @@
modal.classList.remove('show-modal');
}

const hideMessage = async () => {
const message = document.getElementById('messages');
message.classList.add('opacity-0');
await delay(opacityTransitionTime * 1000);
const messageButton = message.querySelector('button');
if (messageButton) {
messageButton.click();
}
}

const providerMatchesLastLogin = async (provider) => {
const lastLoginProvider = localStorage.getItem('boostLoginMethod') || null;
return provider === lastLoginProvider;
}

const trackLoginUpdateCheck = async () => {
const footer = document.querySelector('footer');
const indicator = footer.dataset.trackLoginMethod; // intentionally not cast to boolean
const loginMethod = footer.dataset.loginMethod;
if (indicator) {
if (indicator === 'True') {
localStorage.setItem('boostLoginMethod', loginMethod);
} else {
localStorage.removeItem('boostLoginMethod');
}
}
}

(async () => {
await trackLoginUpdateCheck();
await delay(messageVisibilitySeconds * 1000);
await hideMessage();
})();
</script>

</body>
Expand Down
2 changes: 1 addition & 1 deletion templates/includes/_footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
}
}
</style>
<footer class="footer">
<footer class="footer"{% if user.is_authenticated %} data-track-login-method="{{ request.user.indicate_last_login_method }}" data-login-method="{{ request.session.boost_login_method }}"{% endif %}>
<div class="footer-container">
<div class="links">
<a href="https://lists.boost.org/mailman/listinfo.cgi/boost-users">Contact</a>
Expand Down
2 changes: 1 addition & 1 deletion templates/partials/messages.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div id="messages" class="w-full text-center" x-data="{show: true}">
<div id="messages" class="w-full text-center transition-opacity" x-data="{show: true}">
{% if messages %}
{% for message in messages %}
<div x-show="show" class="w-2/3 mx-auto text-left items-center text-slate dark:text-white rounded text-base px-3 py-2 {% if 'error' in message.tags %}bg-red-500{% else %}bg-green/70{% endif %} fade show">
Expand Down
9 changes: 5 additions & 4 deletions templates/socialaccount/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ <h1 class="text-4xl">{% blocktrans with provider.name as provider %}Log In with
<p>{% blocktrans with provider.name as provider %}You are about to log in using a third party account from {{ provider }}.{% endblocktrans %}</p>
{% endif %}

<form method="post" x-data="{ boostlogin: $persist('{{ provider.name }}') }">
<form method="post">
{% csrf_token %}
<button type="submit"
x-on:click="boostlogin = '{{ provider.name }}'"
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md">{% trans "Continue" %}</button>
<button
type="submit"
class="py-3 px-8 text-sm text-base font-medium text-white uppercase rounded-md border md:py-1 md:px-4 md:text-lg bg-orange hover:bg-orange/80 border-orange dark:bg-slate dark:hover:bg-charcoal dark:text-white hover:drop-shadow-md"
>{% trans "Continue" %}</button>
</form>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/socialaccount/snippets/provider_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class="relative w-2/3 mx-auto block px-8 py-3 text-base font-medium rounded-md border border-orange !text-white hover:!text-white bg-orange hover:bg-orange/80 dark:bg-slate dark:hover:bg-charcoal hover:drop-shadow-md md:py-4 md:text-lg md:px-10 {{provider.id}}"
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"
>
<span x-cloak class="absolute right-1 top-1 text-xs bg-white text-slate rounded p-1" x-show='loginMethod === "\"GitHub\""'>Last Log in</span>
<span x-cloak class="absolute right-1 top-1 text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('github')">Last Log in</span>
<i class="fab fa-github"></i>
Use {{provider.name}}
</a>
Expand All @@ -27,7 +27,7 @@
class="relative w-2/3 mx-auto block px-8 py-3 text-base font-medium rounded-md border border-orange !text-white hover:!text-white bg-orange hover:bg-orange/80 dark:bg-slate dark:hover:bg-charcoal hover:drop-shadow-md md:py-4 md:text-lg md:px-10 {{provider.id}}"
href="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}"
>
<span x-cloak class="absolute right-1 top-1 text-xs bg-white text-slate rounded p-1" x-show='loginMethod === "\"Google\""'>Last Log in</span>
<span x-cloak class="absolute right-1 top-1 text-xs bg-emerald-400 text-slate rounded px-1 ml-2" x-show="providerMatchesLastLogin('google')">Last Log in</span>
<i class="fab fa-google"></i>
Use {{provider.name}}
</a>
Expand Down
5 changes: 4 additions & 1 deletion templates/users/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ <h3>{% trans "Update Profile Photo" %}</h3>
{% endif %}
</div>

{% if not social_accounts %}
<div class="rounded bg-white dark:bg-charcoal p-4">
<h3>{% trans "Set Password" %}</h3>
<form method="POST" action="." class="password_set space-y-3">
Expand All @@ -103,13 +104,15 @@ <h3>{% trans "Set Password" %}</h3>
</div>
</form>
</div>

{% else %}
<div class="rounded bg-white dark:bg-charcoal p-4">
<h3>{% trans "Account Connections" %}</h3>
<div class="mt-4">
<a href="{% url 'socialaccount_connections' %}"><button class="py-2 px-3 text-sm text-white rounded bg-orange">{% trans 'Manage Account Connections' %}</button></a>
</div>
</div>
{% endif %}

</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions users/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LOGIN_METHOD_SESSION_FIELD_NAME = "boost_login_method"
2 changes: 1 addition & 1 deletion users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Meta:
class UserProfileForm(forms.ModelForm):
class Meta:
model = User
fields = ["email", "first_name", "last_name"]
fields = ["email", "first_name", "last_name", "indicate_last_login_method"]


class CustomClearableFileInput(forms.ClearableFileInput):
Expand Down
21 changes: 21 additions & 0 deletions users/migrations/0013_user_indicate_last_login_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.15 on 2024-10-02 18:34

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("users", "0012_user_can_update_image"),
]

operations = [
migrations.AddField(
model_name="user",
name="indicate_last_login_method",
field=models.BooleanField(
default=False,
help_text="Indicate on the login page the last login method used.",
),
),
]
4 changes: 4 additions & 0 deletions users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ class User(BaseUser):
"a user's ability to update their own profile photo, uncheck this box."
),
)
indicate_last_login_method = models.BooleanField(
default=False,
help_text="Indicate on the login page the last login method used.",
)

def save_image_from_github(self, avatar_url):
response = requests.get(avatar_url)
Expand Down
14 changes: 14 additions & 0 deletions users/signals.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from allauth.account.signals import user_logged_in
from django.dispatch import receiver
from django.db.models.signals import post_save

from allauth.socialaccount.models import SocialAccount

from users.constants import LOGIN_METHOD_SESSION_FIELD_NAME

GITHUB = "github"


Expand All @@ -26,3 +29,14 @@ def import_social_profile_data(sender, instance, created, **kwargs):

if avatar_url:
instance.user.save_image_from_github(avatar_url)


@receiver(user_logged_in)
def user_logged_in_handler(request, user, **kwargs):
# We trigger this here as well as on the profile update in case there are two users
# on one machine, we need to reflag for the cookie update
try:
method = request.session["account_authentication_methods"][0].get("provider")
except (KeyError, IndexError):
method = None
request.session[LOGIN_METHOD_SESSION_FIELD_NAME] = method or "email"
1 change: 1 addition & 0 deletions users/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def user(db):
email="[email protected]",
first_name="Regular",
last_name="User",
indicate_last_login_method=False,
last_login=timezone.now(),
image=None,
)
Expand Down
2 changes: 2 additions & 0 deletions users/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,13 @@ def test_user_profile_form(user):
"first_name",
"last_name",
"email",
"indicate_last_login_method",
}
assert form.initial == {
"first_name": user.first_name,
"last_name": user.last_name,
"email": user.email,
"indicate_last_login_method": user.indicate_last_login_method,
}
form = UserProfileForm(instance=user, data={"email": "[email protected]"})
assert form.is_valid()
Expand Down

0 comments on commit 8afad74

Please sign in to comment.