diff --git a/app/corpora/decorators.py b/app/corpora/decorators.py
index 96d4ac6938f02a046f265ccfab0fb38e0b0c313f..1d6eb2e1e2104280cadf86b009368b7a6c7a86b0 100644
--- a/app/corpora/decorators.py
+++ b/app/corpora/decorators.py
@@ -9,14 +9,13 @@ def corpus_follower_permission_required(*permissions):
         @wraps(f)
         def decorated_function(*args, **kwargs):
             corpus_id = kwargs.get('corpus_id')
-            corpus = Corpus.query.get_or_404(corpus_id)
-            if current_user == corpus.user or current_user.is_administrator():
-                return f(*args, **kwargs)
-            if not current_user.is_following_corpus(corpus):
-                abort(403)
-            corpus_follower_association = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first_or_404()
-            if not all([corpus_follower_association.role.has_permission(p) for p in permissions]):
+            cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
+            if cfa is None:
                 abort(403)
+            corpus = cfa.corpus
+            if not (corpus.user == current_user or current_user.is_administrator()):
+                if not all([cfa.role.has_permission(p) for p in permissions]):
+                    abort(403)
             return f(*args, **kwargs)
         return decorated_function
     return decorator
@@ -27,8 +26,8 @@ def corpus_owner_or_admin_required(f):
     def decorated_function(*args, **kwargs):
         corpus_id = kwargs.get('corpus_id')
         corpus = Corpus.query.get_or_404(corpus_id)
-        if current_user == corpus.user or current_user.is_administrator():
-            return f(*args, **kwargs)
-        abort(403)
+        if not (corpus.user == current_user or current_user.is_administrator()):
+            abort(403)
+        return f(*args, **kwargs)
     return decorated_function
 
diff --git a/app/corpora/files/json_routes.py b/app/corpora/files/json_routes.py
index 1794c4d128cf72e34993d95665333a998fdcc84c..e4f06084410905356cb48d8197c857ee3a79becd 100644
--- a/app/corpora/files/json_routes.py
+++ b/app/corpora/files/json_routes.py
@@ -1,4 +1,4 @@
-from flask import current_app
+from flask import abort, current_app
 from threading import Thread
 from app import db
 from app.decorators import content_negotiation
diff --git a/app/corpora/followers/json_routes.py b/app/corpora/followers/json_routes.py
index b5c3c99088838145e7480925f644a3ab008d588c..f8517359e81bc8d441fd08d2bef4cde8b87b0055 100644
--- a/app/corpora/followers/json_routes.py
+++ b/app/corpora/followers/json_routes.py
@@ -1,5 +1,4 @@
 from flask import abort, request
-from flask_login import current_user
 from app import db
 from app.decorators import content_negotiation
 from app.models import (
@@ -8,12 +7,12 @@ from app.models import (
     CorpusFollowerRole,
     User
 )
-from ..decorators import corpus_owner_or_admin_required
+from ..decorators import corpus_follower_permission_required
 from . import bp
 
 
 @bp.route('/<hashid:corpus_id>/followers', methods=['POST'])
-@corpus_owner_or_admin_required
+@corpus_follower_permission_required('ADD_FOLLOWER')
 @content_negotiation(consumes='application/json', produces='application/json')
 def create_corpus_followers(corpus_id):
     usernames = request.json
@@ -32,7 +31,7 @@ def create_corpus_followers(corpus_id):
 
 
 @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>/role', methods=['PUT'])
-@corpus_owner_or_admin_required
+@corpus_follower_permission_required('UPDATE_FOLLOWER')
 @content_negotiation(consumes='application/json', produces='application/json')
 def update_corpus_follower_role(corpus_id, follower_id):
     role_name = request.json
@@ -52,19 +51,17 @@ def update_corpus_follower_role(corpus_id, follower_id):
 
 
 @bp.route('/<hashid:corpus_id>/followers/<hashid:follower_id>', methods=['DELETE'])
+@corpus_follower_permission_required('REMOVE_FOLLOWER')
 @content_negotiation(produces='application/json')
 def delete_corpus_follower(corpus_id, follower_id):
-    corpus = Corpus.query.get_or_404(corpus_id)
-    follower = User.query.get_or_404(follower_id)
-    if not (corpus.user == current_user or follower == current_user or current_user.is_administrator()):
-        abort(403)
-    if not follower.is_following_corpus(corpus):
-        abort(409)
-    follower.unfollow_corpus(corpus)
+    cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=follower_id).first_or_404()
+    cfa.follower.unfollow_corpus(cfa.corpus)
     db.session.commit()
     response_data = {
-        'message': \
-            f'"{follower.username}" is not following "{corpus.title}" anymore',
+        'message': (
+            f'"{cfa.follower.username}" is not following '
+            f'"{cfa.corpus.title}" anymore'
+        ),
         'category': 'corpus'
     }
     return response_data, 200
diff --git a/app/corpora/json_routes.py b/app/corpora/json_routes.py
index 4da60341f36056ef75d00db04a23f4029ad71e29..e73281ed6efc8efe704ae1882fc302ea9a3ac37b 100644
--- a/app/corpora/json_routes.py
+++ b/app/corpora/json_routes.py
@@ -2,11 +2,11 @@ from datetime import datetime
 from flask import abort, current_app, request, url_for
 from flask_login import current_user
 from threading import Thread
