Compare commits
5 commits
production
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bb919c50ab | |||
| 63b89a059f | |||
| 0aa132b43f | |||
| dba4e12032 | |||
| b09897a39c |
10 changed files with 146 additions and 7 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
9
markdownblog/markdownblog/static/login.css
Normal file
9
markdownblog/markdownblog/static/login.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.round-corner {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.oauth-option {
|
||||
background-color: #35aca1;
|
||||
font-size: 1.1em;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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')
|
||||
]
|
||||
|
|
|
|||
61
markdownblog/markdownblog/views.py
Normal file
61
markdownblog/markdownblog/views.py
Normal 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'))
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue