diff --git a/apps/logs/middlewares.py b/apps/logs/middlewares.py
deleted file mode 100644
index 77f749b9da0663a4a3fd4e1b17aa8c8967fc5372..0000000000000000000000000000000000000000
--- a/apps/logs/middlewares.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
-# SPDX-License-Identifier: GPL-3.0-or-later
-
-from django.conf import settings
-from django.contrib.auth.models import AnonymousUser
-
-from threading import local
-
-
-USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
-IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
-
-_thread_locals = local()
-
-
-def _set_current_user_and_ip(user=None, ip=None):
-    setattr(_thread_locals, USER_ATTR_NAME, user)
-    setattr(_thread_locals, IP_ATTR_NAME, ip)
-
-
-def get_current_user():
-    return getattr(_thread_locals, USER_ATTR_NAME, None)
-
-
-def get_current_ip():
-    return getattr(_thread_locals, IP_ATTR_NAME, None)
-
-
-def get_current_authenticated_user():
-    current_user = get_current_user()
-    if isinstance(current_user, AnonymousUser):
-        return None
-    return current_user
-
-
-class LogsMiddleware(object):
-    """
-    This middleware get the current user with his or her IP address on each request.
-    """
-
-    def __init__(self, get_response):
-        self.get_response = get_response
-
-    def __call__(self, request):
-        user = request.user
-        if 'HTTP_X_FORWARDED_FOR' in request.META:
-            ip = request.META.get('HTTP_X_FORWARDED_FOR')
-        else:
-            ip = request.META.get('REMOTE_ADDR')
-
-        _set_current_user_and_ip(user, ip)
-        response = self.get_response(request)
-        _set_current_user_and_ip(None, None)
-
-        return response
diff --git a/apps/logs/signals.py b/apps/logs/signals.py
index fb17157a9fc14f8b22dc99bab586b95b60e6f834..0c80a4cd66daf4cb40c8769c25ca8ec4f17d1f12 100644
--- a/apps/logs/signals.py
+++ b/apps/logs/signals.py
@@ -9,7 +9,7 @@ import getpass
 
 from note.models import NoteUser, Alias
 
-from .middlewares import get_current_authenticated_user, get_current_ip
+from note_kfet.middlewares import get_current_authenticated_user, get_current_ip
 from .models import Changelog
 
 
diff --git a/apps/member/backends.py b/apps/member/backends.py
index f0b4e8f2925ca352e73481920a9b065149c4ba16..e68f6c1950ed4b121cdf45e87b0cd6809d4d1774 100644
--- a/apps/member/backends.py
+++ b/apps/member/backends.py
@@ -3,10 +3,10 @@
 
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import PermissionDenied
 from django.db.models import Q, F
 
 from note.models import Note, NoteUser, NoteClub, NoteSpecial
+from note_kfet.middlewares import get_current_session
 from .models import Membership, RolePermissions, Club
 from django.contrib.auth.backends import ModelBackend
 
@@ -37,7 +37,8 @@ class PermissionBackend(ModelBackend):
                         F=F,
                         Q=Q
                     )
-                    yield permission
+                    if permission.mask.rank <= get_current_session().get("permission_mask", 0):
+                        yield permission
 
     @staticmethod
     def filter_queryset(user, model, t, field=None):
