diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py
index ea66dc5a605..1b7def3a3ed 100644
--- a/netbox/netbox/api/authentication.py
+++ b/netbox/netbox/api/authentication.py
@@ -1,7 +1,11 @@
+import logging
+
from django.conf import settings
+from django.utils import timezone
from rest_framework import authentication, exceptions
from rest_framework.permissions import BasePermission, DjangoObjectPermissions, SAFE_METHODS
+from netbox.config import get_config
from users.models import Token
from utilities.request import get_client_ip
@@ -40,6 +44,17 @@ def authenticate_credentials(self, key):
except model.DoesNotExist:
raise exceptions.AuthenticationFailed("Invalid token")
+ # Update last used, but only once a minute. This reduces the write load on the db
+ if not token.last_used or (timezone.now() - token.last_used).total_seconds() > 60:
+ # If maintenance mode is enabled, assume the database is read-only, and disable updating the token's
+ # last_used time upon authentication.
+ if get_config().MAINTENANCE_MODE:
+ logger = logging.getLogger('netbox.auth.login')
+ logger.warning("Maintenance mode enabled: disabling update of token's last used timestamp")
+ else:
+ token.last_used = timezone.now()
+ token.save()
+
# Enforce the Token's expiration time, if one has been set.
if token.is_expired:
raise exceptions.AuthenticationFailed("Token expired")
diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html
index 360e65a6798..24b32cc9bd8 100644
--- a/netbox/templates/users/api_tokens.html
+++ b/netbox/templates/users/api_tokens.html
@@ -34,6 +34,14 @@
Never
{% endif %}
+
+ Last Used
+ {% if token.last_used %}
+ {{ token.last_used|annotated_date }}
+ {% else %}
+ Never
+ {% endif %}
+
Create/Edit/Delete Operations
{% if token.write_enabled %}
diff --git a/netbox/users/admin/__init__.py b/netbox/users/admin/__init__.py
index ede26cd1bd2..2db822cfefc 100644
--- a/netbox/users/admin/__init__.py
+++ b/netbox/users/admin/__init__.py
@@ -58,7 +58,7 @@ def get_inlines(self, request, obj):
class TokenAdmin(admin.ModelAdmin):
form = forms.TokenAdminForm
list_display = [
- 'key', 'user', 'created', 'expires', 'write_enabled', 'description', 'list_allowed_ips'
+ 'key', 'user', 'created', 'expires', 'last_used', 'write_enabled', 'description', 'list_allowed_ips'
]
def list_allowed_ips(self, obj):
diff --git a/netbox/users/migrations/0003_token_last_used.py b/netbox/users/migrations/0003_token_last_used.py
new file mode 100644
index 00000000000..cc014e59c28
--- /dev/null
+++ b/netbox/users/migrations/0003_token_last_used.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0.4 on 2022-06-16 15:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0002_standardize_id_fields'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='token',
+ name='last_used',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/netbox/users/models.py b/netbox/users/models.py
index 704516c7164..4ee4dce6b4b 100644
--- a/netbox/users/models.py
+++ b/netbox/users/models.py
@@ -204,6 +204,10 @@ class Token(models.Model):
blank=True,
null=True
)
+ last_used = models.DateTimeField(
+ blank=True,
+ null=True
+ )
key = models.CharField(
max_length=40,
unique=True,