stages/authenticator: add created, last_updated and last_used metadata (#10636)
* stages/authenticator: add created, last_updated and last_used metadata Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * also show for users Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * set allow_null Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:

committed by
GitHub

parent
340106594e
commit
d8c3b8bad2
@ -2,7 +2,13 @@
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework.fields import BooleanField, CharField, IntegerField, SerializerMethodField
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
DateTimeField,
|
||||
IntegerField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.permissions import IsAdminUser, IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -20,6 +26,9 @@ class DeviceSerializer(MetaNameSerializer):
|
||||
name = CharField()
|
||||
type = SerializerMethodField()
|
||||
confirmed = BooleanField()
|
||||
created = DateTimeField(read_only=True)
|
||||
last_updated = DateTimeField(read_only=True)
|
||||
last_used = DateTimeField(read_only=True, allow_null=True)
|
||||
|
||||
def get_type(self, instance: Device) -> str:
|
||||
"""Get type of device"""
|
||||
|
@ -1,7 +1,12 @@
|
||||
"""Authenticator devices helpers"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.core.models import User
|
||||
|
||||
|
||||
def verify_token(user, device_id, token):
|
||||
"""
|
||||
@ -63,7 +68,7 @@ def match_token(user, token):
|
||||
return device
|
||||
|
||||
|
||||
def devices_for_user(user, confirmed=True, for_verify=False):
|
||||
def devices_for_user(user: "User", confirmed: bool | None = True, for_verify: bool = False):
|
||||
"""
|
||||
Return an iterable of all devices registered to the given user.
|
||||
|
||||
|
@ -9,6 +9,7 @@ from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.lib.models import CreatedUpdatedModel
|
||||
from authentik.stages.authenticator.util import random_number_token
|
||||
|
||||
|
||||
@ -18,7 +19,7 @@ class DeviceManager(models.Manager):
|
||||
``Device.objects``.
|
||||
"""
|
||||
|
||||
def devices_for_user(self, user, confirmed=None):
|
||||
def devices_for_user(self, user: User, confirmed: bool | None = None):
|
||||
"""
|
||||
Returns a queryset for all devices of this class that belong to the
|
||||
given user.
|
||||
@ -37,7 +38,7 @@ class DeviceManager(models.Manager):
|
||||
return devices
|
||||
|
||||
|
||||
class Device(models.Model):
|
||||
class Device(CreatedUpdatedModel):
|
||||
"""
|
||||
Abstract base model for a :term:`device` attached to a user. Plugins must
|
||||
subclass this to define their OTP models.
|
||||
@ -85,6 +86,8 @@ class Device(models.Model):
|
||||
|
||||
confirmed = models.BooleanField(default=True, help_text="Is this device ready for use?")
|
||||
|
||||
last_used = models.DateTimeField(null=True)
|
||||
|
||||
objects = DeviceManager()
|
||||
|
||||
class Meta:
|
||||
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-25 16:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_duo", "0005_authenticatorduostage_friendly_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="duodevice",
|
||||
name="created",
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="duodevice",
|
||||
name="last_updated",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="duodevice",
|
||||
name="last_used",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-25 16:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_sms", "0006_authenticatorsmsstage_friendly_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="smsdevice",
|
||||
name="created",
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="smsdevice",
|
||||
name="last_updated",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="smsdevice",
|
||||
name="last_used",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-25 16:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_static", "0009_throttling"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="staticdevice",
|
||||
name="created",
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="staticdevice",
|
||||
name="last_updated",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="staticdevice",
|
||||
name="last_used",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-25 16:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_totp", "0010_alter_totpdevice_key"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="totpdevice",
|
||||
name="created",
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="totpdevice",
|
||||
name="last_updated",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="totpdevice",
|
||||
name="last_used",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-25 16:28
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_webauthn", "0001_squashed_0011_webauthndevice_aaguid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="webauthndevice",
|
||||
name="created",
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(1, 1, 1, 0, 0)),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="webauthndevice",
|
||||
name="last_updated",
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="webauthndevice",
|
||||
name="last_used",
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
16
schema.yml
16
schema.yml
@ -36459,8 +36459,24 @@ components:
|
||||
readOnly: true
|
||||
confirmed:
|
||||
type: boolean
|
||||
created:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
last_updated:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
last_used:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
nullable: true
|
||||
required:
|
||||
- confirmed
|
||||
- created
|
||||
- last_updated
|
||||
- last_used
|
||||
- meta_model_name
|
||||
- name
|
||||
- pk
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
@ -44,7 +45,10 @@ export class UserDeviceTable extends Table<Device> {
|
||||
return [
|
||||
msg("Name"),
|
||||
msg("Type"),
|
||||
msg("Confirmed")
|
||||
msg("Confirmed"),
|
||||
msg("Created at"),
|
||||
msg("Last updated at"),
|
||||
msg("Last used at"),
|
||||
].map((th) => new TableColumn(th, ""));
|
||||
}
|
||||
|
||||
@ -98,6 +102,14 @@ export class UserDeviceTable extends Table<Device> {
|
||||
html`${item.name}`,
|
||||
html`${deviceTypeName(item)}`,
|
||||
html`${item.confirmed ? msg("Yes") : msg("No")}`,
|
||||
html`<div>${getRelativeTime(item.created)}</div>
|
||||
<small>${item.created.toLocaleString()}</small>`,
|
||||
html`<div>${getRelativeTime(item.lastUpdated)}</div>
|
||||
<small>${item.lastUpdated.toLocaleString()}</small>`,
|
||||
html`${item.lastUsed
|
||||
? html`<div>${getRelativeTime(item.lastUsed)}</div>
|
||||
<small>${item.lastUsed.toLocaleString()}</small>`
|
||||
: html`-`}`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/buttons/Dropdown";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/TokenCopyButton";
|
||||
@ -48,6 +49,8 @@ export class MFADevicesPage extends Table<Device> {
|
||||
return [
|
||||
msg("Name"),
|
||||
msg("Type"),
|
||||
msg("Created at"),
|
||||
msg("Last used at"),
|
||||
""
|
||||
].map((th) => new TableColumn(th, ""));
|
||||
}
|
||||
@ -122,6 +125,12 @@ export class MFADevicesPage extends Table<Device> {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${deviceTypeName(item)}`,
|
||||
html`<div>${getRelativeTime(item.created)}</div>
|
||||
<small>${item.created.toLocaleString()}</small>`,
|
||||
html`${item.lastUsed
|
||||
? html`<div>${getRelativeTime(item.lastUsed)}</div>
|
||||
<small>${item.lastUsed.toLocaleString()}</small>`
|
||||
: html`-`}`,
|
||||
html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Update")}</span>
|
||||
|
Reference in New Issue
Block a user