diff --git a/README.md b/README.md
index 45eeac10b3bc7ea60db19a0ff78777098f812c50..859d4e16ccbc75f9bc331c0b77a7cd4bbfe534ba 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,9 @@ A web service to manage LDAP users, groups and permissions.
 - python3-flask-oauthlib
 - git (cli utility, musst be in path)
 
+Some of the dependencies (especially fido2 and flask-oauthlib) changed their API in recent versions, so make sure to install the versions from Debian Buster.
+You can also use virtualenv with the supplied `requirements.txt`.
+
 ## development
 
 During development, you may want to enable LDAP mocking, as you otherwise need to have access to an actual LDAP server with the required schema.
@@ -46,6 +49,7 @@ The userinfo endpoint returns json data with the following structure:
   "name": "Test User",
   "nickname": "testuser"
   "email": "testuser@example.com",
+  "ldap_dn": "uid=testuser,ou=users,dc=example,dc=com",
   "groups": [
     "uffd_access",
     "users"
diff --git a/requirements.txt b/requirements.txt
index 347cf2a9073124b259908bfa7bb8f851ce0af5ef..e5d9378ef3c596fc10d4a4efae409a99ef76774a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,33 @@ Flask-SQLAlchemy==2.1
 qrcode==6.1
 fido2==0.5.0
 Flask-OAuthlib==0.9.5
+
+# The main dependencies on their own lead to version collisions and pip is
+# not very good at resolving them, so we pin the versions from Debian Buster
+# for all dependencies.
+certifi==2018.8.24
+cffi==1.12.2
+chardet==3.0.4
+click==7.0
+cryptography==2.6.1
+idna==2.6
+itsdangerous==0.24
+Jinja2==2.10
+MarkupSafe==1.1.0
+oauthlib==2.1.0
+pyasn1==0.4.2
+pycparser==2.19
+requests==2.21.0
+requests-oauthlib==1.0.0
+six==1.12.0
+SQLAlchemy==1.2.18
+urllib3==1.24.1
+Werkzeug==0.14.1
+
+# Testing
+pytest==3.10.1
+atomicwrites==1.1.5
+attrs==18.2.0
+more-itertools==4.2.0
+pluggy==0.8.0
+py==1.7.0
diff --git a/runTests.py b/runTests.py
deleted file mode 100755
index fabbb5e767c8f2bed38e1f4227e0e2f92185c347..0000000000000000000000000000000000000000
--- a/runTests.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python3
-import unittest
-import os
-from src import server
-
-def setUp():
-	server.app.testing = True
-
-def tearDown():
-	os.unlink(server.app.config['SQLITE_DB'])
-
-if __name__ == '__main__':
-	setUp()
-	try:
-		suite = unittest.defaultTestLoader.discover('./tests/', pattern="*")
-		unittest.TextTestRunner(verbosity=2, failfast=True).run(suite)
-	finally:
-		tearDown()
-
diff --git a/tests/test_oauth2.py b/tests/test_oauth2.py
index c5d024eca7f63b9fb58538836d99bba8efcf66f9..1d9751b7faa8494c475948868b9af773217cb81f 100644
--- a/tests/test_oauth2.py
+++ b/tests/test_oauth2.py
@@ -87,6 +87,10 @@ class TestViews(UffdTestCase):
 			data={'loginname': 'testuser', 'password': 'userpassword'}, follow_redirects=True)
 		state = 'teststate'
 		r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state=state, redirect_uri='http://localhost:5009/callback'), follow_redirects=False)
+		while True:
+			if r.status_code != 302 or r.location.startswith('http://localhost:5009/callback'):
+				break
+			r = self.client.get(r.location, follow_redirects=False)
 		self.assertEqual(r.status_code, 302)
 		self.assertTrue(r.location.startswith('http://localhost:5009/callback'))
 		args = parse_qs(urlparse(r.location).query)
diff --git a/uffd/oauth2/views.py b/uffd/oauth2/views.py
index 011a956acc0c7b6434758049d83fbf25930012ce..a054b0d1ad25d1326395a8d5b42505f6c3449c8e 100644
--- a/uffd/oauth2/views.py
+++ b/uffd/oauth2/views.py
@@ -3,7 +3,6 @@ import functools
 import urllib.parse
 
 from flask import Blueprint, request, jsonify, render_template, session, redirect
