diff --git a/deps/ldapalchemy b/deps/ldapalchemy
index db135ec7a4940a384e82a4500cfb0e73ecd2f557..d2c133381a8e536ee433e5ab305fc24146c0feb9 160000
--- a/deps/ldapalchemy
+++ b/deps/ldapalchemy
@@ -1 +1 @@
-Subproject commit db135ec7a4940a384e82a4500cfb0e73ecd2f557
+Subproject commit d2c133381a8e536ee433e5ab305fc24146c0feb9
diff --git a/uffd/ldap.py b/uffd/ldap.py
index 237271e7e004d7103f3b4966a5b4a46f98b5d297..67e627c9eb99787dd856997afe20ae8498d3ba2c 100644
--- a/uffd/ldap.py
+++ b/uffd/ldap.py
@@ -1,12 +1,30 @@
-from flask import current_app, request
+from flask import current_app, request, abort
 
 import ldap3
 
 from ldapalchemy import LDAPMapper, LDAPCommitError # pylint: disable=unused-import
+from ldapalchemy.model import Query
+
+class FlaskQuery(Query):
+	def get_or_404(self, dn):
+		res = self.get(dn)
+		if res is None:
+			abort(404)
+		return res
+
+	def first_or_404(self):
+		res = self.first()
+		if res is None:
+			abort(404)
+		return res
 
 class FlaskLDAPMapper(LDAPMapper):
 	def __init__(self):
 		super().__init__()
+		class Model(self.Model):
+			query_class = FlaskQuery
+
+		self.Model = Model # pylint: disable=invalid-name
 
 	@property
 	def session(self):
diff --git a/uffd/mail/views.py b/uffd/mail/views.py
index d7b68782650127a14c4c9033df88fe08db0c1462..2aaebf559f44465c17ac6bde67796047b48d86e9 100644
--- a/uffd/mail/views.py
+++ b/uffd/mail/views.py
@@ -28,7 +28,7 @@ def index():
 def show(uid=None):
 	mail = Mail()
 	if uid is not None:
-		mail = Mail.query.filter_by(uid=uid)[0]
+		mail = Mail.query.filter_by(uid=uid).first_or_404()
 	return render_template('mail.html', mail=mail)
 
 @bp.route("/<uid>/update", methods=['POST'])
@@ -36,7 +36,7 @@ def show(uid=None):
 @csrf_protect(blueprint=bp)
 def update(uid=None):
 	if uid is not None:
-		mail = Mail.query.filter_by(uid=uid)[0]
+		mail = Mail.query.filter_by(uid=uid).first_or_404()
 	else:
 		mail = Mail(uid=request.form.get('mail-uid'))
 	mail.receivers = request.form.get('mail-receivers', '').splitlines()
@@ -49,7 +49,7 @@ def update(uid=None):
 @bp.route("/<uid>/del")
 @csrf_protect(blueprint=bp)
 def delete(uid):
-	mail = Mail.query.filter_by(uid=uid)[0]
+	mail = Mail.query.filter_by(uid=uid).first_or_404()
 	ldap.session.delete(mail)
 	ldap.session.commit()
 	flash('Deleted mail mapping.')
diff --git a/uffd/mfa/models.py b/uffd/mfa/models.py
index 898798897e9fd50cffabc4ff450b97e2864854d0..e525a3782551efca3a51d14a71d4cc31f8c2d5f8 100644
--- a/uffd/mfa/models.py
+++ b/uffd/mfa/models.py
@@ -44,6 +44,7 @@ class MFAMethod(db.Model):
 class RecoveryCodeMethod(MFAMethod):
 	code_salt = Column('recovery_salt', String(64))
 	code_hash = Column('recovery_hash', String(256))
