diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c2a9e2cfa662e0705c27ac709003883aae431b54..37ce94292fc02e9530fb1ea996d246a415cd6eee 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -26,12 +26,16 @@ Fixed
 * Due to a merge error, the once removed account menu in the sidenav appeared again.
 * Scheduled notifications were shown on dashboard before time.
 * Remove broken notifications menu item in favor of item next to account menu.
+* [OAuth2] Resources which are protected with client credentials 
+  allowed access if no scopes were allowed.
+* The site logo could overlap with the menu for logos with an unexpected aspect ratio.
 
 Changed
 ~~~~~~~
 
 * [Dev] ActionForm now checks permissions on objects before executing
 * [Dev] ActionForm now returns a proper return value from the executed action
+* Pin version of javascript dependencies
 
 2.8.1`_ - 2022-03-13
 --------------------
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 3fdcbd99316f7f70f6bf232e45509715735e428c..60facaa04a1430f4e5fc756a4be31deee2a4b532 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -561,20 +561,20 @@ MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media"))
 NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
 
 YARN_INSTALLED_APPS = [
-    "cleave.js",
-    "@fontsource/roboto",
-    "jquery",
-    "@materializecss/materialize",
-    "material-design-icons-iconfont",
-    "select2",
-    "select2-materialize",
-    "paper-css",
-    "jquery-sortablejs",
-    "sortablejs",
-    "@sentry/tracing",
-    "luxon",
-    "@iconify/iconify",
-    "@iconify/json",
+    "cleave.js@^1.6.0",
+    "@fontsource/roboto@^4.5.5",
+    "jquery@^3.6.0",
+    "@materializecss/materialize@~1.0.0",
+    "material-design-icons-iconfont@^6.6.0",
+    "select2@^4.1.0-rc.0",
+    "select2-materialize@^0.1.8",
+    "paper-css@^0.4.1",
+    "jquery-sortablejs@^1.0.1",
+    "sortablejs@^1.15.0",
+    "@sentry/tracing@^6.19.6",
+    "luxon@^2.3.2",
+    "@iconify/iconify@^2.2.1",
+    "@iconify/json@^2.1.30",
 ]
 
 merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True)
diff --git a/aleksis/core/static/public/style.scss b/aleksis/core/static/public/style.scss
index 5a35561c389f5656fd573e8f9b698ae456d42ad5..b6b4ce7831311178d8044ebb74f2d37b749ebfee 100644
--- a/aleksis/core/static/public/style.scss
+++ b/aleksis/core/static/public/style.scss
@@ -115,17 +115,15 @@ main {
 /* SIDENAV */
 /***********/
 
-ul.sidenav.sidenav-fixed li.logo {
-  margin-top: 32px;
-  margin-bottom: 50px;
-}
-
 ul.sidenav.sidenav-fixed .brand-logo {
   margin: 0;
+  height: 100%;
 }
 
 .logo img {
-  width: 250px;
+  width: 268px;
+  margin-block: 1em;
+  vertical-align: middle;
 }
 
 .sidenav a:not(.collapsible-header) {
diff --git a/aleksis/core/tests/regression/test_regression.py b/aleksis/core/tests/regression/test_regression.py
index b9d33e6981312817324b92d4fe67176b96763cda..c2417a56ebd02c015e84f18401d39cfc582b4252 100644
--- a/aleksis/core/tests/regression/test_regression.py
+++ b/aleksis/core/tests/regression/test_regression.py
@@ -1,10 +1,16 @@
+import base64
+
 from django.contrib.auth import get_user_model
 
 import pytest
 
-from aleksis.core.models import Group, Person
+from aleksis.core.models import Group, OAuthApplication, Person
 
 pytestmark = pytest.mark.django_db
+from django.http import HttpResponse
+from django.test import override_settings
+from django.urls import path, reverse
+from django.views.generic import View
 
 
 def test_all_settigns_registered():
@@ -82,3 +88,61 @@ def test_reassign_user_to_person():
     assert user2.groups.count() == 1
     assert user1.groups.first().name == "Group 2"
     assert user2.groups.first().name == "Group 1"
+
+
+@override_settings(ROOT_URLCONF="aleksis.core.tests.regression.view_oauth")
+def test_no_access_oauth2_client_credentials_without_allowed_scopes(client):
+    """Tests that ClientProtectedResourceMixin doesn't allow access if no allowed scopes are set.
+
+    https://edugit.org/AlekSIS/official/AlekSIS-Core/-/issues/688
+    """
+
+    wrong_application = OAuthApplication.objects.create(
+        name="Test Application",
+        allowed_scopes=[],
+        authorization_grant_type=OAuthApplication.GRANT_CLIENT_CREDENTIALS,
+        client_type=OAuthApplication.CLIENT_CONFIDENTIAL,
+        redirect_uris=["http://localhost:8000/"],
+    )
+    wrong_application_2 = OAuthApplication.objects.create(
+        name="Test Application",
+        allowed_scopes=["read"],
+        authorization_grant_type=OAuthApplication.GRANT_CLIENT_CREDENTIALS,
+        client_type=OAuthApplication.CLIENT_CONFIDENTIAL,
+        redirect_uris=["http://localhost:8000/"],
+    )
+    correct_application = OAuthApplication.objects.create(
+        name="Test Application",
+        allowed_scopes=["write"],
+        authorization_grant_type=OAuthApplication.GRANT_CLIENT_CREDENTIALS,
+        client_type=OAuthApplication.CLIENT_CONFIDENTIAL,
+        redirect_uris=["http://localhost:8000/"],
+    )
+
+    url = reverse("client_protected_resource_mixin_test")
+    auth_header = (
+        "Basic "
+        + base64.b64encode(
+            f"{wrong_application.client_id}:{wrong_application.client_secret}".encode()
+        ).decode()
+    )
+    r = client.get(url, HTTP_AUTHORIZATION=auth_header)
+    assert r.status_code == 403
+
+    auth_header = (
+        "Basic "
+        + base64.b64encode(
+            f"{wrong_application_2.client_id}:{wrong_application_2.client_secret}".encode()
+        ).decode()
+    )
+    r = client.get(url, HTTP_AUTHORIZATION=auth_header)
+    assert r.status_code == 403
+
+    auth_header = (
+        "Basic "
+        + base64.b64encode(
+            f"{correct_application.client_id}:{correct_application.client_secret}".encode()
+        ).decode()
+    )
+    r = client.get(url, HTTP_AUTHORIZATION=auth_header)
+    assert r.status_code == 200
diff --git a/aleksis/core/tests/regression/view_oauth.py b/aleksis/core/tests/regression/view_oauth.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5671208e127e79c30d7bb179bbf2e82a35616fb
--- /dev/null
+++ b/aleksis/core/tests/regression/view_oauth.py
@@ -0,0 +1,24 @@
+from django.http import HttpResponse
+from django.test import override_settings
+from django.urls import path, reverse
+from django.views.generic import View
+
+from oauth2_provider.views.mixins import ScopedResourceMixin
+
+from aleksis.core.util.auth_helpers import ClientProtectedResourceMixin
+
+
+class TestViewClientProtectedResourceMixin(ScopedResourceMixin, ClientProtectedResourceMixin, View):
+    required_scopes = ["write"]
+
+    def get(self, request):
+        return HttpResponse("OK")
+
+
+urlpatterns = [
+    path(
+        "client_protected_resource_mixin_test/",
+        TestViewClientProtectedResourceMixin.as_view(),
+        name="client_protected_resource_mixin_test",
+    ),
+]
diff --git a/aleksis/core/util/auth_helpers.py b/aleksis/core/util/auth_helpers.py
index 6edfac83373882d077d0a588d822fd3a0d0cc9b4..ca80aeae4a59ac069023465559d599f929bab6d8 100644
--- a/aleksis/core/util/auth_helpers.py
+++ b/aleksis/core/util/auth_helpers.py
@@ -134,6 +134,10 @@ class ClientProtectedResourceMixin(_ClientProtectedResourceMixin):
         # Verify scopes of configured application
         # The OAuth request was enriched with a reference to the Application when using the
         #  validator above.
+        if not oauth_request.client.allowed_scopes:
+            # If there are no allowed scopes, the client is not allowed to access this resource
+            return False
+
         required_scopes = set(self.get_scopes() or [])
         allowed_scopes = set(AppScopes().get_available_scopes(oauth_request.client) or [])
         return required_scopes.issubset(allowed_scopes)