-from werkzeug.datastructures import ImmutableMultiDict
 
 from flask_oauthlib.provider import OAuth2Provider
 
@@ -101,13 +100,15 @@ def token():
 @oauth.require_oauth('profile')
 def userinfo():
 	user = request.oauth.user
+	# We once exposed the entryUUID here as "ldap_uuid" until realising that it
+	# can (and does!) change randomly and is therefore entirely useless as an
+	# indentifier.
 	return jsonify(
 		id=user.uid,
 		name=user.displayname,
 		nickname=user.loginname,
 		email=user.mail,
 		ldap_dn=user.dn,
-		ldap_uuid=user.uuid,
 		groups=[group.name for group in user.get_groups()]
 	)
 
diff --git a/uffd/selfservice/templates/newuser.mail.txt b/uffd/selfservice/templates/newuser.mail.txt
index afae818151964b3c1882bf6f5923b68d273dfcd2..7ef8a46964ae799078e8e2b0c108bd78cf9b235f 100644
--- a/uffd/selfservice/templates/newuser.mail.txt
+++ b/uffd/selfservice/templates/newuser.mail.txt
@@ -4,7 +4,9 @@ welcome to the CCCV infrastructure.
 An account was created for you, visit this url to set your password: {{ url_for('selfservice.token_password', token=token, _external=True) }}
 **Please note this link is only valid for 48h**
 
+You can find more information here: https://docs.cccv.de/
+
 If you have no idea why someone would create an account for you to be used for the next CCC event organization, please contact it@cccv.de.
 
 Kind regards,
-uffd
+uffd
\ No newline at end of file
diff --git a/uffd/user/models.py b/uffd/user/models.py
index 277e694260f9c20d83a953db09d36d09ec71a936..5158b32305162f164178dee5618d9e21c44b9c6e 100644
--- a/uffd/user/models.py
+++ b/uffd/user/models.py
@@ -7,14 +7,13 @@ from flask import current_app
 from uffd import ldap
 
 class User():
-	def __init__(self, uid=None, loginname='', displayname='', mail='', groups=None, dn=None, uuid=None): # pylint: disable=too-many-arguments
+	def __init__(self, uid=None, loginname='', displayname='', mail='', groups=None, dn=None):
 		self.uid = uid
 		self.loginname = loginname
 		self.displayname = displayname
 		self.mail = mail
 		self.newpassword = None
 		self.dn = dn
-		self.uuid = uuid
 
 		self.groups_ldap = groups or []
 		self.initial_groups_ldap = groups or []
@@ -30,12 +29,6 @@ class User():
 				mail=ldapobject['mail'].value,
 				groups=ldap.get_ldap_array_attribute_safe(ldapobject, 'memberOf'),
 				dn=ldapobject.entry_dn,
-				# The LDAP mock does not generate UUIDs for newly created LDAP objects,
-				# so we use a dummy value if the attribute is missing (only for testing!)
-				uuid=ldapobject['entryUUID'].value \
-					if 'entryUUID' in ldapobject.entry_attributes_as_dict \
-					or not current_app.config.get('LDAP_SERVICE_MOCK', False) \
-					else '00000000-0000-0000-0000-000000000000'
 			)
 
 	@classmethod
diff --git a/uffd/user/templates/user.html b/uffd/user/templates/user.html
index 1411c9f12269143f62b7c80bfe05704ca5337651..3458d375178a0385375c6f218277ca5902c0b9fa 100644
--- a/uffd/user/templates/user.html
+++ b/uffd/user/templates/user.html
@@ -8,7 +8,7 @@
 		<a href="{{ url_for("user.index") }}" class="btn btn-secondary">Cancel</a>
 		{% if user.uid %}
 			<a href="{{ url_for("mfa.admin_disable", uid=user.uid) }}" class="btn btn-secondary">Reset 2FA</a>
-			<a href="{{ url_for("user.delete", uid=user.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
+			<a href="{{ url_for("user.delete", uid=user.uid) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
 		{% else %}
 			<a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a>
 		{% endif %}