+	user = DBRelationship('dn', User, backref='mfa_recovery_codes')
 
 	__mapper_args__ = {
 		'polymorphic_identity': MFAType.RECOVERY_CODE
@@ -76,6 +77,7 @@ def _hotp(counter, key, digits=6):
 
 class TOTPMethod(MFAMethod):
 	key = Column('totp_key', String(64))
+	user = DBRelationship('dn', User, backref='mfa_totp_methods')
 
 	__mapper_args__ = {
 		'polymorphic_identity': MFAType.TOTP
@@ -124,6 +126,7 @@ class TOTPMethod(MFAMethod):
 
 class WebauthnMethod(MFAMethod):
 	_cred = Column('webauthn_cred', Text())
+	user = DBRelationship('dn', User, backref='mfa_webauthn_methods')
 
 	__mapper_args__ = {
 		'polymorphic_identity': MFAType.WEBAUTHN
diff --git a/uffd/mfa/views.py b/uffd/mfa/views.py
index 3ce9a9f0f5dcb739e7732ad1db4b586415910503..f7a3e239c6394471543baf940dbbaa9c01de45f4 100644
--- a/uffd/mfa/views.py
+++ b/uffd/mfa/views.py
@@ -46,7 +46,7 @@ def admin_disable(uid):
 	if not get_current_user().is_in_group(current_app.config['ACL_ADMIN_GROUP']):
 		flash('Access denied')
 		return redirect(url_for('index'))
-	user = User.query.filter_by(uid=uid)[0]
+	user = User.query.filter_by(uid=uid).one()
 	MFAMethod.query.filter_by(dn=user.dn).delete()
 	db.session.commit()
 	flash('Two-factor authentication was reset')
diff --git a/uffd/selfservice/views.py b/uffd/selfservice/views.py
index 4ba815143e6aec26d1732db69d2b9c2310ee3ea8..40dc49e010832ed5c5d1d1add343842ad6f2eb61 100644
--- a/uffd/selfservice/views.py
+++ b/uffd/selfservice/views.py
@@ -67,7 +67,7 @@ def forgot_password():
 	reset_ratelimit.log(loginname+'/'+mail)
 	host_ratelimit.log()
 	flash("We sent a mail to this users mail address if you entered the correct mail and login name combination")
-	user = (User.query.filter_by(loginname=loginname) or [None])[0]
+	user = User.query.filter_by(loginname=loginname).one_or_none()
 	if user and user.mail == mail:
 		send_passwordreset(user)
 	return redirect(url_for('session.login'))
@@ -89,7 +89,7 @@ def token_password(token):
 	if not request.values['password1'] == request.values['password2']:
 		flash('Passwords do not match, please try again.')
 		return render_template('set_password.html', token=token)
-	user = User.query.filter_by(loginname=dbtoken.loginname)[0]
+	user = User.query.filter_by(loginname=dbtoken.loginname).one()
 	if not user.set_password(request.values['password1']):
 		flash('Password ist not valid, please try again.')
 		return render_template('set_password.html', token=token)
@@ -110,7 +110,7 @@ def token_mail(token):
 			db.session.commit()
 		return redirect(url_for('selfservice.index'))
 
-	user = User.query.filter_by(loginname=dbtoken.loginname)[0]
+	user = User.query.filter_by(loginname=dbtoken.loginname).one()
 	user.set_mail(dbtoken.newmail)
 	flash('New mail set')
 	db.session.delete(dbtoken)
@@ -129,7 +129,7 @@ def send_mail_verification(loginname, newmail):
 	db.session.add(token)
 	db.session.commit()
 
-	user = User.query.filter_by(loginname=loginname)[0]
+	user = User.query.filter_by(loginname=loginname).one()
 
 	msg = EmailMessage()
 	msg.set_content(render_template('mailverification.mail.txt', user=user, token=token.token))
diff --git a/uffd/user/views_group.py b/uffd/user/views_group.py
index d4e777417eb0891bee30972eee303f587ac59f11..22fdd9e9d9def67a36fb5c18e2351878e61ebc0f 100644
--- a/uffd/user/views_group.py
+++ b/uffd/user/views_group.py
@@ -23,4 +23,4 @@ def index():
 
 @bp.route("/<int:gid>")
 def show(gid):
-	return render_template('group.html', group=Group.query.filter_by(gid=gid)[0])
+	return render_template('group.html', group=Group.query.filter_by(gid=gid).first_or_404())
diff --git a/uffd/user/views_user.py b/uffd/user/views_user.py
index 18b380b021521813abcc049682a7abc3705456ca..78ad85c26074927d79dda1fbb123f0f972125690 100644
--- a/uffd/user/views_user.py
+++ b/uffd/user/views_user.py
@@ -32,7 +32,7 @@ def index():
 @bp.route("/<int:uid>")
 @bp.route("/new")
 def show(uid=None):
-	user = User() if uid is None else User.query.filter_by(uid=uid)[0]
+	user = User() if uid is None else User.query.filter_by(uid=uid).first_or_404()
 	return render_template('user.html', user=user, roles=Role.query.all())
 
 @bp.route("/<int:uid>/update", methods=['POST'])
@@ -45,7 +45,7 @@ def update(uid=None):
 			flash('Login name does not meet requirements')
 			return redirect(url_for('user.show'))
 	else:
-		user = User.query.filter_by(uid=uid)[0]
+		user = User.query.filter_by(uid=uid).first_or_404()
 	if not user.set_mail(request.form['mail']):
 		flash('Mail is invalid')
 		return redirect(url_for('user.show', uid=uid))
@@ -74,7 +74,7 @@ def update(uid=None):
 @bp.route("/<int:uid>/del")
 @csrf_protect(blueprint=bp)
 def delete(uid):
-	user = User.query.filter_by(uid=uid)[0]
+	user = User.query.filter_by(uid=uid).first_or_404()
 	user.roles.clear()
 	ldap.session.delete(user)
 	ldap.session.commit()