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.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
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.permissions import IsAdminUser, IsAuthenticated
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -20,6 +26,9 @@ class DeviceSerializer(MetaNameSerializer):
|
|||||||
name = CharField()
|
name = CharField()
|
||||||
type = SerializerMethodField()
|
type = SerializerMethodField()
|
||||||
confirmed = BooleanField()
|
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:
|
def get_type(self, instance: Device) -> str:
|
||||||
"""Get type of device"""
|
"""Get type of device"""
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
"""Authenticator devices helpers"""
|
"""Authenticator devices helpers"""
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from authentik.core.models import User
|
||||||
|
|
||||||
|
|
||||||
def verify_token(user, device_id, token):
|
def verify_token(user, device_id, token):
|
||||||
"""
|
"""
|
||||||
@ -63,7 +68,7 @@ def match_token(user, token):
|
|||||||
return device
|
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.
|
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 django.utils.functional import cached_property
|
||||||
|
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
|
from authentik.lib.models import CreatedUpdatedModel
|
||||||
from authentik.stages.authenticator.util import random_number_token
|
from authentik.stages.authenticator.util import random_number_token
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ class DeviceManager(models.Manager):
|
|||||||
``Device.objects``.
|
``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
|
Returns a queryset for all devices of this class that belong to the
|
||||||
given user.
|
given user.
|
||||||
@ -37,7 +38,7 @@ class DeviceManager(models.Manager):
|
|||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|
||||||
class Device(models.Model):
|
class Device(CreatedUpdatedModel):
|
||||||
"""
|
"""
|
||||||
Abstract base model for a :term:`device` attached to a user. Plugins must
|
Abstract base model for a :term:`device` attached to a user. Plugins must
|
||||||
subclass this to define their OTP models.
|
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?")
|
confirmed = models.BooleanField(default=True, help_text="Is this device ready for use?")
|
||||||
|
|
||||||
|
last_used = models.DateTimeField(null=True)
|
||||||
|
|
||||||
objects = DeviceManager()
|
objects = DeviceManager()
|
||||||
|
|
||||||
class Meta:
|
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
|
readOnly: true
|
||||||
confirmed:
|
confirmed:
|
||||||
type: boolean
|
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:
|
required:
|
||||||
- confirmed
|
- confirmed
|
||||||
|
- created
|
||||||
|
- last_updated
|
||||||
|
- last_used
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
- pk
|
- pk
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||||
|
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
@ -44,7 +45,10 @@ export class UserDeviceTable extends Table<Device> {
|
|||||||
return [
|
return [
|
||||||
msg("Name"),
|
msg("Name"),
|
||||||
msg("Type"),
|
msg("Type"),
|
||||||
msg("Confirmed")
|
msg("Confirmed"),
|
||||||
|
msg("Created at"),
|
||||||
|
msg("Last updated at"),
|
||||||
|
msg("Last used at"),
|
||||||
].map((th) => new TableColumn(th, ""));
|
].map((th) => new TableColumn(th, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +102,14 @@ export class UserDeviceTable extends Table<Device> {
|
|||||||
html`${item.name}`,
|
html`${item.name}`,
|
||||||
html`${deviceTypeName(item)}`,
|
html`${deviceTypeName(item)}`,
|
||||||
html`${item.confirmed ? msg("Yes") : msg("No")}`,
|
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 { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { deviceTypeName } from "@goauthentik/common/labels";
|
import { deviceTypeName } from "@goauthentik/common/labels";
|
||||||
|
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/buttons/Dropdown";
|
import "@goauthentik/elements/buttons/Dropdown";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/TokenCopyButton";
|
import "@goauthentik/elements/buttons/TokenCopyButton";
|
||||||
@ -48,6 +49,8 @@ export class MFADevicesPage extends Table<Device> {
|
|||||||
return [
|
return [
|
||||||
msg("Name"),
|
msg("Name"),
|
||||||
msg("Type"),
|
msg("Type"),
|
||||||
|
msg("Created at"),
|
||||||
|
msg("Last used at"),
|
||||||
""
|
""
|
||||||
].map((th) => new TableColumn(th, ""));
|
].map((th) => new TableColumn(th, ""));
|
||||||
}
|
}
|
||||||
@ -122,6 +125,12 @@ export class MFADevicesPage extends Table<Device> {
|
|||||||
return [
|
return [
|
||||||
html`${item.name}`,
|
html`${item.name}`,
|
||||||
html`${deviceTypeName(item)}`,
|
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`
|
html`
|
||||||
<ak-forms-modal>
|
<ak-forms-modal>
|
||||||
<span slot="submit">${msg("Update")}</span>
|
<span slot="submit">${msg("Update")}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user