@@ -50,7 +51,7 @@ class PermissionBackend(ModelBackend):
         :return: A query that corresponds to the filter to give to a queryset
         """
 
-        if user.is_superuser:
+        if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
             # Superusers have all rights
             return Q()
 
@@ -68,7 +69,7 @@ class PermissionBackend(ModelBackend):
         return query
 
     def has_perm(self, user_obj, perm, obj=None):
-        if user_obj.is_superuser:
+        if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
             return True
 
         if obj is None:
diff --git a/apps/member/forms.py b/apps/member/forms.py
index d2134cddfda74bf1825b4d672c2c0d921744a961..0f1ff1896f9ecb4820d6e2d073b7430479b358a2 100644
--- a/apps/member/forms.py
+++ b/apps/member/forms.py
@@ -6,12 +6,21 @@ from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Layout
 from dal import autocomplete
 from django import forms
-from django.contrib.auth.forms import UserCreationForm
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
 from django.contrib.auth.models import User
 
+from permission.models import PermissionMask
 from .models import Profile, Club, Membership
 
 
+class CustomAuthenticationForm(AuthenticationForm):
+    permission_mask = forms.ModelChoiceField(
+        label="Masque de permissions",
+        queryset=PermissionMask.objects.order_by("rank"),
+        empty_label=None,
+    )
+
+
 class SignUpForm(UserCreationForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
diff --git a/apps/member/views.py b/apps/member/views.py
index 293ad3a8c0b6e1aeca4d6d7a555e5a5eb02f83bf..3b19503beb52b97c486c1225f4fd4e71a08f778f 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -9,6 +9,7 @@ from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.auth.models import User
+from django.contrib.auth.views import LoginView
 from django.core.exceptions import ValidationError
 from django.db.models import Q
 from django.http import HttpResponseRedirect
@@ -26,11 +27,20 @@ from note.tables import HistoryTable, AliasTable
 from .backends import PermissionBackend
 
 from .filters import UserFilter, UserFilterFormHelper
-from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
+from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \
+    CustomAuthenticationForm
 from .models import Club, Membership
 from .tables import ClubTable, UserTable
 
 
+class CustomLoginView(LoginView):
+    form_class = CustomAuthenticationForm
+
+    def form_valid(self, form):
+        self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank
+        return super().form_valid(form)
+
+
 class UserCreateView(CreateView):
     """
     Une vue pour inscrire un utilisateur et lui créer un profile
diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py
index 4d8be07fb220b25a4f2d8d6f149915bbac098664..366960240de1e7ddff8963ca30c7fddc4e9ff65c 100644
--- a/apps/note/api/serializers.py
+++ b/apps/note/api/serializers.py
@@ -4,7 +4,7 @@
 from rest_framework import serializers
 from rest_polymorphic.serializers import PolymorphicSerializer
 
-from logs.middlewares import get_current_authenticated_user
+from note_kfet.middlewares import get_current_authenticated_user
 from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
 from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
     TemplateTransaction, SpecialTransaction