-from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required
-from app import db, hashids
+from app import db
 from app.decorators import content_negotiation
 from app.models import Corpus, CorpusFollowerRole
 from . import bp
+from .decorators import corpus_follower_permission_required, corpus_owner_or_admin_required
 
 
 @bp.route('/<hashid:corpus_id>', methods=['DELETE'])
@@ -58,10 +58,9 @@ def build_corpus(corpus_id):
 
 
 @bp.route('/<hashid:corpus_id>/generate-share-link', methods=['POST'])
-@corpus_follower_permission_required('GENERATE_SHARE_LINK')
+@corpus_follower_permission_required('ADD_FOLLOWER')
 @content_negotiation(consumes='application/json', produces='application/json')
 def generate_corpus_share_link(corpus_id):
-    corpus_hashid = hashids.encode(corpus_id)
     data = request.json
     if not isinstance(data, dict):
         abort(400)
@@ -75,7 +74,8 @@ def generate_corpus_share_link(corpus_id):
     cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
     if cfr is None:
         abort(400)
-    token = current_user.generate_follow_corpus_token(corpus_hashid, role_name, expiration_date)
+    corpus = Corpus.query.get_or_404(corpus_id)
+    token = current_user.generate_follow_corpus_token(corpus.hashid, role_name, expiration_date)
     corpus_share_link = url_for(
         'corpora.follow_corpus',
         corpus_id=corpus_id,
diff --git a/app/corpora/routes.py b/app/corpora/routes.py
index ccb70760867e90000335e6f7bf4991823750b1f2..c42165dbdcaa4c2ee5ad59eb558d751465ee73df 100644
--- a/app/corpora/routes.py
+++ b/app/corpora/routes.py
@@ -1,7 +1,6 @@
 from flask import abort, flash, redirect, render_template, url_for
 from flask_breadcrumbs import register_breadcrumb
 from flask_login import current_user
-from .decorators import corpus_follower_permission_required
 from app import db
 from app.models import (
     Corpus,
@@ -10,6 +9,7 @@ from app.models import (
     User
 )
 from . import bp
+from .decorators import corpus_follower_permission_required
 from .forms import CreateCorpusForm
 from .utils import (
     corpus_endpoint_arguments_constructor as corpus_eac,
@@ -73,8 +73,8 @@ def corpus(corpus_id):
 
 
 @bp.route('/<hashid:corpus_id>/analyse')
-@register_breadcrumb(bp, '.entity.analyse', 'Analyse', endpoint_arguments_constructor=corpus_eac)
 @corpus_follower_permission_required('VIEW')
+@register_breadcrumb(bp, '.entity.analyse', 'Analyse', endpoint_arguments_constructor=corpus_eac)
 def analyse_corpus(corpus_id):
     corpus = Corpus.query.get_or_404(corpus_id)
     return render_template(
@@ -101,6 +101,7 @@ def import_corpus():
 
 
 @bp.route('/<hashid:corpus_id>/export')
+@corpus_follower_permission_required('VIEW')
 @register_breadcrumb(bp, '.entity.export', 'Export', endpoint_arguments_constructor=corpus_eac)
 def export_corpus(corpus_id):
     abort(503)
diff --git a/app/models.py b/app/models.py
index 5765807452dcd1530a6f46ff0e0b543cda551559..07d648ad3aa9d10840c06315e35fda3988e44763 100644
--- a/app/models.py
+++ b/app/models.py
@@ -116,9 +116,9 @@ class CorpusFollowerPermission(IntEnum):
     ADD_CORPUS_FILE = 2
     UPDATE_CORPUS_FILE = 4
     REMOVE_CORPUS_FILE = 8
-    GENERATE_SHARE_LINK = 16
-    REMOVE_FOLLOWER = 32
-    UPDATE_FOLLOWER = 64
+    ADD_FOLLOWER = 16
+    UPDATE_FOLLOWER = 32
+    REMOVE_FOLLOWER = 64
 
     @staticmethod
     def get(corpus_follower_permission: Union['CorpusFollowerPermission', int, str]) -> 'CorpusFollowerPermission':
@@ -448,9 +448,9 @@ class CorpusFollowerRole(HashidMixin, db.Model):
                 CorpusFollowerPermission.ADD_CORPUS_FILE,
                 CorpusFollowerPermission.UPDATE_CORPUS_FILE,
                 CorpusFollowerPermission.REMOVE_CORPUS_FILE,
-                CorpusFollowerPermission.GENERATE_SHARE_LINK,
-                CorpusFollowerPermission.REMOVE_FOLLOWER,
-                CorpusFollowerPermission.UPDATE_FOLLOWER
+                CorpusFollowerPermission.ADD_FOLLOWER,
+                CorpusFollowerPermission.UPDATE_FOLLOWER,
+                CorpusFollowerPermission.REMOVE_FOLLOWER
             ]
         }
         default_role_name = 'Viewer'