Skip to content

Commit

Permalink
Allow logging in by IP address
Browse files Browse the repository at this point in the history
There are many situations that this commit is untested against. Not
ready for merging yet.
  • Loading branch information
jalsol committed May 16, 2023
1 parent 63f0773 commit e18fb52
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
1 change: 1 addition & 0 deletions dmoj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@
'social_core.backends.facebook.FacebookOAuth2',
'judge.social_auth.GitHubSecureEmailOAuth2',
'django.contrib.auth.backends.ModelBackend',
'judge.ip_based_auth.IPBasedAuthBackend',
)

SOCIAL_AUTH_PIPELINE = (
Expand Down
40 changes: 37 additions & 3 deletions judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import webauthn
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -47,6 +48,15 @@
}


def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip


class ProfileForm(ModelForm):
if newsletter_id is not None:
newsletter = forms.BooleanField(label=_('Subscribe to contest updates'), initial=False, required=False)
Expand Down Expand Up @@ -428,13 +438,37 @@ def _has_social_auth(self, key):
getattr(settings, 'SOCIAL_AUTH_%s_SECRET' % key, None))

def clean(self):
username = self.cleaned_data.get('username')
is_ip_login = self.request.POST.get('ip-login', 'false') == 'true'

try:
user = User.objects.get(username=username)
except User.DoesNotExist:
if is_ip_login:
ip = get_client_ip(self.request)
user = Profile.objects.filter(ip=ip).select_related('user').first().user
else:
username = self.cleaned_data.get('username')
user = User.objects.get(username=username)
except (Profile.DoesNotExist, User.DoesNotExist):
user = None

if user is not None:
self.confirm_login_allowed(user)

if is_ip_login:
# a hack to remove the username/password errors, since we don't need them
del self.errors['username']
del self.errors['password']

# set the password to the user's password to pass the form_valid check
self.cleaned_data['password'] = user.password

# super.clean() will skip the check, since there is no username/password when logging in by IP address,
# so this is a hack to process that manually
self.user_cache = authenticate(self.request, ip_address=ip)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)

return super(CustomAuthenticationForm, self).clean()

def confirm_login_allowed(self, user):
Expand Down
13 changes: 13 additions & 0 deletions templates/registration/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
</style>
{% endblock %}

{% block js_media %}
<script>
$(document).ready(function () {
$("#ip-login-button").click(function () {
$("input[name='ip-login']").val("true");
});
});
</script>
{% endblock %}

{% block body %}
<div class="auth-flow-form">
<form action="" method="post" class="form-area">
Expand Down Expand Up @@ -57,7 +67,10 @@
</table>
<hr>
<button style="float:right;" type="submit">{{ _('Login!') }}</button>
<!-- TODO: enabled if allowing login by ip address -->
<button style="float:right; margin-right: 0.5em" type="submit" id="ip-login-button" formnovalidate>{{_('Login by IP Address!')}}</button>
<input type="hidden" name="next" value="{{ next }}">
<input type="hidden" name="ip-login" value="false">
</form>
<a href="{{ url('password_reset') }}">{{ _('Forgot your password?') }}</a>

Expand Down

0 comments on commit e18fb52

Please sign in to comment.