diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bb5e2ed --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_size = 4 + +[{Makefile,debian/rules,make.d/*}] +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore index aaf09c5..d0b2d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,27 @@ -# local env files -.env -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -.DS_Store -node_modules -/dist __pycache__/ + +/data/* +!/data/.gitkeep /db.sqlite3 -/venv/ +/media/ + +/dist/ +*.egg-info/ +/build/ +/.pybuild/ +/.tox/ +.coverage* /debian/*debhelper* /debian/*.substvars /debian/files /debian/python3-userausfall/ /debian/userausfall/ /debian/userausfall-webapp/ -/.pybuild/ -/build/ -/userausfall.egg-info/ + +node_modules/ +app/dist/ +npm-debug.log* + +.idea/ +*.env* +*.swp diff --git a/README.md b/README.md index 1d7696a..d28ef3e 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,31 @@ -# userausfall +# Userausfall -## Backend: Development server +User account management for systemausfall.org + +## Quick Development Setup + +Bootstrap your venv and project (you’ll need to do this only once): ```shell -export DJANGO_SETTINGS_MODULE=userausfall.settings USERAUSFALL_SECRET_KEY=dev +# Create a virtual environment +python3 -m venv --system-site-packages venv +# Activate your venv +. venv/bin/activate +# Install dependencies +pip install --editable . +``` + +In the future just run: +```shell +# Activate your venv +. venv/bin/activate +# Configure the settings +export DJANGO_SETTINGS_MODULE=userausfall.settings +# Apply database migrations python3 -m django migrate +``` + +Start the API development server with: +```shell python3 -m django runserver ``` - -## Frontend: Project setup -``` -npm install -``` - -### Compiles and hot-reloads for development -``` -npm run serve -``` - -### Compiles and minifies for production -``` -npm run build -``` - -### Lints and fixes files -``` -npm run lint -``` - -### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/). - -## Credits - -* Parts of initial code are based on [schnipsel](https://git.hack-hro.de/kmohrf/schnipsel). \ No newline at end of file diff --git a/debian/control b/debian/control index fc16f79..cf8adc5 100644 --- a/debian/control +++ b/debian/control @@ -31,6 +31,7 @@ Depends: ${python3:Depends}, python3-django (>= 2.2), python3-django-imagekit, + python3-ldap3, # python3-djangorestframework, # python3-djoser (>= 2.1), Description: Python backend for the userausfall web application diff --git a/djeveric/__init__.py b/djeveric/__init__.py deleted file mode 100644 index 5c0c9d3..0000000 --- a/djeveric/__init__.py +++ /dev/null @@ -1,154 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.tokens import default_token_generator -from django.contrib.contenttypes.models import ContentType -from django.utils.encoding import force_bytes -from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode -from rest_framework import serializers, status -from rest_framework.exceptions import ValidationError -from rest_framework.response import Response -from rest_framework.settings import api_settings -from rest_framework.views import APIView - - -def encode_pk(resource): - """Encode the primary key of a resource with Base64 for usage in URLs.""" - return urlsafe_base64_encode(force_bytes(resource.pk)) - - -class ConfirmationError(Exception): - """An error occurring while checking a confirmation.""" - - def __init__(self, message=None, errors=None): - if errors is None: - self.errors = {} - else: - self.errors = errors - if message is not None: - non_field_errors = self.errors.get(api_settings.NON_FIELD_ERRORS_KEY, []) - non_field_errors.append(message) - self.errors[api_settings.NON_FIELD_ERRORS_KEY] = non_field_errors - - -class Email: - """ - Base class for an email message. - """ - subject = "" - - def __init__(self, user): - self.user = user - - def get_subject(self): - return self.subject - - def get_message(self, context): - raise NotImplementedError() - - def send(self, context): - self.user.email_user(self.get_subject(), self.get_message(context)) - - -class Confirmation: - """ - Base class for handling a confirmation process. - """ - email_class = Email - - def __init__(self, user, resource): - self.user = user - self.resource = resource - - def check(self): - if not self.has_permission(self.user, self.resource): - raise ConfirmationError("Permission denied") - if self.is_confirmed(self.resource): - raise ConfirmationError("Already confirmed") - self.confirm(self.resource) - - def confirm(self, resource): - """Overwrite this method to supply operations to confirm the resource.""" - pass - - def get_email(self): - return self.email_class(self.user) - - def has_permission(self, user, resource) -> bool: - """Overwrite this method returning if a user may confirm a resource.""" - return False - - def is_confirmed(self, resource) -> bool: - """Overwrite this method to tell if a resource is confirmed.""" - return False - - def send_request(self): - if self.is_confirmed(self.resource): - return - self.get_email().send({ - "token": default_token_generator.make_token(self.user), - "uid": encode_pk(self.user), - "rtid": encode_pk(ContentType.objects.get_for_model(self.resource)), - "rid": encode_pk(self.resource), - }) - - -class ConfirmationSerializer(serializers.Serializer): - """ - Serializer class for confirmation requests. - """ - token = serializers.CharField() - uid = serializers.CharField() - rtid = serializers.CharField() - rid = serializers.CharField() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.user = None - self.resource_type = None - self.resource = None - - def validate_uid(self, value): - self.user = self.get_model_object(get_user_model(), value) - return value - - def validate_rtid(self, value): - self.resource_type = self.get_model_object(ContentType, value) - return value - - def validate(self, data): - # we need to be sure that the rtid was already decoded - self.resource = self.get_model_object(self.resource_type.model_class(), data["rid"]) - return data - - def get_model_object(self, model, oid): - try: - # urlsafe_base64_decode() decodes to bytestring - oid = urlsafe_base64_decode(oid).decode() - return model._default_manager.get(id=oid) - except (TypeError, ValueError, OverflowError, model.DoesNotExist): - raise ValidationError("Error while decoding object id") - - -class ConfirmationView(APIView): - """ - View for creating a confirmation API endpoint. - """ - confirmation_class = Confirmation - serializer_class = ConfirmationSerializer - - def post(self, request, format=None): - try: - self.check_confirmation(request.data) - return Response({}, status=status.HTTP_204_NO_CONTENT) - except ConfirmationError as e: - return Response(e.errors, status=status.HTTP_400_BAD_REQUEST) - - def check_confirmation(self, data): - serializer = self.serializer_class(data=data) - if not serializer.is_valid(): - raise ConfirmationError(errors=serializer.errors) - token = serializer.validated_data["token"] - if default_token_generator.check_token(serializer.user, token): - # confirm resource or raise an exception otherwise - self.confirmation_class(serializer.user, serializer.resource).check() - else: - raise ConfirmationError("Invalid token") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9b7a2f3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 120 +target-version = ['py39'] +exclude = ''' +( + /( + \.eggs + | \.git + | \.tox + | migrations + ) +) +''' diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 906b98f..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -setuptools~=40.8.0 -django~=2.2.13 -djangorestframework~=3.9.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ac4ff4e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 120 +select = C,E,F,I,W,B,B950 +ignore = E203, E501, W503 +exclude = .tox, node_modules, src, **/migrations/*.py +import-order-style = google +application-import-names = userausfall diff --git a/setup.py b/setup.py index 0a4e9fc..2929038 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,45 @@ -from setuptools import setup, find_packages +import os + +from setuptools import find_namespace_packages, setup from userausfall import __version__ +__dir__ = os.path.abspath(os.path.dirname(__file__)) + +try: + with open(os.path.join(__dir__, "README.md")) as f: + long_description = "\n" + f.read() +except FileNotFoundError: + long_description = "" + setup( name="userausfall", version=__version__, description="account management for systemausfall.org", + long_description=long_description, + long_description_content_type="text/markdown", url="https://git.systemausfall.org/systemausfall.org/userausfall", - author="Robert Waltemath", - author_email="rw@roko.li", - packages=find_packages(), - install_requires=( - "django>=2.2<3.0", - # "djangorestframework>=3.12<4.0", - # "djoser>=2.1<3.0", - ), + author="userausfall developers", + author_email="hallo@roko.li", + license="AGPL-3.0-or-later", + packages=find_namespace_packages(include=["userausfall"]), + install_requires=[ + "django~=3.2.8", + "djangorestframework~=3.12.1", + "djangorestframework-camel-case~=1.2.0", + "django-filter~=2.4.0", + "django-rest-registration~=0.6.4", + "djeveric@https://git.hack-hro.de/memoorje/djeveric/-/archive/main/djeveric-main.tar.gz", + "drf-spectacular~=0.18.2", + "ldap3~=2.8.1", + ], include_package_data=True, + classifiers=[ + "Development Status :: 3 - Alpha", + "Framework :: Django :: 3.2", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Internet :: WWW/HTTP", + ], ) diff --git a/src/App.vue b/src/App.vue index 9441873..411bb06 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,16 +1,25 @@ - - + + + + + + - - diff --git a/src/api.ts b/src/api.ts index 07ca5ca..015eedb 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,5 @@ import Cookies from "js-cookie"; +import Vue from "vue"; type HTTPMethod = "GET" | "POST" | "PUT" | "PATCH"; @@ -95,3 +96,5 @@ export class User { }); } } + +export const user = Vue.observable(new User()); diff --git a/src/components/InlineEditor.vue b/src/components/InlineEditor.vue deleted file mode 100644 index f3d3faa..0000000 --- a/src/components/InlineEditor.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - {{ value }} - {{ - isEditing ? "Speichern" : "Bearbeiten" - }} - - - - diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue deleted file mode 100644 index 9dd5186..0000000 --- a/src/components/LoginForm.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - {{ errorMessage }} - - - - - - - - - - - - - - - {{ mode === "login" ? "Anmelden" : "Konto anlegen" }} - - Konto anlegen - Anmelden - - - - - - - diff --git a/src/router/index.ts b/src/router/index.ts index 5d8f855..a4806e7 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,29 +1,25 @@ import Vue from "vue"; import VueRouter, { RouteConfig } from "vue-router"; -import MainPage from "../views/MainPage.vue"; +import LoginView from "../views/Login.vue"; +import UserView from "../views/User.vue"; Vue.use(VueRouter); const routes: Array = [ { path: "/", - name: "home", - component: MainPage, + name: "index", + component: UserView, }, { path: "/login", name: "login", - component: MainPage, + component: LoginView, }, { path: "/signup", name: "signup", - component: MainPage, - }, - { - path: "/confirm/:uid/:token", - name: "confirm", - component: MainPage, + component: LoginView, }, ]; diff --git a/src/views/Login.vue b/src/views/Login.vue new file mode 100644 index 0000000..b7d286d --- /dev/null +++ b/src/views/Login.vue @@ -0,0 +1,73 @@ + + + + + {{ errorMessage }} + + + + + + + + + + + + + + + {{ mode === "login" ? "Anmelden" : "Konto anlegen" }} + + Konto anlegen + Anmelden + + + + + + diff --git a/src/views/MainPage.vue b/src/views/MainPage.vue deleted file mode 100644 index cf0509d..0000000 --- a/src/views/MainPage.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/components/UserTable.vue b/src/views/User.vue similarity index 56% rename from src/components/UserTable.vue rename to src/views/User.vue index 4da3984..9565941 100644 --- a/src/components/UserTable.vue +++ b/src/views/User.vue @@ -1,5 +1,5 @@ - + Dein Konto ist noch nicht aktiv. Jetzt aktivieren @@ -12,15 +12,11 @@ Benutzername - - - + {{ user.username }} Vertrauensperson - - - + @@ -28,15 +24,12 @@