add app user management allauth
This commit is contained in:
1
.env
1
.env
@@ -3,6 +3,7 @@ DB_USER=postgres
|
|||||||
DB_PASSWORD=BatManu#171017
|
DB_PASSWORD=BatManu#171017
|
||||||
DB_HOST=10.211.114.101
|
DB_HOST=10.211.114.101
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
MASTER_PASSWORD=pipperepettenuse
|
||||||
MQTT_HOST=10.211.114.214
|
MQTT_HOST=10.211.114.214
|
||||||
MQTT_PORT=1883
|
MQTT_PORT=1883
|
||||||
MQTT_KEEPALIVE=60
|
MQTT_KEEPALIVE=60
|
||||||
|
|||||||
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@@ -12,7 +12,6 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"args": [
|
"args": [
|
||||||
"runserver",
|
"runserver",
|
||||||
"--noreload",
|
|
||||||
],
|
],
|
||||||
"django": true,
|
"django": true,
|
||||||
"autoStartBrowser": false,
|
"autoStartBrowser": false,
|
||||||
|
|||||||
0
aseusers/__init__.py
Normal file
0
aseusers/__init__.py
Normal file
4
aseusers/admin.py
Normal file
4
aseusers/admin.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
|
admin.site.register(Profile)
|
||||||
9
aseusers/apps.py
Normal file
9
aseusers/apps.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AseusersConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'aseusers'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import aseusers.signals
|
||||||
22
aseusers/forms.py
Normal file
22
aseusers/forms.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from django.forms import ModelForm
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
|
class ProfileForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Profile
|
||||||
|
fields = ['image', 'displayname', 'info' ]
|
||||||
|
widgets = {
|
||||||
|
'image': forms.FileInput(),
|
||||||
|
'displayname' : forms.TextInput(attrs={'placeholder': 'Add display name'}),
|
||||||
|
'info' : forms.Textarea(attrs={'rows':3, 'placeholder': 'Add information'})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EmailForm(ModelForm):
|
||||||
|
email = forms.EmailField(required=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['email']
|
||||||
27
aseusers/migrations/0001_initial.py
Normal file
27
aseusers/migrations/0001_initial.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-01-31 20:49
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Profile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='avatars/')),
|
||||||
|
('displayname', models.CharField(blank=True, max_length=20, null=True)),
|
||||||
|
('info', models.TextField(blank=True, null=True)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
aseusers/migrations/__init__.py
Normal file
0
aseusers/migrations/__init__.py
Normal file
24
aseusers/models.py
Normal file
24
aseusers/models.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class Profile(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
image = models.ImageField(upload_to='avatars/', null=True, blank=True)
|
||||||
|
displayname = models.CharField(max_length=20, null=True, blank=True)
|
||||||
|
info = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.user)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
if self.displayname:
|
||||||
|
return self.displayname
|
||||||
|
return self.user.username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def avatar(self):
|
||||||
|
if self.image:
|
||||||
|
return self.image.url
|
||||||
|
return f'{settings.STATIC_URL}images/avatar.svg'
|
||||||
37
aseusers/signals.py
Normal file
37
aseusers/signals.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.dispatch import receiver
|
||||||
|
from django.db.models.signals import post_save, pre_save
|
||||||
|
from allauth.account.models import EmailAddress
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
def user_postsave(sender, instance, created, **kwargs):
|
||||||
|
user = instance
|
||||||
|
|
||||||
|
# add profile if user is created
|
||||||
|
if created:
|
||||||
|
Profile.objects.create(
|
||||||
|
user = user,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# update allauth emailaddress if exists
|
||||||
|
try:
|
||||||
|
email_address = EmailAddress.objects.get_primary(user)
|
||||||
|
if email_address.email != user.email:
|
||||||
|
email_address.email = user.email
|
||||||
|
email_address.verified = False
|
||||||
|
email_address.save()
|
||||||
|
except:
|
||||||
|
# if allauth emailaddress doesn't exist create one
|
||||||
|
EmailAddress.objects.create(
|
||||||
|
user = user,
|
||||||
|
email = user.email,
|
||||||
|
primary = True,
|
||||||
|
verified = False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=User)
|
||||||
|
def user_presave(sender, instance, **kwargs):
|
||||||
|
if instance.username:
|
||||||
|
instance.username = instance.username.lower()
|
||||||
21
aseusers/templates/aseusers/profile.html
Normal file
21
aseusers/templates/aseusers/profile.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="container d-flex flex-column align-items-center pt-5 px-4"
|
||||||
|
style="max-width: 32rem"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="w-36 h-36 rounded-circle object-fit-cover mb-4"
|
||||||
|
src="{{ profile.avatar }}"
|
||||||
|
height="144"
|
||||||
|
/>
|
||||||
|
<div class="text-center">
|
||||||
|
<h1>{{ profile.name }}</h1>
|
||||||
|
<div class="text-muted mb-2 mt-n3">@{{ profile.user.username }}</div>
|
||||||
|
{% if profile.info %}
|
||||||
|
<div class="mt-4 hyphens-auto">{{ profile.info|linebreaksbr }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
14
aseusers/templates/aseusers/profile_delete.html
Normal file
14
aseusers/templates/aseusers/profile_delete.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'layouts/box.html' %} {% block content %}
|
||||||
|
|
||||||
|
<h1 class="mb-4">Delete Account</h1>
|
||||||
|
<p class="mb-4">Are you sure you want to delete your account?</p>
|
||||||
|
|
||||||
|
<form method="POST" class="d-flex gap-2">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
Yes, I want to delete my account
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-secondary" href="{{ request.META.HTTP_REFERER }}">Cancel</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
55
aseusers/templates/aseusers/profile_edit.html
Normal file
55
aseusers/templates/aseusers/profile_edit.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{% extends 'layouts/box.html' %} {% block content %} {% if onboarding %}
|
||||||
|
<h1 class="mb-4">Complete your Profile</h1>
|
||||||
|
{% else %}
|
||||||
|
<h1 class="mb-4">Edit your Profile</h1>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="text-center d-flex flex-column align-items-center">
|
||||||
|
<img
|
||||||
|
id="avatar"
|
||||||
|
class="w-36 h-36 rounded-circle object-fit-cover my-4"
|
||||||
|
src="{{ user.profile.avatar }} "
|
||||||
|
height="144"
|
||||||
|
/>
|
||||||
|
<div class="text-center" style="max-width: 24rem">
|
||||||
|
<h1 id="displayname">{{ user.profile.displayname|default:"" }}</h1>
|
||||||
|
<div class="text-muted mb-2 mt-n3">@{{ user.username }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %} {{ form.as_p }}
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
{% if onboarding %}
|
||||||
|
<a class="btn btn-secondary ms-2" href="{% url 'home' %}">Skip</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-secondary ms-2" href="{{ request.META.HTTP_REFERER }}"
|
||||||
|
>Cancel</a
|
||||||
|
>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// This updates the avatar
|
||||||
|
const fileInput = document.querySelector('input[type="file"]');
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
const image = document.querySelector("#avatar");
|
||||||
|
|
||||||
|
if (file && file.type.includes("image")) {
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
image.src = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This updates the name
|
||||||
|
const display_nameInput = document.getElementById("id_displayname");
|
||||||
|
const display_nameOutput = document.getElementById("displayname");
|
||||||
|
|
||||||
|
display_nameInput.addEventListener("input", (event) => {
|
||||||
|
display_nameOutput.innerText = event.target.value;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
58
aseusers/templates/aseusers/profile_settings.html
Normal file
58
aseusers/templates/aseusers/profile_settings.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'layouts/box.html' %} {% block content %}
|
||||||
|
|
||||||
|
<h1 class="mb-4">Account Settings</h1>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" class="pt-3 pb-1 fw-bold">Email address</th>
|
||||||
|
<td id="email-address" class="pt-3 pb-1 ps-3">
|
||||||
|
{% if user.email %}{{ user.email }}{% else %}No Email{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="pt-3 pb-1 ps-3">
|
||||||
|
<a
|
||||||
|
id="email-edit"
|
||||||
|
class="text-decoration-none text-primary cursor-pointer"
|
||||||
|
hx-get="{% url 'profile-emailchange' %}"
|
||||||
|
hx-target="#email-address"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-bottom">
|
||||||
|
<th scope="row" class="pb-3 fw-bold"></th>
|
||||||
|
<td class="pb-3 ps-3">
|
||||||
|
{% if user.emailaddress_set.first.verified %}
|
||||||
|
<span class="text-success">Verified</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-warning">Not verified</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="pb-3 ps-3">
|
||||||
|
<a
|
||||||
|
href="{% url 'profile-emailverify' %}"
|
||||||
|
class="text-decoration-none text-primary"
|
||||||
|
>
|
||||||
|
{% if not user.emailaddress_set.first.verified %}Verify{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr class="border-bottom">
|
||||||
|
<th scope="row" class="py-3 fw-bold">Delete Account</th>
|
||||||
|
<td class="py-3 ps-3">Once deleted, account is gone. Forever.</td>
|
||||||
|
<td class="py-3 ps-3">
|
||||||
|
<a
|
||||||
|
href="{% url 'profile-delete' %}"
|
||||||
|
class="text-decoration-none text-danger"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
3
aseusers/tests.py
Normal file
3
aseusers/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
12
aseusers/urls.py
Normal file
12
aseusers/urls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from aseusers.views import *
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', profile_view, name="profile"),
|
||||||
|
path('edit/', profile_edit_view, name="profile-edit"),
|
||||||
|
path('onboarding/', profile_edit_view, name="profile-onboarding"),
|
||||||
|
path('settings/', profile_settings_view, name="profile-settings"),
|
||||||
|
path('emailchange/', profile_emailchange, name="profile-emailchange"),
|
||||||
|
path('emailverify/', profile_emailverify, name="profile-emailverify"),
|
||||||
|
path('delete/', profile_delete_view, name="profile-delete"),
|
||||||
|
]
|
||||||
93
aseusers/views.py
Normal file
93
aseusers/views.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from allauth.account.utils import send_email_confirmation
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth import logout
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
|
from django.contrib import messages
|
||||||
|
from .forms import *
|
||||||
|
|
||||||
|
def profile_view(request, username=None):
|
||||||
|
if username:
|
||||||
|
profile = get_object_or_404(User, username=username).profile
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
profile = request.user.profile
|
||||||
|
except:
|
||||||
|
return redirect_to_login(request.get_full_path())
|
||||||
|
return render(request, 'aseusers/profile.html', {'profile':profile})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_edit_view(request):
|
||||||
|
form = ProfileForm(instance=request.user.profile)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ProfileForm(request.POST, request.FILES, instance=request.user.profile)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect('profile')
|
||||||
|
|
||||||
|
if request.path == reverse('profile-onboarding'):
|
||||||
|
onboarding = True
|
||||||
|
else:
|
||||||
|
onboarding = False
|
||||||
|
|
||||||
|
return render(request, 'aseusers/profile_edit.html', { 'form':form, 'onboarding':onboarding })
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_settings_view(request):
|
||||||
|
return render(request, 'aseusers/profile_settings.html')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_emailchange(request):
|
||||||
|
|
||||||
|
if request.htmx:
|
||||||
|
form = EmailForm(instance=request.user)
|
||||||
|
return render(request, 'partials/email_form.html', {'form':form})
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = EmailForm(request.POST, instance=request.user)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
# Check if the email already exists
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
if User.objects.filter(email=email).exclude(id=request.user.id).exists():
|
||||||
|
messages.warning(request, f'{email} is already in use.')
|
||||||
|
return redirect('profile-settings')
|
||||||
|
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
# Then Signal updates emailaddress and set verified to False
|
||||||
|
|
||||||
|
# Then send confirmation email
|
||||||
|
send_email_confirmation(request, request.user)
|
||||||
|
|
||||||
|
return redirect('profile-settings')
|
||||||
|
else:
|
||||||
|
messages.warning(request, 'Form not valid')
|
||||||
|
return redirect('profile-settings')
|
||||||
|
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_emailverify(request):
|
||||||
|
send_email_confirmation(request, request.user)
|
||||||
|
return redirect('profile-settings')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def profile_delete_view(request):
|
||||||
|
user = request.user
|
||||||
|
if request.method == "POST":
|
||||||
|
logout(request)
|
||||||
|
user.delete()
|
||||||
|
messages.success(request, 'Account deleted, what a pity')
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
return render(request, 'aseusers/profile_delete.html')
|
||||||
BIN
avatars/ape2.jpg
Normal file
BIN
avatars/ape2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -8,9 +8,12 @@ dependencies = [
|
|||||||
"bcrypt>=4.2.1",
|
"bcrypt>=4.2.1",
|
||||||
"cryptography>=44.0.0",
|
"cryptography>=44.0.0",
|
||||||
"django>=5.1.4",
|
"django>=5.1.4",
|
||||||
|
"django-allauth>=65.3.1",
|
||||||
|
"django-cleanup>=9.0.0",
|
||||||
"django-htmx>=1.21.0",
|
"django-htmx>=1.21.0",
|
||||||
"gunicorn>=23.0.0",
|
"gunicorn>=23.0.0",
|
||||||
"paho-mqtt>=2.1.0",
|
"paho-mqtt>=2.1.0",
|
||||||
|
"pillow>=11.1.0",
|
||||||
"psycopg2-binary>=2.9.10",
|
"psycopg2-binary>=2.9.10",
|
||||||
"python-dotenv>=1.0.1",
|
"python-dotenv>=1.0.1",
|
||||||
]
|
]
|
||||||
|
|||||||
BIN
static/images/ase-logo.png
Normal file
BIN
static/images/ase-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
65
static/js/htmx.min.js
vendored
65
static/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
10
templates/404.html
Normal file
10
templates/404.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<div class="container text-center py-5">
|
||||||
|
<h1 class="display-1">Page not found</h1>
|
||||||
|
<p class="my-4 text-muted">
|
||||||
|
Error 404: This page doesn't exist or is unavailable
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
44
templates/account/login.html
Normal file
44
templates/account/login.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends "account/base_entrance.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load allauth account %}
|
||||||
|
{% block head_title %}
|
||||||
|
{% trans "Sign In" %}
|
||||||
|
{% endblock head_title%}
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mb-4">{% trans "Sign In" %}</h1>
|
||||||
|
<p class="mb-4">
|
||||||
|
If you have not created an account yet, then please
|
||||||
|
<a
|
||||||
|
href="{% url 'account_signup' %}?next={% url 'profile-onboarding' %}"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>sign up</a
|
||||||
|
>
|
||||||
|
first.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% url 'account_login' as login_url %}
|
||||||
|
<form method="post" action="{{ login_url }}" class="mb-4">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ field }} {% if field.errors %}
|
||||||
|
<div class="text-danger">{{ field.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if redirect_field_value %}
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="{{ redirect_field_name }}"
|
||||||
|
value="{{ redirect_field_value }}"
|
||||||
|
/>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans "Sign In" %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if SOCIALACCOUNT_ENABLED %}
|
||||||
|
{% include "socialaccount/snippets/login.html" with page_layout="entrance" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
||||||
0
templates/account/messages/logged_in.txt
Normal file
0
templates/account/messages/logged_in.txt
Normal file
0
templates/account/messages/logged_out.txt
Normal file
0
templates/account/messages/logged_out.txt
Normal file
12
templates/allauth/layouts/base.html
Normal file
12
templates/allauth/layouts/base.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'layouts/box.html' %} {% block class %}allauth{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-8 col-lg-6">
|
||||||
|
<div class="bg-white rounded-3 p-4 p-md-5 shadow-lg">
|
||||||
|
{% block allauth_content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
35
templates/base.html
Normal file
35
templates/base.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load django_htmx %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="Django Template">
|
||||||
|
<title>Project Title</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" defer></script>
|
||||||
|
|
||||||
|
<!-- HTMX -->
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.4" defer></script>
|
||||||
|
{% django_htmx_script %}
|
||||||
|
|
||||||
|
<!-- Custom Styles -->
|
||||||
|
<style>
|
||||||
|
.dropdown-menu { min-width: 200px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||||
|
{% include 'includes/messages.html' %}
|
||||||
|
|
||||||
|
{% include 'includes/header.html' %}
|
||||||
|
|
||||||
|
{% block layout %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
templates/home.html
Normal file
7
templates/home.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<div class="container py-5">
|
||||||
|
<h1>New Project</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
38
templates/includes/header.html
Normal file
38
templates/includes/header.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% load static %}
|
||||||
|
<header class="d-flex align-items-center justify-content-between bg-dark text-white py-3 px-4 sticky-top z-40">
|
||||||
|
<div>
|
||||||
|
<a class="d-flex align-items-center gap-2 text-decoration-none text-white" href="/">
|
||||||
|
<img class="h-6" src="{% static 'images/ase-logo.png' %}" alt="ASE" height="40"/>
|
||||||
|
<span class="text-lg fw-bold">MQTT User Management</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<nav class="position-relative">
|
||||||
|
<ul class="nav nav-pills align-items-center">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-white" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2 text-white" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<img class="h-8 w-8 rounded-circle object-cover" src="{{ request.user.profile.avatar }}" alt="Avatar" height="40"/>
|
||||||
|
{{ request.user.profile.name }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end shadow-lg" style="min-width: 200px;">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'profile' %}">My Profile</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'profile-edit' %}">Edit Profile</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'profile-settings' %}">Settings</a></li>
|
||||||
|
<li><hr class="dropdown-divider" /></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'account_logout' %}">Log Out</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-white" href="{% url 'account_login' %}">Login</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-white" href="{% url 'account_signup' %}?next={% url 'profile-onboarding' %}">Signup</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
29
templates/includes/messages.html
Normal file
29
templates/includes/messages.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<messages>
|
||||||
|
{% if messages %}
|
||||||
|
<div>
|
||||||
|
{% for message in messages %}
|
||||||
|
<div
|
||||||
|
class="position-fixed top-0 start-50 translate-middle-x mt-3 z-50"
|
||||||
|
style="width: 100%; max-width: 576px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="alert alert-{{ message.tags }} alert-dismissible fade show"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="me-3">
|
||||||
|
<div class="text-lg">{{ message }}</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="alert"
|
||||||
|
aria-label="Close"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</messages>
|
||||||
12
templates/layouts/blank.html
Normal file
12
templates/layouts/blank.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block layout %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-10 col-lg-8">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
11
templates/layouts/box.html
Normal file
11
templates/layouts/box.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %} {% block layout %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-8 col-lg-6">
|
||||||
|
<div class="bg-white rounded-3 p-4 p-md-5 shadow-lg">
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
8
templates/partials/disable_user.html
Normal file
8
templates/partials/disable_user.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<button
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
hx-post="/user/disable/{{user.slug}}"
|
||||||
|
hx-target="#user-status"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
>
|
||||||
|
Disable
|
||||||
|
</button>
|
||||||
18
templates/partials/email_form.html
Normal file
18
templates/partials/email_form.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<form
|
||||||
|
action="{% url 'profile-emailchange' %}"
|
||||||
|
method="post"
|
||||||
|
class="d-flex gap-2"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
{% csrf_token %} {% for field in form %} {{ field }} {% endfor %}
|
||||||
|
<button class="btn btn-dark" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a
|
||||||
|
hx-swap-oob="true"
|
||||||
|
id="email-edit"
|
||||||
|
href="{% url 'profile-settings' %}"
|
||||||
|
class="text-primary text-decoration-none"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
8
templates/partials/enable_user.html
Normal file
8
templates/partials/enable_user.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<button
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
hx-post="/user/enable/{{user.slug}}"
|
||||||
|
hx-target="#user-status"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
>
|
||||||
|
Enable
|
||||||
|
</button>
|
||||||
@@ -1,6 +1,21 @@
|
|||||||
{% extends "base.html" %} {% load static %} {% block content %}
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<!-- Mostra i messaggi di errore o successo -->
|
||||||
|
{% if messages %}
|
||||||
|
<div>
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Form per la creazione dell'utente -->
|
||||||
|
|
||||||
<h1>Add User</h1>
|
<h1>Add User</h1>
|
||||||
<form hx-post="/create_user" hx-target="#content">
|
<form method="post" hx-post="/create_user" hx-target="#content">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="site" class="form-label">Site</label>
|
<label for="site" class="form-label">Site</label>
|
||||||
<input type="text" class="form-control" id="site" name="site" required />
|
<input type="text" class="form-control" id="site" name="site" required />
|
||||||
@@ -29,10 +44,6 @@
|
|||||||
<label for="topic" class="form-label">Topic</label>
|
<label for="topic" class="form-label">Topic</label>
|
||||||
<input type="text" class="form-control" id="topic" name="topic" required />
|
<input type="text" class="form-control" id="topic" name="topic" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="role" class="form-label">Role</label>
|
|
||||||
<input type="text" class="form-control" id="role" name="role" required />
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="password" class="form-label">Password</label>
|
<label for="password" class="form-label">Password</label>
|
||||||
<input
|
<input
|
||||||
60
templates/wallet_api/create_user.html
Normal file
60
templates/wallet_api/create_user.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<!-- Mostra i messaggi di errore o successo -->
|
||||||
|
{% if messages %}
|
||||||
|
<div>
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h1>Add User</h1>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="site" class="form-label">Site</label>
|
||||||
|
<input type="text" class="form-control" id="site" name="site" required />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="clientId" class="form-label">Client ID</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="clientId"
|
||||||
|
name="clientId"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="topic" class="form-label">Topic</label>
|
||||||
|
<input type="text" class="form-control" id="topic" name="topic" required />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Create</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Risultato della creazione -->
|
||||||
|
<div id="result"></div>
|
||||||
|
{% endblock content%}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{% extends "base.html" %} {% load static %} {% block content %}
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
<h1>Edit User</h1>
|
<h1>Edit User</h1>
|
||||||
<form hx-post="/update_user" hx-target="#content">
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="id" value="{{ user.id }}" />
|
<input type="hidden" name="id" value="{{ user.id }}" />
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="site" class="form-label">Site</label>
|
<label for="site" class="form-label">Site</label>
|
||||||
@@ -46,17 +47,6 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="role" class="form-label">Role</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="role"
|
|
||||||
name="role"
|
|
||||||
value="{{ user.role }}"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Update</button>
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
57
templates/wallet_api/list_users.html
Normal file
57
templates/wallet_api/list_users.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
|
|
||||||
|
<!-- Mostra i messaggi di errore o successo -->
|
||||||
|
{% if messages %}
|
||||||
|
<div>
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h1>List Mosquitto Users</h1>
|
||||||
|
<a href="{% url 'create_user' %}">
|
||||||
|
<button class="btn btn-primary mb-3">Add User</button>
|
||||||
|
</a>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Site</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Client ID</th>
|
||||||
|
<th>Topic</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>{{user.site}}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'edit_user' user.slug %}">{{user.username}}</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>{{user.client_id}}</td>
|
||||||
|
<td>{{user.topic}}</td>
|
||||||
|
<td id="user-status">
|
||||||
|
{% if user.status == "enabled" %}
|
||||||
|
<a href="{% url 'disable_user' user.slug %}">
|
||||||
|
<button class="btn btn-danger btn-sm">Disable</button>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'enable_user' user.slug %}">
|
||||||
|
<button class="btn btn-warning btn-sm">Enable</button>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5"><i> No users </i></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock content %}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends "base.html" %} {% load static %} {% block content %}
|
{% extends 'layouts/blank.html' %} {% block content %}
|
||||||
<h1>Role Permissions</h1>
|
<h1>Role Permissions</h1>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
63
uv.lock
generated
63
uv.lock
generated
@@ -118,6 +118,25 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/58/0b/8a4ab2c02982df4ed41e29f28f189459a7eba37899438e6bea7f39db793b/Django-5.1.4-py3-none-any.whl", hash = "sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0", size = 8276471 },
|
{ url = "https://files.pythonhosted.org/packages/58/0b/8a4ab2c02982df4ed41e29f28f189459a7eba37899438e6bea7f39db793b/Django-5.1.4-py3-none-any.whl", hash = "sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0", size = 8276471 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-allauth"
|
||||||
|
version = "65.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "asgiref" },
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/20/73/3a24b32df7046c5106846c0cfb3309033e2ebe00b08dfbf64c883add253a/django_allauth-65.3.1.tar.gz", hash = "sha256:e02e951b71a2753a746459f2efa114c7c72bf2cef6887dbe8607a577c0350587", size = 1547530 }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-cleanup"
|
||||||
|
version = "9.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4b/01/b15a8de8b9ec75ea157ec58f86411894ca1182305fabaee31193076e7f62/django_cleanup-9.0.0.tar.gz", hash = "sha256:bb9fb560aaf62959c81e31fa40885c36bbd5854d5aa21b90df2c7e4ba633531e", size = 17917 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/d7/a83dc87c2383e125da29948f7bccf5b30126c087a5a831316482407a960f/django_cleanup-9.0.0-py3-none-any.whl", hash = "sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c", size = 10726 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-htmx"
|
name = "django-htmx"
|
||||||
version = "1.21.0"
|
version = "1.21.0"
|
||||||
@@ -139,9 +158,12 @@ dependencies = [
|
|||||||
{ name = "bcrypt" },
|
{ name = "bcrypt" },
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
|
{ name = "django-allauth" },
|
||||||
|
{ name = "django-cleanup" },
|
||||||
{ name = "django-htmx" },
|
{ name = "django-htmx" },
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "paho-mqtt" },
|
{ name = "paho-mqtt" },
|
||||||
|
{ name = "pillow" },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
]
|
]
|
||||||
@@ -151,9 +173,12 @@ requires-dist = [
|
|||||||
{ name = "bcrypt", specifier = ">=4.2.1" },
|
{ name = "bcrypt", specifier = ">=4.2.1" },
|
||||||
{ name = "cryptography", specifier = ">=44.0.0" },
|
{ name = "cryptography", specifier = ">=44.0.0" },
|
||||||
{ name = "django", specifier = ">=5.1.4" },
|
{ name = "django", specifier = ">=5.1.4" },
|
||||||
|
{ name = "django-allauth", specifier = ">=65.3.1" },
|
||||||
|
{ name = "django-cleanup", specifier = ">=9.0.0" },
|
||||||
{ name = "django-htmx", specifier = ">=1.21.0" },
|
{ name = "django-htmx", specifier = ">=1.21.0" },
|
||||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||||
{ name = "paho-mqtt", specifier = ">=2.1.0" },
|
{ name = "paho-mqtt", specifier = ">=2.1.0" },
|
||||||
|
{ name = "pillow", specifier = ">=11.1.0" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
||||||
]
|
]
|
||||||
@@ -188,6 +213,44 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219 },
|
{ url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "11.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg2-binary"
|
name = "psycopg2-binary"
|
||||||
version = "2.9.10"
|
version = "2.9.10"
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ SECRET_KEY = 'django-insecure-=-1c582(r8ly33q+-ljg!xb2)#$+a0ykedc4zcj-dqh&i$4$l3
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '*']
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [ 'https://*' ]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@@ -38,8 +40,15 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django_cleanup.apps.CleanupConfig',
|
||||||
|
# 'django.contrib.sites',
|
||||||
|
'allauth',
|
||||||
|
'allauth.account',
|
||||||
'django_htmx',
|
'django_htmx',
|
||||||
|
|
||||||
|
# My apps
|
||||||
'wallet_api.apps.WalletApiConfig',
|
'wallet_api.apps.WalletApiConfig',
|
||||||
|
'aseusers.apps.AseusersConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -50,15 +59,22 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'allauth.account.middleware.AccountMiddleware',
|
||||||
"django_htmx.middleware.HtmxMiddleware",
|
"django_htmx.middleware.HtmxMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'wallet.urls'
|
ROOT_URLCONF = 'wallet.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [ BASE_DIR / "templates" ],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
@@ -112,9 +128,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'it-it'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'CET'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
@@ -125,9 +141,19 @@ USE_TZ = True
|
|||||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
STATICFILES_DIRS = [BASE_DIR / "static"]
|
STATICFILES_DIRS = [ BASE_DIR / "static" ]
|
||||||
|
|
||||||
|
# MEDIA_URL = 'media/'
|
||||||
|
# MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
ACCOUNT_SIGNUP_REDIRECT_URL = "{% url 'account_signup' %}?next={% url 'profile-onboarding' %}"
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for wallet project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 5.1.4.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.1/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/5.1/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
# Load env variable from .env
|
|
||||||
config = dotenv_values(".env")
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'y@-63z+ao=w=5%%e&p)a7e=qscvqokg$ai5gyejuh5$hs*p4ij'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'wallet_api.apps.WalletApiConfig',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'wallet.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'wallet.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
|
||||||
'NAME': config['DB_NAME'],
|
|
||||||
'USER': config['DB_USER'],
|
|
||||||
'PASSWORD': config['DB_PASSWORD'],
|
|
||||||
'HOST': config['DB_HOST'],
|
|
||||||
'PORT': config['DB_PORT'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
@@ -16,8 +16,19 @@ Including another URLconf
|
|||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.conf import settings
|
||||||
|
from aseusers.views import profile_view
|
||||||
|
from wallet_api.views import *
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('wallet/', include('wallet_api.urls')),
|
path('accounts/', include('allauth.urls')),
|
||||||
|
path('', include('wallet_api.urls')),
|
||||||
|
path('profile/', include('aseusers.urls')),
|
||||||
|
path('@<username>/', profile_view, name="profile"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Only used when DEBUG=True, whitenoise can serve files when DEBUG=False
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
from queue import Queue
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
print(f"__init__.py eseguito in PID {os.getpid()}")
|
|
||||||
|
|
||||||
config = dotenv_values(".env")
|
|
||||||
|
|
||||||
listClients_queue = Queue()
|
|
||||||
listRoles_queue = Queue()
|
|
||||||
getClient_queue = Queue()
|
|
||||||
changeClient_queue = Queue()
|
|
||||||
|
|
||||||
command_queue_map = {
|
|
||||||
"listClients": listClients_queue,
|
|
||||||
"listRoles": listRoles_queue,
|
|
||||||
"getClient": getClient_queue,
|
|
||||||
"createClient": changeClient_queue,
|
|
||||||
"deleteClient": changeClient_queue,
|
|
||||||
"enableClient": changeClient_queue,
|
|
||||||
"disableClient": changeClient_queue
|
|
||||||
}
|
|
||||||
|
|
||||||
def on_message(client, userdata, msg):
|
|
||||||
msg_json = json.loads(msg.payload.decode("utf-8"))
|
|
||||||
command = msg_json['responses'][0]['command']
|
|
||||||
print(f"Received message: {msg.payload.decode("utf-8")} - Command: {command}")
|
|
||||||
|
|
||||||
if command in command_queue_map:
|
|
||||||
command_queue_map[command].put(msg.payload.decode("utf-8"))
|
|
||||||
|
|
||||||
def on_connect(client, userdata, flags, rc, properties):
|
|
||||||
if rc == 0:
|
|
||||||
print('Connected successfully. Properties:', properties)
|
|
||||||
client.subscribe(config['MQTT_DS_RESP_TOPIC'])
|
|
||||||
else:
|
|
||||||
print('Bad connection. Code:', rc)
|
|
||||||
|
|
||||||
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
|
|
||||||
client.on_connect = on_connect
|
|
||||||
client.on_message = on_message
|
|
||||||
client.username_pw_set(config['MQTT_USER'], config['MQTT_PASSWORD'])
|
|
||||||
client.connect(config['MQTT_HOST'], int(config['MQTT_PORT']), int(config['MQTT_KEEPALIVE']))
|
|
||||||
client.loop_start()
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-01-15 18:24
|
# Generated by Django 5.1.4 on 2025-01-31 22:09
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -14,9 +14,8 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='MasterHash',
|
name='MasterHash',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.CharField(default='1', max_length=10, primary_key=True, serialize=False)),
|
||||||
('hash', models.BinaryField()),
|
('hash', models.BinaryField()),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -26,12 +25,10 @@ class Migration(migrations.Migration):
|
|||||||
('site', models.CharField(max_length=255)),
|
('site', models.CharField(max_length=255)),
|
||||||
('username', models.CharField(max_length=255)),
|
('username', models.CharField(max_length=255)),
|
||||||
('password', models.TextField()),
|
('password', models.TextField()),
|
||||||
('client_id', models.CharField(max_length=255)),
|
('client_id', models.CharField(blank=True, max_length=255)),
|
||||||
('topic', models.CharField(max_length=255)),
|
('topic', models.CharField(blank=True, max_length=255)),
|
||||||
('role', models.CharField(max_length=255)),
|
('status', models.CharField(blank=True, max_length=255)),
|
||||||
('acls', models.JSONField()),
|
('slug', models.SlugField(blank=True, unique=True)),
|
||||||
('status', models.CharField(max_length=255)),
|
|
||||||
('slug', models.SlugField(default='', null=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
@@ -6,14 +7,17 @@ class PasswordEntry(models.Model):
|
|||||||
site = models.CharField(max_length=255)
|
site = models.CharField(max_length=255)
|
||||||
username = models.CharField(max_length=255)
|
username = models.CharField(max_length=255)
|
||||||
password = models.TextField()
|
password = models.TextField()
|
||||||
client_id = models.CharField(max_length=255)
|
client_id = models.CharField(max_length=255, blank=True)
|
||||||
topic = models.CharField(max_length=255)
|
topic = models.CharField(max_length=255, blank=True)
|
||||||
role = models.CharField(max_length=255)
|
status = models.CharField(max_length=255, blank=True)
|
||||||
acls = models.JSONField()
|
slug = models.SlugField(unique=True, blank=True)
|
||||||
status = models.CharField(max_length=255)
|
|
||||||
slug = models.SlugField(default="", null=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.username, self.site) # Automatically generate slug from the title
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('site', 'username', 'client_id')
|
unique_together = ('site', 'username', 'client_id')
|
||||||
|
|
||||||
@@ -21,5 +25,8 @@ class PasswordEntry(models.Model):
|
|||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
class MasterHash(models.Model):
|
class MasterHash(models.Model):
|
||||||
hash = models.BinaryField()
|
id = models.CharField(primary_key=True, max_length=10, default="1") # Unica riga con ID fisso
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
hash = models.BinaryField() # Hash bcrypt della master password
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"MasterHash(id={self.id})"
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import paho.mqtt.client as mqtt
|
|
||||||
|
|
||||||
MQTT_SERVER="10.211.114.214"
|
|
||||||
MQTT_PORT=1883
|
|
||||||
MQTT_KEEPALIVE=60
|
|
||||||
MQTT_USER="pippo"
|
|
||||||
MQTT_PASSWORD="batt1l0"
|
|
||||||
MQTT_DS_TOPIC="$CONTROL/dynamic-security/v1"
|
|
||||||
|
|
||||||
def on_connect(mqtt_client, userdata, flags, rc, properties):
|
|
||||||
if rc == 0:
|
|
||||||
print('Connected successfully. Properties:', properties)
|
|
||||||
mqtt_client.subscribe(MQTT_TOPIC)
|
|
||||||
else:
|
|
||||||
print('Bad connection. Code:', rc)
|
|
||||||
|
|
||||||
def on_disconnect(mqtt_client, obj, flags, rc, properties):
|
|
||||||
print('disconnected. Code:', rc)
|
|
||||||
|
|
||||||
def on_message(mqtt_client, userdata, msg):
|
|
||||||
print(f'Received message on topic: {msg.topic} with payload: {msg.payload}')
|
|
||||||
|
|
||||||
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
|
|
||||||
client.on_connect = on_connect
|
|
||||||
client.on_disconnect = on_disconnect
|
|
||||||
client.on_message = on_message
|
|
||||||
client.username_pw_set(MQTT_USER, MQTT_PASSWORD)
|
|
||||||
client.connect(
|
|
||||||
host=MQTT_SERVER,
|
|
||||||
port=MQTT_PORT,
|
|
||||||
keepalive=MQTT_KEEPALIVE
|
|
||||||
)
|
|
||||||
58
wallet_api/mqtt_service.py
Normal file
58
wallet_api/mqtt_service.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
|
class MosquittoDynamicSecurity:
|
||||||
|
def __init__(self):
|
||||||
|
config = dotenv_values(".env")
|
||||||
|
self.broker_url = config["MQTT_HOST"]
|
||||||
|
self.broker_port = config["MQTT_PORT"]
|
||||||
|
self.broker_keepalive = config["MQTT_KEEPALIVE"]
|
||||||
|
self.user = config["MQTT_USER"]
|
||||||
|
self.password = config["MQTT_PASSWORD"]
|
||||||
|
self.topic = config["MQTT_DS_TOPIC"]
|
||||||
|
self.resp_topic = config["MQTT_DS_RESP_TOPIC"]
|
||||||
|
self.response = None
|
||||||
|
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
|
||||||
|
self.client.on_connect = self.on_connect
|
||||||
|
self.client.on_disconnect = self.on_disconnect
|
||||||
|
self.client.on_message = self.on_message
|
||||||
|
|
||||||
|
def on_disconnect(self, mqtt_client, obj, flags, rc, properties):
|
||||||
|
print('Disconnesso da Mosquitto con codice:', rc)
|
||||||
|
|
||||||
|
def on_connect(self, client, userdata, flags, rc, properties):
|
||||||
|
if rc == 0:
|
||||||
|
print("Connesso a Mosquitto con codice:", rc)
|
||||||
|
# Sottoscriviti al topic di risposta
|
||||||
|
client.subscribe(self.resp_topic)
|
||||||
|
else:
|
||||||
|
print("Errore di connessione a Mosquitto con codice:", rc)
|
||||||
|
|
||||||
|
def on_message(self, client, userdata, msg):
|
||||||
|
|
||||||
|
print('Response:', msg.payload.decode())
|
||||||
|
# Memorizza la risposta
|
||||||
|
self.response = msg.payload.decode()
|
||||||
|
|
||||||
|
def send_command(self, command):
|
||||||
|
# Pubblica il comando sul topic di controllo
|
||||||
|
self.client.username_pw_set(self.user, self.password)
|
||||||
|
self.client.connect(self.broker_url, int(self.broker_port), int(self.broker_keepalive))
|
||||||
|
self.client.loop_start()
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.client.publish(self.topic, json.dumps(command))
|
||||||
|
|
||||||
|
# Attendi la risposta (timeout di 5 secondi)
|
||||||
|
start_time = time.time()
|
||||||
|
while self.response is None and time.time() - start_time < 5:
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
self.client.loop_stop()
|
||||||
|
self.client.disconnect()
|
||||||
|
|
||||||
|
if self.response:
|
||||||
|
return json.loads(self.response)
|
||||||
|
else:
|
||||||
|
return {"error": "Timeout: nessuna risposta da Mosquitto"}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link
|
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<script src="{% static 'js/htmx.min.js' %}" defer></script>
|
|
||||||
|
|
||||||
<title>{% if title %}: {{title}} {% endif %}</title>
|
|
||||||
|
|
||||||
{% block extra_head %} {% endblock extra_head %} {% block extra_script %}
|
|
||||||
{%endblock extra_script %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% block container %}
|
|
||||||
<main role="main" class="container mt-4">
|
|
||||||
{% block content %} {% endblock content %}
|
|
||||||
</main>
|
|
||||||
{% endblock container %} {% block footer %} {% endblock footer %}
|
|
||||||
|
|
||||||
<!-- Bootstrap Javascript -->
|
|
||||||
<script
|
|
||||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
|
|
||||||
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
|
|
||||||
{% block body_script %} {% endblock body_script %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
{% extends "base.html" %} {% load static %} {% block content %}
|
|
||||||
<h1>List Mosquitto Users</h1>
|
|
||||||
<button class="btn btn-primary mb-3" hx-get="/add_user" hx-target="#content">
|
|
||||||
Add User
|
|
||||||
</button>
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Site</th>
|
|
||||||
<th>Username</th>
|
|
||||||
<th>Client ID</th>
|
|
||||||
<th>Topic</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for user in users %}
|
|
||||||
<tr>
|
|
||||||
<td>{{user.site}}</td>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
href="/wallet/user/edit/{{user.id}}"
|
|
||||||
hx-get="/wallet/user/edit/{{user.id}}"
|
|
||||||
hx-target="#content"
|
|
||||||
>{{user.username}}</a
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>{{user.client_id}}</td>
|
|
||||||
<td>{{user.topic}}</td>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
href="/wallet/user/role/{{user.role}}"
|
|
||||||
hx-get="/wallet/user/role/{{user.role}}"
|
|
||||||
hx-target="#content"
|
|
||||||
>{{user.role}}</a
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if user.status == "enabled" %}
|
|
||||||
<button
|
|
||||||
class="btn btn-danger btn-sm"
|
|
||||||
hx-post="/wallet/user/disable/{{user.id}}"
|
|
||||||
hx-target="#user-list"
|
|
||||||
>
|
|
||||||
Disable
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<button
|
|
||||||
class="btn btn-warning btn-sm"
|
|
||||||
hx-post="/wallet/user/enable/{{user.id}}"
|
|
||||||
hx-target="#user-list"
|
|
||||||
>
|
|
||||||
Enable
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<i> No users </i>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endblock content %}
|
|
||||||
@@ -2,13 +2,15 @@ from django.urls import path
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('', views.home_view, name="home"),
|
||||||
path('add/', views.add_password_api, name='add_user'),
|
path('add/', views.add_password_api, name='add_user'),
|
||||||
path('disable/', views.disable_password_api, name='disable_user'),
|
|
||||||
path('get/', views.get_password_api, name='get_password'),
|
path('get/', views.get_password_api, name='get_password'),
|
||||||
path('list/', views.list_sites_api, name='list_sites'),
|
path('list/', views.list_sites_api, name='list_sites'),
|
||||||
path('user/list/', views.list_users, name='list_users'),
|
|
||||||
path('user/add/', views.add_user, name='add_user'),
|
|
||||||
path('user/edit/<int:user_id>', views.edit_user, name='edit_user'),
|
|
||||||
path('user/role/<str:role>', views.view_role, name='view_role'),
|
|
||||||
path('publish', views.publish_message, name='publish'),
|
path('publish', views.publish_message, name='publish'),
|
||||||
|
path('user/list/', views.list_users, name='list_users'),
|
||||||
|
path('user/create_user/', views.create_user, name='create_user'),
|
||||||
|
path('user/edit/<slug:slug>', views.edit_user, name='edit_user'),
|
||||||
|
path('user/disable/<slug:slug>', views.disable_user, name='disable_user'),
|
||||||
|
path('user/enable/<slug:slug>', views.enable_user, name='enable_user'),
|
||||||
|
path('user/role/<str:role>', views.view_role, name='view_role'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def save_master_hash(hash):
|
|||||||
# Carica l'hash della master password
|
# Carica l'hash della master password
|
||||||
def load_master_hash():
|
def load_master_hash():
|
||||||
try:
|
try:
|
||||||
entry = MasterHash.objects.get(id="1")
|
entry = MasterHash.objects.get()
|
||||||
return entry.hash
|
return entry.hash
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
|
|||||||
76
wallet_api/utils_new.py
Normal file
76
wallet_api/utils_new.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import bcrypt
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from .models import MasterHash
|
||||||
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
# 1. Salva l'hash della master password e la chiave principale cifrata
|
||||||
|
def save_master_data(hashed_password, encrypted_data_key):
|
||||||
|
entry, created = MasterHash.objects.get_or_create(id="1")
|
||||||
|
entry.hash = hashed_password
|
||||||
|
entry.encrypted_data_key = encrypted_data_key
|
||||||
|
entry.save()
|
||||||
|
|
||||||
|
# 2. Carica i dati della master password
|
||||||
|
def load_master_data():
|
||||||
|
try:
|
||||||
|
entry = MasterHash.objects.get(id="1")
|
||||||
|
return entry.hash, entry.encrypted_data_key
|
||||||
|
except Exception:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 3. Autenticazione della master password
|
||||||
|
def authenticate(master_password):
|
||||||
|
stored_hash, encrypted_data_key = load_master_data()
|
||||||
|
if stored_hash is None:
|
||||||
|
hashed_password = bcrypt.hashpw(master_password.encode(), bcrypt.gensalt())
|
||||||
|
key = Fernet.generate_key() # Genera una chiave principale
|
||||||
|
derived_key = derive_key(master_password) # Deriva la chiave dalla master password
|
||||||
|
encrypted_data_key = encrypt_password(key.decode(), derived_key) # Cifra la chiave principale
|
||||||
|
save_master_data(hashed_password, encrypted_data_key)
|
||||||
|
return True, key
|
||||||
|
|
||||||
|
# Controlla se la password inserita è corretta
|
||||||
|
if bcrypt.checkpw(master_password.encode(), stored_hash.tobytes()):
|
||||||
|
derived_key = derive_key(master_password)
|
||||||
|
decrypted_data_key = decrypt_password(encrypted_data_key, derived_key) # Decifra la chiave principale
|
||||||
|
return True, decrypted_data_key.encode()
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# 4. Funzione per cambiare la master password
|
||||||
|
def change_master_password(old_password, new_password):
|
||||||
|
authenticated, data_key = authenticate(old_password)
|
||||||
|
if not authenticated:
|
||||||
|
return False # Fallisce se la vecchia password non è corretta
|
||||||
|
|
||||||
|
# Deriviamo la nuova chiave dalla nuova master password
|
||||||
|
new_derived_key = derive_key(new_password)
|
||||||
|
|
||||||
|
# Cifriamo la chiave principale con la nuova chiave derivata
|
||||||
|
new_encrypted_data_key = encrypt_password(data_key.decode(), new_derived_key)
|
||||||
|
|
||||||
|
# Creiamo il nuovo hash della nuova master password
|
||||||
|
new_hashed_password = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt())
|
||||||
|
|
||||||
|
# Aggiorniamo il database con i nuovi dati
|
||||||
|
save_master_data(new_hashed_password, new_encrypted_data_key)
|
||||||
|
|
||||||
|
return True # Cambio password riuscito
|
||||||
|
|
||||||
|
# 5. Deriva una chiave da una password
|
||||||
|
def derive_key(master_password):
|
||||||
|
hash = hashlib.sha256(master_password.encode()).digest()
|
||||||
|
return base64.urlsafe_b64encode(hash)
|
||||||
|
|
||||||
|
# 6. Cifra una password con una chiave
|
||||||
|
def encrypt_password(password, key):
|
||||||
|
cipher = Fernet(key)
|
||||||
|
return cipher.encrypt(password.encode()).decode()
|
||||||
|
|
||||||
|
# 7. Decifra una password con una chiave
|
||||||
|
def decrypt_password(encrypted_password, key):
|
||||||
|
cipher = Fernet(key)
|
||||||
|
return cipher.decrypt(encrypted_password.encode()).decode()
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
from django.http import JsonResponse
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.shortcuts import render
|
from django.contrib import messages
|
||||||
|
from django.http import JsonResponse, HttpResponse
|
||||||
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from .models import PasswordEntry, MasterHash
|
|
||||||
from .utils import authenticate, derive_key, encrypt_password, decrypt_password
|
|
||||||
from dotenv import dotenv_values
|
|
||||||
import json
|
import json
|
||||||
import threading
|
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
import paho.mqtt.publish as publish
|
import paho.mqtt.publish as publish
|
||||||
|
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
|
from .models import PasswordEntry
|
||||||
|
from .utils import authenticate, derive_key, encrypt_password, decrypt_password
|
||||||
|
from .mqtt_service import MosquittoDynamicSecurity
|
||||||
|
|
||||||
|
|
||||||
config = dotenv_values(".env")
|
config = dotenv_values(".env")
|
||||||
|
authenticate(config['MASTER_PASSWORD'])
|
||||||
|
|
||||||
|
def home_view(request):
|
||||||
|
return render(request, 'home.html')
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def publish_message(request):
|
def publish_message(request):
|
||||||
@@ -25,34 +35,34 @@ def publish_message(request):
|
|||||||
|
|
||||||
def list_users(request):
|
def list_users(request):
|
||||||
users = PasswordEntry.objects.all()
|
users = PasswordEntry.objects.all()
|
||||||
lock_users = threading.Lock()
|
|
||||||
return render(request, 'wallet_api/list_users.html', {'users': users})
|
return render(request, 'wallet_api/list_users.html', {'users': users})
|
||||||
|
|
||||||
def edit_user(request, user_id):
|
def create_user(request):
|
||||||
user = PasswordEntry.objects.filter(id=user_id).first()
|
|
||||||
return render(request, 'wallet_api/edit_user.html', {'user': user})
|
|
||||||
|
|
||||||
def add_user(request):
|
|
||||||
return render(request, 'wallet_api/add_user.html')
|
|
||||||
|
|
||||||
def view_role(request, role):
|
|
||||||
return render(request, 'wallet_api/role_info.html', {'role': role })
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def disable_password_api(request):
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
data = json.loads(request.body)
|
site = request.POST.get('site')
|
||||||
master_password = data.get('master_password')
|
username = request.POST.get('username')
|
||||||
site = data.get('site')
|
client_id = request.POST.get('clientId')
|
||||||
username = data.get('username')
|
topic = request.POST.get('topic')
|
||||||
password = data.get('password')
|
password = request.POST.get('password')
|
||||||
client_id = data.get('client_id')
|
|
||||||
topic = data.get('topic')
|
|
||||||
|
|
||||||
if not authenticate(master_password):
|
# Comando per creare un utente
|
||||||
return JsonResponse({"error": "Master password errata"}, status=403)
|
command = {
|
||||||
|
"commands":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "createClient",
|
||||||
|
"username": username,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
key = derive_key(master_password)
|
# Invia il comando a Mosquitto
|
||||||
|
mqtt_service = MosquittoDynamicSecurity()
|
||||||
|
response = mqtt_service.send_command(command)
|
||||||
|
if "error" not in response["responses"][0]:
|
||||||
|
|
||||||
|
key = derive_key(config['MASTER_PASSWORD'])
|
||||||
encrypted_password = encrypt_password(password, key)
|
encrypted_password = encrypt_password(password, key)
|
||||||
|
|
||||||
PasswordEntry.objects.create(
|
PasswordEntry.objects.create(
|
||||||
@@ -60,9 +70,86 @@ def disable_password_api(request):
|
|||||||
username=username,
|
username=username,
|
||||||
password=encrypted_password,
|
password=encrypted_password,
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
topic=topic
|
topic=topic,
|
||||||
|
status='enabled'
|
||||||
)
|
)
|
||||||
return JsonResponse({"message": "Password aggiunta con successo"})
|
|
||||||
|
messages.success(request, 'Utente creato con successo!') # Messaggio di successo
|
||||||
|
return redirect('list_users') # Reindirizza alla lista degli utenti
|
||||||
|
else:
|
||||||
|
messages.warning(request, f'Errore durante la creazione dell\'utente: {response["responses"][0]["error"]}') # Messaggio di errore
|
||||||
|
return render(request, 'wallet_api/create_user.html', {'response': response}) # Rimani sulla stessa vista
|
||||||
|
|
||||||
|
# Se la richiesta non è POST, mostra il form di creazione utente
|
||||||
|
return render(request, 'wallet_api/create_user.html')
|
||||||
|
|
||||||
|
def edit_user(request, slug):
|
||||||
|
user = PasswordEntry.objects.filter(slug=slug).first()
|
||||||
|
return render(request, 'wallet_api/edit_user.html', {'user': user})
|
||||||
|
|
||||||
|
def disable_user(request, slug):
|
||||||
|
user = PasswordEntry.objects.filter(slug=slug).values('id','username')
|
||||||
|
print(user.first())
|
||||||
|
# Comando per creare un utente
|
||||||
|
command = {
|
||||||
|
"commands":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "disableClient",
|
||||||
|
"username": user.first()["username"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Invia il comando a Mosquitto
|
||||||
|
mqtt_service = MosquittoDynamicSecurity()
|
||||||
|
response = mqtt_service.send_command(command)
|
||||||
|
if "error" not in response["responses"][0]:
|
||||||
|
|
||||||
|
PasswordEntry.objects.update(
|
||||||
|
status='disabled'
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, 'Utente disabilitato con successo!') # Messaggio di successo
|
||||||
|
if request.htmx:
|
||||||
|
return render(request, 'partials/enable_user.html')
|
||||||
|
return redirect('list_users') # Reindirizza alla lista degli utenti
|
||||||
|
else:
|
||||||
|
messages.warning(request, f'Errore durante la disabilitazione dell\'utente: {response["responses"][0]["error"]}') # Messaggio di errore
|
||||||
|
return redirect('list_users') # Reindirizza alla lista degli utenti
|
||||||
|
|
||||||
|
def enable_user(request, slug):
|
||||||
|
user = PasswordEntry.objects.filter(slug=slug).values('id','username')
|
||||||
|
print(user.first())
|
||||||
|
# Comando per creare un utente
|
||||||
|
command = {
|
||||||
|
"commands":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "enableClient",
|
||||||
|
"username": user.first()["username"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Invia il comando a Mosquitto
|
||||||
|
mqtt_service = MosquittoDynamicSecurity()
|
||||||
|
response = mqtt_service.send_command(command)
|
||||||
|
if "error" not in response["responses"][0]:
|
||||||
|
|
||||||
|
PasswordEntry.objects.update(
|
||||||
|
status='enabled'
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, 'Utente abilitato con successo!') # Messaggio di successo
|
||||||
|
|
||||||
|
return redirect('list_users') # Reindirizza alla lista degli utenti
|
||||||
|
else:
|
||||||
|
messages.warning(request, f'Errore durante la abilitazione dell\'utente: {response["responses"][0]["error"]}') # Messaggio di errore
|
||||||
|
return redirect('list_users') # Reindirizza alla lista degli utenti
|
||||||
|
|
||||||
|
def view_role(request, role):
|
||||||
|
return render(request, 'wallet_api/role_info.html', {'role': role })
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def get_password_api(request):
|
def get_password_api(request):
|
||||||
@@ -122,8 +209,6 @@ def add_password_api(request):
|
|||||||
username=username,
|
username=username,
|
||||||
password=encrypted_password,
|
password=encrypted_password,
|
||||||
client_id=client_id,
|
client_id=client_id,
|
||||||
topic=topic,
|
topic=topic
|
||||||
role='',
|
|
||||||
acls='{}'
|
|
||||||
)
|
)
|
||||||
return JsonResponse({"message": "Password aggiunta con successo"})
|
return JsonResponse({"message": "Password aggiunta con successo"})
|
||||||
|
|||||||
Reference in New Issue
Block a user