Skip to content

Commit

Permalink
stages/authenticator: add created, last_updated and last_used metadata (
Browse files Browse the repository at this point in the history
#10636)

* stages/authenticator: add created, last_updated and last_used metadata

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* lint

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* also show for users

Signed-off-by: Marc 'risson' Schmitt <[email protected]>

* set allow_null

Signed-off-by: Jens Langhammer <[email protected]>

---------

Signed-off-by: Marc 'risson' Schmitt <[email protected]>
Signed-off-by: Jens Langhammer <[email protected]>
Co-authored-by: Jens Langhammer <[email protected]>
  • Loading branch information
rissson and BeryJu authored Aug 7, 2024
1 parent 3401065 commit d8c3b8b
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 5 deletions.
11 changes: 10 additions & 1 deletion authentik/core/api/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"""
Expand Down
7 changes: 6 additions & 1 deletion authentik/stages/authenticator/__init__.py
Original file line number Diff line number Diff line change
@@ -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):
"""
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions authentik/stages/authenticator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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


Expand All @@ -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.
Expand All @@ -37,7 +38,7 @@ def devices_for_user(self, user, confirmed=None):
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.
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion web/src/admin/users/UserDevicesTable.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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, ""));
}

Expand Down Expand Up @@ -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`-`}`,
];
}
}
Expand Down
9 changes: 9 additions & 0 deletions web/src/user/user-settings/mfa/MFADevicesPage.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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, ""));
}
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit d8c3b8b

Please sign in to comment.