Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
bb919c50ab documented enviroment variables 2022-12-18 12:10:04 +01:00
63b89a059f implemented nextcloud oauth login 2022-12-18 12:04:42 +01:00
0aa132b43f began working on oauth auth flow 2022-12-16 23:10:18 +01:00
dba4e12032 set up oauth and fixed login flow 2022-12-16 22:21:35 +01:00
b09897a39c small style fix 2022-12-16 21:04:38 +01:00
10 changed files with 146 additions and 7 deletions

View file

@ -15,9 +15,14 @@ In order for this project to work you must set up some enviroment variables:
- `DJANGO_SECRET_KEY` the secret key used by Django which should obviously be kept secret
- `DJANGO_DEBUG` indicates whether the Django server runs in Debug mode or not. Should be False iff the server runs in production
- `SALT_KEY` some type of hash salt the [2fa provider needs](https://github.com/neutron-sync/django-2fa/blob/main/docs/config.md#environmental-variables---required). Use a 36 character string.
- `ENABLE_NEXTCLOUD_OAUTH` `True` iff you wish to enable nextcloud oauth login
- `NC_OAUTH_CLIENT_ID` the OAuth client_id provided by nextcloud
- `NC_OAUTH_CLIENT_SECRET` the OAuth client secret provided by nextcloud
- `NC_BASE_URL` the base URL of the nextcloud instance (e.g. https://cloud.example.com/)
- `REQUESTS_CA_BUNDLE` an envvar read by the requests module. Enables you to pass a custom certificate bundle e.g. if your nextcloud instance uses a LetsEncrypt certificate
- `TIMEZONE` the server timezone. Needs to be accurate for OAuth to work. Example: `Europe/Berlin`.
### Docker
After saving the above mentioned enviroment variables in a `envvars.env` file (or adjusting the `docker-compose.yml` to use a different .env file) only a `docker compose up` should be required to bring the project up.
After saving the above-mentioned enviroment variables in a `envvars.env` file (or adjusting the `docker-compose.yml` to use a different .env file) only a `docker compose up` should be required to bring the project up.
### Migrate
To initialize the Database you need to run a Django migration. To do this you enter the docker container via `docker exec -it mdblog-web-1 /bin/bash` and running the command `python3 manage.py migrate`. After that the DB should be set up and the project is accessible at [127.0.0.1:8000](127.0.0.1:8000). If not you can try putting a https:// before the URL.

View file

@ -7,6 +7,8 @@ services:
- db
volumes:
- ./markdownblog:/markdownblog
# mount lets encrypt cert pem file
- ./cloud-cdaut-de-chain.pem:/le_cert.pem
ports:
- "8000:8000"
env_file:

View file

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from pathlib import Path
from authlib.integrations.django_client import OAuth
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -115,7 +116,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
TIME_ZONE = os.environ['TIMEZONE']
USE_I18N = True
@ -147,3 +148,19 @@ MFA_ISSUER_NAME = 'MDBlog'
# Configure mail here
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
AUTHLIB_OAUTH_CLIENTS = {}
if os.environ['ENABLE_NEXTCLOUD_OAUTH'] == "True":
AUTHLIB_OAUTH_CLIENTS['nextcloud'] = {
'client_id': os.environ['NC_OAUTH_CLIENT_ID'], # "Client Identifier" in Nextcloud
'client_secret': os.environ['NC_OAUTH_CLIENT_SECRET'], # "Secret" in Nextcloud
'access_token_url': os.environ['NC_BASE_URL'] + '/index.php/apps/oauth2/api/v1/token',
'request_token_params': None,
'access_token_params': None,
'refresh_token_url': None,
'authorize_url': os.environ['NC_BASE_URL'] + '/index.php/apps/oauth2/authorize',
'api_base_url': None,
'client_kwargs': None
}

View file

@ -6,4 +6,12 @@
#input_title {
font-size: 2.3em;
margin-top: 0.5em;
}
.dropdown-content li {
list-style-type: none !important;
}
.dropdown-content {
padding: 0 !important;
}

View file

@ -0,0 +1,9 @@
.round-corner {
border-radius: 5px;
}
.oauth-option {
background-color: #35aca1;
font-size: 1.1em;
color: #ffffff;
}

View file

@ -61,7 +61,8 @@
</li>
<li class="no-liststyle"><a class="waves-effect" href="{% url 'admin:index' %}"><i class="material-icons">
admin_panel_settings</i>Admin panel</a></li>
<li class="no-liststyle"><a class="waves-effect" href="{% url 'django_2fa:devices' %}"><i class="material-icons">
<li class="no-liststyle"><a class="waves-effect" href="{% url 'django_2fa:devices' %}"><i
class="material-icons">
vpn_key</i>Manage 2FA</a></li>
<li class="no-liststyle"><a class="waves-effect" href="{% url 'logout' %}"><i
class="material-icons">logout</i>Logout</a></li>
@ -100,6 +101,10 @@
document.addEventListener('DOMContentLoaded', function () {
const elems = document.querySelectorAll('.sidenav');
M.Sidenav.init(elems);
if ("{{ error }}" !== "") {
M.toast({html: '{{ error }}'});
}
});
</script>
{% block scripts %}{% endblock %}

View file

@ -1,14 +1,39 @@
{% extends 'base/base.html' %}
{% load static %}
{% block title %}
Log-in
{% endblock %}
{% block content %}
<div class="col s8 offset-s2">
<link rel="stylesheet" href="{% static 'login.css' %}">
<div class="col s8 offset-s3">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="waves-effect waves-light btn">Log In<i class="material-icons right">send</i>
<div class="row">
<div class="input-field col s6">
<input id="username" type="text" class="validate" name="username">
<label for="username">Username</label>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<input id="password" type="password" class="validate" name="password">
<label for="password">Password</label>
</div>
</div>
<button type="submit" class="waves-effect waves-light btn">Log In<i
class="material-icons right">send</i>
</button>
</form>
{% for auth_method in AUTHLIB_OAUTH_CLIENTS %}
<div class="row mt-3">
<a href="{% url 'oauth' provider=auth_method %}">
<div class="col s6 z-depth-3 round-corner oauth-option">
<p class="oauth-option">Log in via {{ auth_method }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
{% endblock %}

View file

@ -1,11 +1,15 @@
from django.contrib import admin
from django.urls import path, include
from markdownblog.views import login_view, oauth_view, oauth_authorize
import django_2fa.urls
urlpatterns = [
path('', include('blog.urls')),
path('admin/', admin.site.urls),
path('accounts/login/', login_view),
path('accounts/', include('django.contrib.auth.urls')),
path('accounts/2fa/', include(django_2fa.urls)),
path('accounts/oauth/<str:provider>/', oauth_view, name='oauth'),
path('accounts/oauth/<str:provider>/callback',oauth_authorize, name='oauth_authorize')
]

View file

@ -0,0 +1,61 @@
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from authlib.integrations.django_client import OAuth
from django.urls import reverse
oauth = OAuth()
def login_view(request):
context = {'AUTHLIB_OAUTH_CLIENTS': settings.AUTHLIB_OAUTH_CLIENTS, 'form': LoginView.authentication_form}
# handle user login if standard login is used
if request.method == 'POST':
if 'username' not in request.POST or 'password' not in request.POST:
context['error'] = 'Please enter a username and a password'
else:
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect(settings.LOGIN_REDIRECT_URL)
else:
context['error'] = "Invalid credentials"
return render(request, 'registration/login.html', context)
def oauth_view(request, provider):
context = {'AUTHLIB_OAUTH_CLIENTS': settings.AUTHLIB_OAUTH_CLIENTS, 'form': LoginView.authentication_form}
# check if provider is configured and supported
if provider == 'nextcloud' and 'nextcloud' in settings.AUTHLIB_OAUTH_CLIENTS:
oauth.register("nextcloud")
redirect_uri = request.build_absolute_uri(reverse('oauth_authorize', args={'provider': 'nextcloud'}))
return oauth.nextcloud.authorize_redirect(request, redirect_uri)
else:
context['error'] = f'Unknown oauth provider \"{provider}\"'
return render(request, 'registration/login.html', context)
def oauth_authorize(request, provider):
# handle oauth callback
if provider == 'nextcloud' and 'nextcloud' in settings.AUTHLIB_OAUTH_CLIENTS:
token = oauth.nextcloud.authorize_access_token(request)
# extract username
oauth_username = token['user_id']
# create user if necessary
newuser, _ = User.objects.get_or_create(username=oauth_username)
login(request, newuser)
else:
# return an error if provider is not configured
context = {'AUTHLIB_OAUTH_CLIENTS': settings.AUTHLIB_OAUTH_CLIENTS, 'form': LoginView.authentication_form,
'error': f'Unknown oauth provider \"{provider}\"'}
return render(request, 'registration/login.html', context)
return redirect(reverse('index'))

View file

@ -7,4 +7,7 @@ pygments==2.12.0
django-2fa==0.9.0
django-extensions==3.1.5
Werkzeug==2.1.2
pyOpenSSL==22.0.0
pyOpenSSL==22.0.0
authlib==1.2.0
requests==2.28.1
certifi==2022.12.7