mirror of
https://github.com/sissbruecker/linkding.git
synced 2026-02-27 22:43:15 +08:00
Add option to disable login form (#1269)
This commit is contained in:
@@ -24,6 +24,8 @@ LD_AUTH_PROXY_USERNAME_HEADER=
|
||||
# The URL that linkding should redirect to after a logout, when using an auth proxy
|
||||
# See docs/Options.md for more details
|
||||
LD_AUTH_PROXY_LOGOUT_URL=
|
||||
# Disables the login form, useful to enforce OIDC authentication
|
||||
LD_DISABLE_LOGIN_FORM=False
|
||||
# List of trusted origins from which to accept POST requests
|
||||
# See docs/Options.md for more details
|
||||
LD_CSRF_TRUSTED_ORIGINS=
|
||||
|
||||
@@ -180,6 +180,13 @@ HUEY = {
|
||||
},
|
||||
}
|
||||
|
||||
# Disable login form if configured
|
||||
LD_DISABLE_LOGIN_FORM = os.getenv("LD_DISABLE_LOGIN_FORM", False) in (
|
||||
True,
|
||||
"True",
|
||||
"true",
|
||||
"1",
|
||||
)
|
||||
|
||||
# Enable OICD support if configured
|
||||
LD_ENABLE_OIDC = os.getenv("LD_ENABLE_OIDC", False) in (True, "True", "true", "1")
|
||||
|
||||
4
bookmarks/styles/auth.css
Normal file
4
bookmarks/styles/auth.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.auth-page {
|
||||
margin: 0 auto;
|
||||
max-width: 350px;
|
||||
}
|
||||
@@ -31,3 +31,4 @@
|
||||
@import "settings.css";
|
||||
@import "bundles.css";
|
||||
@import "tags.css";
|
||||
@import "auth.css";
|
||||
|
||||
@@ -4,33 +4,32 @@
|
||||
{% with page_title="Login - Linkding" %}{{ block.super }}{% endwith %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<main class="mx-auto width-50 width-md-100" aria-labelledby="main-heading">
|
||||
<main class="auth-page" aria-labelledby="main-heading">
|
||||
<div class="section-header">
|
||||
<h1 id="main-heading">Login</h1>
|
||||
</div>
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<p class="form-input-hint is-error">Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
{% formlabel form.username 'Username' %}
|
||||
{% formfield form.username class='form-input' %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{% formlabel form.password 'Password' %}
|
||||
{% formfield form.password class='form-input' %}
|
||||
</div>
|
||||
<br />
|
||||
<div class="d-flex justify-between">
|
||||
<input type="submit" value="Login" class="btn btn-primary btn-wide" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
{% if enable_oidc %}
|
||||
<a class="btn btn-link"
|
||||
href="{% url 'oidc_authentication_init' %}"
|
||||
data-turbo="false">Login with OIDC</a>
|
||||
{% if not disable_login %}
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<p class="form-input-hint is-error">Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="form-group">
|
||||
{% formlabel form.username 'Username' %}
|
||||
{% formfield form.username class='form-input' %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{% formlabel form.password 'Password' %}
|
||||
{% formfield form.password class='form-input' %}
|
||||
</div>
|
||||
<input type="submit" value="Login" class="btn btn-primary width-100 mt-4" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if enable_oidc %}
|
||||
<a class="btn width-100 mt-4"
|
||||
href="{% url 'oidc_authentication_init' %}"
|
||||
data-turbo="false">Login with OIDC</a>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% with page_title="Password changed - Linkding" %}{{ block.super }}{% endwith %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<main class="mx-auto width-50 width-md-100" aria-labelledby="main-heading">
|
||||
<main class="auth-page" aria-labelledby="main-heading">
|
||||
<div class="section-header">
|
||||
<h1 id="main-heading">Password Changed</h1>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% with page_title="Change password - Linkding" %}{{ block.super }}{% endwith %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<main class="mx-auto width-50 width-md-100" aria-labelledby="main-heading">
|
||||
<main class="auth-page" aria-labelledby="main-heading">
|
||||
<div class="section-header">
|
||||
<h1 id="main-heading">Change Password</h1>
|
||||
</div>
|
||||
@@ -25,10 +25,9 @@
|
||||
{% formfield form.new_password2 class='form-input' %}
|
||||
{{ form.new_password2.errors }}
|
||||
</div>
|
||||
<br />
|
||||
<input type="submit"
|
||||
value="Change Password"
|
||||
class="btn btn-primary btn-wide">
|
||||
class="btn btn-primary width-100 mt-4">
|
||||
</form>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
@@ -41,3 +41,43 @@ class LoginViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
|
||||
# should have turbo disabled
|
||||
self.assertEqual("false", oidc_login_link.get("data-turbo"))
|
||||
|
||||
def test_should_show_login_form_by_default(self):
|
||||
response = self.client.get("/login/")
|
||||
soup = self.make_soup(response.content.decode())
|
||||
|
||||
form = soup.find("form", {"action": "/login/"})
|
||||
username_input = soup.find("input", {"name": "username"})
|
||||
password_input = soup.find("input", {"name": "password"})
|
||||
submit_button = soup.find("input", {"type": "submit", "value": "Login"})
|
||||
|
||||
self.assertIsNotNone(form)
|
||||
self.assertIsNotNone(username_input)
|
||||
self.assertIsNotNone(password_input)
|
||||
self.assertIsNotNone(submit_button)
|
||||
|
||||
@override_settings(LD_DISABLE_LOGIN_FORM=True)
|
||||
def test_should_hide_login_form_when_disabled(self):
|
||||
response = self.client.get("/login/")
|
||||
soup = self.make_soup(response.content.decode())
|
||||
|
||||
form = soup.find("form", {"action": "/login/"})
|
||||
username_input = soup.find("input", {"name": "username"})
|
||||
password_input = soup.find("input", {"name": "password"})
|
||||
submit_button = soup.find("input", {"type": "submit", "value": "Login"})
|
||||
|
||||
self.assertIsNone(form)
|
||||
self.assertIsNone(username_input)
|
||||
self.assertIsNone(password_input)
|
||||
self.assertIsNone(submit_button)
|
||||
|
||||
@override_settings(LD_DISABLE_LOGIN_FORM=True, LD_ENABLE_OIDC=True)
|
||||
def test_should_only_show_oidc_login_when_login_disabled_and_oidc_enabled(self):
|
||||
response = self.client.get("/login/")
|
||||
soup = self.make_soup(response.content.decode())
|
||||
|
||||
form = soup.find("form", {"action": "/login/"})
|
||||
oidc_login_link = soup.find("a", string="Login with OIDC")
|
||||
|
||||
self.assertIsNone(form)
|
||||
self.assertIsNotNone(oidc_login_link)
|
||||
|
||||
@@ -19,6 +19,7 @@ class LinkdingLoginView(auth_views.LoginView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context["enable_oidc"] = settings.LD_ENABLE_OIDC
|
||||
context["disable_login"] = settings.LD_DISABLE_LOGIN_FORM
|
||||
return context
|
||||
|
||||
def form_invalid(self, form):
|
||||
|
||||
@@ -179,6 +179,14 @@ identity_providers:
|
||||
|
||||
</details>
|
||||
|
||||
### `LD_DISABLE_LOGIN_FORM`
|
||||
|
||||
Values: `True`, `False` | Default = `False`
|
||||
|
||||
Disables the login form on the login page.
|
||||
This is useful when you want to enforce authentication through OIDC only.
|
||||
When enabled, users will not be able to log in using their username and password, and only the "Login with OIDC" button will be shown on the login page.
|
||||
|
||||
### `LD_CSRF_TRUSTED_ORIGINS`
|
||||
|
||||
Values: `String` | Default = None
|
||||
|
||||
Reference in New Issue
Block a user