diff --git a/apps/note/fixtures/initial.json b/apps/note/fixtures/initial.json
index 3654fa2f5f24c17498de4452295d1a899d798f1b..eac2bda19282b70c92d54f2e628847e2e5bd6d18 100644
--- a/apps/note/fixtures/initial.json
+++ b/apps/note/fixtures/initial.json
@@ -3,7 +3,7 @@
         "model": "note.note",
         "pk": 1,
         "fields": {
-            "polymorphic_ctype": 40,
+            "polymorphic_ctype": 41,
             "balance": 0,
             "is_active": true,
             "display_image": "",
@@ -14,7 +14,7 @@
         "model": "note.note",
         "pk": 2,
         "fields": {
-            "polymorphic_ctype": 40,
+            "polymorphic_ctype": 41,
             "balance": 0,
             "is_active": true,
             "display_image": "",
@@ -25,7 +25,7 @@
         "model": "note.note",
         "pk": 3,
         "fields": {
-            "polymorphic_ctype": 40,
+            "polymorphic_ctype": 41,
             "balance": 0,
             "is_active": true,
             "display_image": "",
@@ -36,7 +36,7 @@
         "model": "note.note",
         "pk": 4,
         "fields": {
-            "polymorphic_ctype": 40,
+            "polymorphic_ctype": 41,
             "balance": 0,
             "is_active": true,
             "display_image": "",
@@ -47,7 +47,7 @@
         "model": "note.note",
         "pk": 5,
         "fields": {
-            "polymorphic_ctype": 39,
+            "polymorphic_ctype": 40,
             "balance": 0,
             "is_active": true,
             "display_image": "",
@@ -58,7 +58,7 @@
         "model": "note.note",
         "pk": 6,
         "fields": {
-            "polymorphic_ctype": 39,
+            "polymorphic_ctype": 40,
             "balance": 0,
             "is_active": true,
             "display_image": "",
diff --git a/apps/permission/admin.py b/apps/permission/admin.py
index f7a9b4b512162554656557ce897b3d41b5d2e4ac..2e6899fda08ff4809e67cf614e27eb7259cfc104 100644
--- a/apps/permission/admin.py
+++ b/apps/permission/admin.py
@@ -3,7 +3,15 @@
 
 from django.contrib import admin
 
-from .models import Permission
+from .models import Permission, PermissionMask
+
+
+@admin.register(PermissionMask)
+class PermissionMaskAdmin(admin.ModelAdmin):
+    """
+    Admin customisation for Permission
+    """
+    list_display = ('rank', 'description')
 
 
 @admin.register(Permission)
diff --git a/apps/permission/models.py b/apps/permission/models.py
index ead3f7217da0f2812706fea2b68ddc776461e986..f333e377e602cfb02c978f42a6df2def5bffdbab 100644
--- a/apps/permission/models.py
+++ b/apps/permission/models.py
@@ -50,6 +50,20 @@ class InstancedPermission:
         return self.__repr__()
 
 
+class PermissionMask(models.Model):
+    rank = models.PositiveSmallIntegerField(
+        verbose_name=_('rank'),
+    )
+
+    description = models.CharField(
+        max_length=255,
+        verbose_name=_('description'),
+    )
+
+    def __str__(self):
+        return self.description
+
+
 class Permission(models.Model):
 
     PERMISSION_TYPES = [
@@ -85,6 +99,11 @@ class Permission(models.Model):
 
     type = models.CharField(max_length=15, choices=PERMISSION_TYPES)
 
+    mask = models.ForeignKey(
+        PermissionMask,
+        on_delete=models.PROTECT,
+    )
+
     field = models.CharField(max_length=255, blank=True)
 
     description = models.CharField(max_length=255, blank=True)
diff --git a/apps/permission/signals.py b/apps/permission/signals.py
index e93c1666775cf9a9d1b6f8c3a271aa4eb710e73e..6d4f5f19fce1a43ba0748b21e1b7e33ea42c0c9e 100644
--- a/apps/permission/signals.py
+++ b/apps/permission/signals.py
@@ -2,7 +2,7 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 from django.core.exceptions import PermissionDenied
-from logs.middlewares import get_current_authenticated_user
+from note_kfet.middlewares import get_current_authenticated_user
 
 
 EXCLUDED = [
diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py
index 460bf9a6a304f7626da4629150e070dd9aa880bc..f65b606e6825831e79f7a3ec90940e683bfa59e4 100644
--- a/apps/permission/templatetags/perms.py
+++ b/apps/permission/templatetags/perms.py
@@ -4,7 +4,7 @@
 from django.contrib.contenttypes.models import ContentType
 from django.template.defaultfilters import stringfilter
 
-from logs.middlewares import get_current_authenticated_user
+from note_kfet.middlewares import get_current_authenticated_user, get_current_session
 from django import template
 
 from member.backends import PermissionBackend
@@ -19,7 +19,7 @@ def not_empty_model_list(model_name):
     user = get_current_authenticated_user()
     if user is None:
         return False
-    elif user.is_superuser:
+    elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
         return True
     spl = model_name.split(".")
     ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
@@ -32,7 +32,7 @@ def not_empty_model_change_list(model_name):
     user = get_current_authenticated_user()
     if user is None:
         return False
-    elif user.is_superuser:
+    elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42:
         return True
     spl = model_name.split(".")
     ct = ContentType.objects.get(app_label=spl[0], model=spl[1])
diff --git a/entrypoint.sh b/entrypoint.sh
index e5a22a5a53f2f25df533f9370a89f7e77205cb93..4d0177e88f09d77a5c69167bbe59e303051aaf19 100755
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -7,7 +7,7 @@ if [ -z ${NOTE_URL+x} ]; then
 else
   sed -i -e "s/example.com/$DOMAIN/g" /code/apps/member/fixtures/initial.json
   sed -i -e "s/localhost/$NOTE_URL/g" /code/note_kfet/fixtures/initial.json
-  sed -i -e "s/\.\*/https?:\/\/$NOTE_URL\/.*/g" /code/note_kfet/fixtures/cas.json
+  sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /code/note_kfet/fixtures/cas.json
   sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /code/note_kfet/fixtures/cas.json
 fi
 
diff --git a/note_kfet/middlewares.py b/note_kfet/middlewares.py
index b034e2bee3453486a1fb10fbabd687de9c70227f..fff824c5f5a8b75074768ce3d255153befcb8b0c 100644
--- a/note_kfet/middlewares.py
+++ b/note_kfet/middlewares.py
@@ -1,6 +1,66 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser, User
+
+from threading import local
+
+from django.contrib.sessions.backends.db import SessionStore
+
+USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
+SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session')
+IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
+
+_thread_locals = local()
+
+
+def _set_current_user_and_ip(user=None, session=None, ip=None):
+    setattr(_thread_locals, USER_ATTR_NAME, user)
+    setattr(_thread_locals, SESSION_ATTR_NAME, session)
+    setattr(_thread_locals, IP_ATTR_NAME, ip)
+
+
+def get_current_user() -> User:
+    return getattr(_thread_locals, USER_ATTR_NAME, None)
+
+
+def get_current_session() -> SessionStore:
+    return getattr(_thread_locals, SESSION_ATTR_NAME, None)
+
+
+def get_current_ip() -> str:
+    return getattr(_thread_locals, IP_ATTR_NAME, None)
+
+
+def get_current_authenticated_user():
+    current_user = get_current_user()
+    if isinstance(current_user, AnonymousUser):
+        return None
+    return current_user
+
+
+class SessionMiddleware(object):
+    """
+    This middleware get the current user with his or her IP address on each request.
+    """
+
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        user = request.user
+        if 'HTTP_X_FORWARDED_FOR' in request.META:
+            ip = request.META.get('HTTP_X_FORWARDED_FOR')
+        else:
+            ip = request.META.get('REMOTE_ADDR')
+
+        _set_current_user_and_ip(user, request.session, ip)
+        response = self.get_response(request)
+        _set_current_user_and_ip(None, None, None)
+
+        return response
+
 
 class TurbolinksMiddleware(object):
     """
diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py
index 28935deba3ee7ba6fc7aeb9fa6d7f5ea383a2623..7370f1bf7b0037403c39dafc58d4bac9f019a5bf 100644
--- a/note_kfet/settings/__init__.py
+++ b/note_kfet/settings/__init__.py
@@ -74,7 +74,7 @@ if "cas" in INSTALLED_APPS:
 
 
 if "logs" in INSTALLED_APPS:
-    MIDDLEWARE += ('logs.middlewares.LogsMiddleware',)
+    MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
 
 if "debug_toolbar" in INSTALLED_APPS:
     MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")
diff --git a/note_kfet/urls.py b/note_kfet/urls.py
index da2f9d6c246833c3962b8b0727869a9585b5c4f9..9170c62ed6c21eab048e8db366f240f194ff880f 100644
--- a/note_kfet/urls.py
+++ b/note_kfet/urls.py
@@ -7,6 +7,8 @@ from django.contrib import admin
 from django.urls import path, include
 from django.views.generic import RedirectView
 
+from member.views import CustomLoginView
+
 urlpatterns = [
     # Dev so redirect to something random
     path('', RedirectView.as_view(pattern_name='note:transfer'), name='index'),
@@ -16,10 +18,11 @@ urlpatterns = [
 
     # Include Django Contrib and Core routers
     path('i18n/', include('django.conf.urls.i18n')),
-    path('accounts/', include('member.urls')),
-    path('accounts/', include('django.contrib.auth.urls')),
     path('admin/doc/', include('django.contrib.admindocs.urls')),
     path('admin/', admin.site.urls),
+    path('accounts/', include('member.urls')),
+    path('accounts/login/', CustomLoginView.as_view()),
+    path('accounts/', include('django.contrib.auth.urls')),
     path('api/', include('api.urls')),
 ]