From ae437f70a3659485a4240d093fce58e49082af0a Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Tue, 4 Apr 2023 17:02:24 +0200
Subject: [PATCH] Several config and admin interface fixes

- Fixed issue with domains starting with `admin`
- Fixed issue with DUO not being enabled globally anymore (regression)
- Renamed `Ciphers` to `Entries` in overview
- Improved `ADMIN_TOKEN` description
- Updated jquery-slim and datatables

Resolves #3382
Resolves #3415
Resolves discussion on #3288
---
 .env.template                                 |   2 +
 src/api/web.rs                                |   4 +-
 src/config.rs                                 |   4 +-
 src/static/scripts/admin.css                  |   4 +-
 src/static/scripts/admin.js                   |  13 +-
 src/static/scripts/datatables.css             |  15 +-
 src/static/scripts/datatables.js              | 137 +++++++++++++-----
 ...ery-3.6.3.slim.js => jquery-3.6.4.slim.js} |  90 ++++--------
 src/static/templates/admin/organizations.hbs  |   4 +-
 src/static/templates/admin/users.hbs          |   4 +-
 10 files changed, 160 insertions(+), 117 deletions(-)
 rename src/static/scripts/{jquery-3.6.3.slim.js => jquery-3.6.4.slim.js} (98%)

diff --git a/.env.template b/.env.template
index 9d6f75a1..425741ae 100644
--- a/.env.template
+++ b/.env.template
@@ -264,6 +264,8 @@
 ## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
 ## If not set, the admin panel is disabled
 ## New Argon2 PHC string
+## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$`
+## Also, use single quotes (') instead of double quotes (") to enclose the string when needed
 # ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
 ## Old plain text string (Will generate warnings in favor of Argon2)
 # ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp
diff --git a/src/api/web.rs b/src/api/web.rs
index f7fbeec4..f447fb82 100644
--- a/src/api/web.rs
+++ b/src/api/web.rs
@@ -136,8 +136,8 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er
         "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
         "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
         "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
-        "jquery-3.6.3.slim.js" => {
-            Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.3.slim.js")))
+        "jquery-3.6.4.slim.js" => {
+            Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.4.slim.js")))
         }
         _ => err!(format!("Static file not found: {filename}")),
     }
diff --git a/src/config.rs b/src/config.rs
index 6ed19a79..2a42b5f5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -476,7 +476,7 @@ make_config! {
         /// provides unauthenticated access to potentially sensitive data.
         show_password_hint:     bool,   true,   def,    false;
 
-        /// Admin page token |> The token used to authenticate in this very same page. Changing it here won't deauthorize the current session
+        /// Admin token/Argon2 PHC |> The plain text token or Argon2 PHC string used to authenticate in this very same page. Changing it here will not deauthorize the current session!
         admin_token:            Pass,   true,   option;
 
         /// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
@@ -603,7 +603,7 @@ make_config! {
     /// Global Duo settings (Note that users can override them)
     duo: _enable_duo {
         /// Enabled
-        _enable_duo:            bool,   true,   def,     false;
+        _enable_duo:            bool,   true,   def,     true;
         /// Integration Key
         duo_ikey:               String, true,   option;
         /// Secret Key
diff --git a/src/static/scripts/admin.css b/src/static/scripts/admin.css
index d700af3c..1db8d4c0 100644
--- a/src/static/scripts/admin.css
+++ b/src/static/scripts/admin.css
@@ -25,7 +25,7 @@ img {
     min-width: 85px;
     max-width: 85px;
 }
-#users-table .vw-ciphers, #orgs-table .vw-users, #orgs-table .vw-ciphers {
+#users-table .vw-entries, #orgs-table .vw-users, #orgs-table .vw-entries {
     min-width: 35px;
     max-width: 40px;
 }
@@ -53,4 +53,4 @@ img {
 }
 .vw-copy-toast {
     width: 15rem;
-}
\ No newline at end of file
+}
diff --git a/src/static/scripts/admin.js b/src/static/scripts/admin.js
index 7408c955..a9c19739 100644
--- a/src/static/scripts/admin.js
+++ b/src/static/scripts/admin.js
@@ -3,16 +3,17 @@
 /* exported BASE_URL, _post */
 
 function getBaseUrl() {
-    // If the base URL is `https://vaultwarden.example.com/base/path/`,
+    // If the base URL is `https://vaultwarden.example.com/base/path/admin/`,
     // `window.location.href` should have one of the following forms:
     //
-    // - `https://vaultwarden.example.com/base/path/`
-    // - `https://vaultwarden.example.com/base/path/#/some/route[?queryParam=...]`
+    // - `https://vaultwarden.example.com/base/path/admin`
+    // - `https://vaultwarden.example.com/base/path/admin/#/some/route[?queryParam=...]`
     //
     // We want to get to just `https://vaultwarden.example.com/base/path`.
-    const baseUrl = window.location.href;
-    const adminPos = baseUrl.indexOf("/admin");
-    return baseUrl.substring(0, adminPos != -1 ? adminPos : baseUrl.length);
+    const pathname = window.location.pathname;
+    const adminPos = pathname.indexOf("/admin");
+    const newPathname = pathname.substring(0, adminPos != -1 ? adminPos : pathname.length);
+    return `${window.location.origin}${newPathname}`;
 }
 const BASE_URL = getBaseUrl();
 
diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css
index d22b2250..0dd6669c 100644
--- a/src/static/scripts/datatables.css
+++ b/src/static/scripts/datatables.css
@@ -4,10 +4,10 @@
  *
  * To rebuild or modify this file with the latest versions of the included
  * software please visit:
- *   https://datatables.net/download/#bs5/dt-1.13.2
+ *   https://datatables.net/download/#bs5/dt-1.13.4
  *
  * Included libraries:
- *   DataTables 1.13.2
+ *   DataTables 1.13.4
  */
 
 @charset "UTF-8";
@@ -79,6 +79,7 @@ table.dataTable thead > tr > td.sorting_asc_disabled:before,
 table.dataTable thead > tr > td.sorting_desc_disabled:before {
   bottom: 50%;
   content: "▲";
+  content: "▲"/"";
 }
 table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
 table.dataTable thead > tr > td.sorting:after,
@@ -88,6 +89,7 @@ table.dataTable thead > tr > td.sorting_asc_disabled:after,
 table.dataTable thead > tr > td.sorting_desc_disabled:after {
   top: 50%;
   content: "▼";
+  content: "▼"/"";
 }
 table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
 table.dataTable thead > tr > td.sorting_asc:before,
@@ -104,9 +106,9 @@ table.dataTable thead > tr > td:active {
   outline: none;
 }
 
-div.dataTables_scrollBody table.dataTable thead > tr > th:before, div.dataTables_scrollBody table.dataTable thead > tr > th:after,
-div.dataTables_scrollBody table.dataTable thead > tr > td:before,
-div.dataTables_scrollBody table.dataTable thead > tr > td:after {
+div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
+div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
+div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
   display: none;
 }
 
@@ -132,7 +134,8 @@ div.dataTables_processing > div:last-child > div {
   width: 13px;
   height: 13px;
   border-radius: 50%;
-  background: 13 110 253;
+  background: rgb(13, 110, 253);
+  background: rgb(var(--dt-row-selected));
   animation-timing-function: cubic-bezier(0, 1, 1, 0);
 }
 div.dataTables_processing > div:last-child > div:nth-child(1) {
diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js
index 9854358e..520de77c 100644
--- a/src/static/scripts/datatables.js
+++ b/src/static/scripts/datatables.js
@@ -4,20 +4,20 @@
  *
  * To rebuild or modify this file with the latest versions of the included
  * software please visit:
- *   https://datatables.net/download/#bs5/dt-1.13.2
+ *   https://datatables.net/download/#bs5/dt-1.13.4
  *
  * Included libraries:
- *   DataTables 1.13.2
+ *   DataTables 1.13.4
  */
 
-/*! DataTables 1.13.2
+/*! DataTables 1.13.4
  * ©2008-2023 SpryMedia Ltd - datatables.net/license
  */
 
 /**
  * @summary     DataTables
  * @description Paginate, search and order HTML tables
- * @version     1.13.2
+ * @version     1.13.4
  * @author      SpryMedia Ltd
  * @contact     www.datatables.net
  * @copyright   SpryMedia Ltd.
@@ -46,21 +46,28 @@
 	}
 	else if ( typeof exports === 'object' ) {
 		// CommonJS
-		module.exports = function (root, $) {
-			if ( ! root ) {
-				// CommonJS environments without a window global must pass a
-				// root. This will give an error otherwise
-				root = window;
-			}
+		// jQuery's factory checks for a global window - if it isn't present then it
+		// returns a factory function that expects the window object
+		var jq = require('jquery');
 
-			if ( ! $ ) {
-				$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
-					require('jquery') :
-					require('jquery')( root );
-			}
+		if (typeof window !== 'undefined') {
+			module.exports = function (root, $) {
+				if ( ! root ) {
+					// CommonJS environments without a window global must pass a
+					// root. This will give an error otherwise
+					root = window;
+				}
 
-			return factory( $, root, root.document );
-		};
+				if ( ! $ ) {
+					$ = jq( root );
+				}
+
+				return factory( $, root, root.document );
+			};
+		}
+		else {
+			return factory( jq, window, window.document );
+		}
 	}
 	else {
 		// Browser
@@ -73,6 +80,12 @@
 	
 	var DataTable = function ( selector, options )
 	{
+		// Check if called with a window or jQuery object for DOM less applications
+		// This is for backwards compatibility
+		if (DataTable.factory(selector, options)) {
+			return DataTable;
+		}
+	
 		// When creating with `new`, create a new DataTable, returning the API instance
 		if (this instanceof DataTable) {
 			return $(selector).DataTable(options);
@@ -1177,6 +1190,7 @@
 								type:   sort !== null   ? i+'.@data-'+sort   : undefined,
 								filter: filter !== null ? i+'.@data-'+filter : undefined
 							};
+							col._isArrayHost = true;
 			
 							_fnColumnOptions( oSettings, i );
 						}
@@ -2365,7 +2379,7 @@
 	
 		// Indicate if DataTables should read DOM data as an object or array
 		// Used in _fnGetRowElements
-		if ( typeof mDataSrc !== 'number' ) {
+		if ( typeof mDataSrc !== 'number' && ! oCol._isArrayHost ) {
 			oSettings._rowReadObject = true;
 		}
 	
@@ -5119,7 +5133,8 @@
 	{
 		return $('<div/>', {
 				'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
-				'class': settings.oClasses.sProcessing
+				'class': settings.oClasses.sProcessing,
+				'role': 'status'
 			} )
 			.html( settings.oLanguage.sProcessing )
 			.append('<div><div></div><div></div><div></div><div></div></div>')
@@ -9367,6 +9382,48 @@
 	
 	
 	
+	/**
+	 * Set the jQuery or window object to be used by DataTables
+	 *
+	 * @param {*} module Library / container object
+	 * @param {string} type Library or container type `lib` or `win`.
+	 */
+	DataTable.use = function (module, type) {
+		if (type === 'lib' || module.fn) {
+			$ = module;
+		}
+		else if (type == 'win' || module.document) {
+			window = module;
+			document = module.document;
+		}
+	}
+	
+	/**
+	 * CommonJS factory function pass through. This will check if the arguments
+	 * given are a window object or a jQuery object. If so they are set
+	 * accordingly.
+	 * @param {*} root Window
+	 * @param {*} jq jQUery
+	 * @returns {boolean} Indicator
+	 */
+	DataTable.factory = function (root, jq) {
+		var is = false;
+	
+		// Test if the first parameter is a window object
+		if (root && root.document) {
+			window = root;
+			document = root.document;
+		}
+	
+		// Test if the second parameter is a jQuery object
+		if (jq && jq.fn && jq.fn.jquery) {
+			$ = jq;
+			is = true;
+		}
+	
+		return is;
+	}
+	
 	/**
 	 * Provide a common method for plug-ins to check the version of DataTables being
 	 * used, in order to ensure compatibility.
@@ -9708,7 +9765,7 @@
 	 *  @type string
 	 *  @default Version number
 	 */
-	DataTable.version = "1.13.2";
+	DataTable.version = "1.13.4";
 	
 	/**
 	 * Private data store, containing all of the settings objects that are
@@ -14132,7 +14189,7 @@
 		 *
 		 *  @type string
 		 */
-		build:"bs5/dt-1.13.2",
+		build:"bs5/dt-1.13.4",
 	
 	
 		/**
@@ -15654,25 +15711,33 @@
 	}
 	else if ( typeof exports === 'object' ) {
 		// CommonJS
-		module.exports = function (root, $) {
-			if ( ! root ) {
-				// CommonJS environments without a window global must pass a
-				// root. This will give an error otherwise
-				root = window;
-			}
-
-			if ( ! $ ) {
-				$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
-					require('jquery') :
-					require('jquery')( root );
-			}
-
+		var jq = require('jquery');
+		var cjsRequires = function (root, $) {
 			if ( ! $.fn.dataTable ) {
 				require('datatables.net')(root, $);
 			}
-
-			return factory( $, root, root.document );
 		};
+
+		if (typeof window !== 'undefined') {
+			module.exports = function (root, $) {
+				if ( ! root ) {
+					// CommonJS environments without a window global must pass a
+					// root. This will give an error otherwise
+					root = window;
+				}
+
+				if ( ! $ ) {
+					$ = jq( root );
+				}
+
+				cjsRequires( root, $ );
+				return factory( $, root, root.document );
+			};
+		}
+		else {
+			cjsRequires( window, jq );
+			module.exports = factory( jq, window, window.document );
+		}
 	}
 	else {
 		// Browser
diff --git a/src/static/scripts/jquery-3.6.3.slim.js b/src/static/scripts/jquery-3.6.4.slim.js
similarity index 98%
rename from src/static/scripts/jquery-3.6.3.slim.js
rename to src/static/scripts/jquery-3.6.4.slim.js
index d7e1a94c..edf6ce9c 100644
--- a/src/static/scripts/jquery-3.6.3.slim.js
+++ b/src/static/scripts/jquery-3.6.4.slim.js
@@ -1,5 +1,5 @@
 /*!
- * jQuery JavaScript Library v3.6.3 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
+ * jQuery JavaScript Library v3.6.4 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
  * https://jquery.com/
  *
  * Includes Sizzle.js
@@ -9,7 +9,7 @@
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2022-12-20T21:28Z
+ * Date: 2023-03-08T15:29Z
  */
 ( function( global, factory ) {
 
@@ -151,7 +151,7 @@ function toType( obj ) {
 
 
 var
-	version = "3.6.3 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
+	version = "3.6.4 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
 
 	// Define a local copy of jQuery
 	jQuery = function( selector, context ) {
@@ -522,14 +522,14 @@ function isArrayLike( obj ) {
 }
 var Sizzle =
 /*!
- * Sizzle CSS Selector Engine v2.3.9
+ * Sizzle CSS Selector Engine v2.3.10
  * https://sizzlejs.com/
  *
  * Copyright JS Foundation and other contributors
  * Released under the MIT license
  * https://js.foundation/
  *
- * Date: 2022-12-19
+ * Date: 2023-02-14
  */
 ( function( window ) {
 var i,
@@ -633,7 +633,7 @@ var i,
 		whitespace + "+$", "g" ),
 
 	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
-	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
+	rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
 		"*" ),
 	rdescend = new RegExp( whitespace + "|>" ),
 
@@ -850,7 +850,7 @@ function Sizzle( selector, context, results, seed ) {
 				// as such selectors are not recognized by querySelectorAll.
 				// Thanks to Andrew Dupont for this technique.
 				if ( nodeType === 1 &&
-					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
+					( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
 
 					// Expand context for sibling selectors
 					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
@@ -879,27 +879,6 @@ function Sizzle( selector, context, results, seed ) {
 				}
 
 				try {
-
-					// `qSA` may not throw for unrecognized parts using forgiving parsing:
-					// https://drafts.csswg.org/selectors/#forgiving-selector
-					// like the `:has()` pseudo-class:
-					// https://drafts.csswg.org/selectors/#relational
-					// `CSS.supports` is still expected to return `false` then:
-					// https://drafts.csswg.org/css-conditional-4/#typedef-supports-selector-fn
-					// https://drafts.csswg.org/css-conditional-4/#dfn-support-selector
-					if ( support.cssSupportsSelector &&
-
-						// eslint-disable-next-line no-undef
-						!CSS.supports( "selector(:is(" + newSelector + "))" ) ) {
-
-						// Support: IE 11+
-						// Throw to get to the same code path as an error directly in qSA.
-						// Note: once we only support browser supporting
-						// `CSS.supports('selector(...)')`, we can most likely drop
-						// the `try-catch`. IE doesn't implement the API.
-						throw new Error();
-					}
-
 					push.apply( results,
 						newContext.querySelectorAll( newSelector )
 					);
@@ -1195,29 +1174,22 @@ setDocument = Sizzle.setDocument = function( node ) {
 			!el.querySelectorAll( ":scope fieldset div" ).length;
 	} );
 
-	// Support: Chrome 105+, Firefox 104+, Safari 15.4+
-	// Make sure forgiving mode is not used in `CSS.supports( "selector(...)" )`.
-	//
-	// `:is()` uses a forgiving selector list as an argument and is widely
-	// implemented, so it's a good one to test against.
-	support.cssSupportsSelector = assert( function() {
-		/* eslint-disable no-undef */
-
-		return CSS.supports( "selector(*)" ) &&
-
-			// Support: Firefox 78-81 only
-			// In old Firefox, `:is()` didn't use forgiving parsing. In that case,
-			// fail this test as there's no selector to test against that.
-			// `CSS.supports` uses unforgiving parsing
-			document.querySelectorAll( ":is(:jqfake)" ) &&
-
-			// `*` is needed as Safari & newer Chrome implemented something in between
-			// for `:has()` - it throws in `qSA` if it only contains an unsupported
-			// argument but multiple ones, one of which is supported, are fine.
-			// We want to play safe in case `:is()` gets the same treatment.
-			!CSS.supports( "selector(:is(*,:jqfake))" );
-
-		/* eslint-enable */
+	// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
+	// Make sure the the `:has()` argument is parsed unforgivingly.
+	// We include `*` in the test to detect buggy implementations that are
+	// _selectively_ forgiving (specifically when the list includes at least
+	// one valid selector).
+	// Note that we treat complete lack of support for `:has()` as if it were
+	// spec-compliant support, which is fine because use of `:has()` in such
+	// environments will fail in the qSA path and fall back to jQuery traversal
+	// anyway.
+	support.cssHas = assert( function() {
+		try {
+			document.querySelector( ":has(*,:jqfake)" );
+			return false;
+		} catch ( e ) {
+			return true;
+		}
 	} );
 
 	/* Attributes
@@ -1486,14 +1458,14 @@ setDocument = Sizzle.setDocument = function( node ) {
 		} );
 	}
 
-	if ( !support.cssSupportsSelector ) {
+	if ( !support.cssHas ) {
 
-		// Support: Chrome 105+, Safari 15.4+
-		// `:has()` uses a forgiving selector list as an argument so our regular
-		// `try-catch` mechanism fails to catch `:has()` with arguments not supported
-		// natively like `:has(:contains("Foo"))`. Where supported & spec-compliant,
-		// we now use `CSS.supports("selector(:is(SELECTOR_TO_BE_TESTED))")`, but
-		// outside that we mark `:has` as buggy.
+		// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
+		// Our regular `try-catch` mechanism fails to detect natively-unsupported
+		// pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
+		// in browsers that parse the `:has()` argument as a forgiving selector list.
+		// https://drafts.csswg.org/selectors/#relational now requires the argument
+		// to be parsed unforgivingly, but browsers have not yet fully adjusted.
 		rbuggyQSA.push( ":has" );
 	}
 
@@ -2406,7 +2378,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
 		matched = false;
 
 		// Combinators
-		if ( ( match = rcombinators.exec( soFar ) ) ) {
+		if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
 			matched = match.shift();
 			tokens.push( {
 				value: matched,
diff --git a/src/static/templates/admin/organizations.hbs b/src/static/templates/admin/organizations.hbs
index 9dd86622..7ac2b6ba 100644
--- a/src/static/templates/admin/organizations.hbs
+++ b/src/static/templates/admin/organizations.hbs
@@ -7,7 +7,7 @@
                     <tr>
                         <th class="vw-org-details">Organization</th>
                         <th class="vw-users">Users</th>
-                        <th class="vw-ciphers">Ciphers</th>
+                        <th class="vw-entries">Entries</th>
                         <th class="vw-attachments">Attachments</th>
                         <th class="vw-misc">Misc</th>
                         <th class="vw-actions">Actions</th>
@@ -59,7 +59,7 @@
 </main>
 
 <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
-<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
+<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script>
 <script src="{{urlpath}}/vw_static/datatables.js"></script>
 <script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
 <script src="{{urlpath}}/vw_static/jdenticon.js"></script>
diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs
index bdb2870f..637dfb22 100644
--- a/src/static/templates/admin/users.hbs
+++ b/src/static/templates/admin/users.hbs
@@ -8,7 +8,7 @@
                         <th class="vw-account-details">User</th>
                         <th class="vw-created-at">Created at</th>
                         <th class="vw-last-active">Last Active</th>
-                        <th class="vw-ciphers">Ciphers</th>
+                        <th class="vw-entries">Entries</th>
                         <th class="vw-attachments">Attachments</th>
                         <th class="vw-organizations">Organizations</th>
                         <th class="vw-actions">Actions</th>
@@ -140,7 +140,7 @@
 </main>
 
 <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
-<script src="{{urlpath}}/vw_static/jquery-3.6.3.slim.js"></script>
+<script src="{{urlpath}}/vw_static/jquery-3.6.4.slim.js"></script>
 <script src="{{urlpath}}/vw_static/datatables.js"></script>
 <script src="{{urlpath}}/vw_static/admin_users.js"></script>
 <script src="{{urlpath}}/vw_static/jdenticon.js"></script>