core: add user settable token durations (#7410)
* core: add support for user settable token duration * web: add support for user settable token duration * website: add documentation for user settable token duration * core : fix locales * web: fix tokenIntent when updating * core: fix linting * website: Update website/docs/user-group-role/user/user_ref.md Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Signed-off-by: Jean-Michel DILLY <48059109+jmdilly@users.noreply.github.com> * make token duration system-wide configurable Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * small fixup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate token configs to tenants Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * add release notes Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * make website Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint-fix Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * fix migrations Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * nosec Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint-fix Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * fix migrations for real this time Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * trying with no model using default_token_key Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint-fix Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * fix save Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint-fix Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * use signal instead of overriding save Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * fix tests Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> --------- Signed-off-by: Jean-Michel DILLY <48059109+jmdilly@users.noreply.github.com> Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:

committed by
GitHub

parent
40c672f246
commit
a70363bd95
@ -20,9 +20,18 @@ from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
|||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.users import UserSerializer
|
from authentik.core.api.users import UserSerializer
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents
|
from authentik.core.models import (
|
||||||
|
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
||||||
|
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME,
|
||||||
|
Token,
|
||||||
|
TokenIntents,
|
||||||
|
User,
|
||||||
|
default_token_duration,
|
||||||
|
token_expires_from_timedelta,
|
||||||
|
)
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.events.utils import model_to_dict
|
from authentik.events.utils import model_to_dict
|
||||||
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.rbac.decorators import permission_required
|
from authentik.rbac.decorators import permission_required
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +58,30 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
|
|||||||
attrs.setdefault("intent", TokenIntents.INTENT_API)
|
attrs.setdefault("intent", TokenIntents.INTENT_API)
|
||||||
if attrs.get("intent") not in [TokenIntents.INTENT_API, TokenIntents.INTENT_APP_PASSWORD]:
|
if attrs.get("intent") not in [TokenIntents.INTENT_API, TokenIntents.INTENT_APP_PASSWORD]:
|
||||||
raise ValidationError({"intent": f"Invalid intent {attrs.get('intent')}"})
|
raise ValidationError({"intent": f"Invalid intent {attrs.get('intent')}"})
|
||||||
|
|
||||||
|
if attrs.get("intent") == TokenIntents.INTENT_APP_PASSWORD:
|
||||||
|
# user IS in attrs
|
||||||
|
user: User = attrs.get("user")
|
||||||
|
max_token_lifetime = user.group_attributes(request).get(
|
||||||
|
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME,
|
||||||
|
)
|
||||||
|
max_token_lifetime_dt = default_token_duration()
|
||||||
|
if max_token_lifetime is not None:
|
||||||
|
try:
|
||||||
|
max_token_lifetime_dt = timedelta_from_string(max_token_lifetime)
|
||||||
|
except ValueError:
|
||||||
|
max_token_lifetime_dt = default_token_duration()
|
||||||
|
|
||||||
|
if "expires" in attrs and attrs.get("expires") > token_expires_from_timedelta(
|
||||||
|
max_token_lifetime_dt
|
||||||
|
):
|
||||||
|
raise ValidationError(
|
||||||
|
{"expires": f"Token expires exceeds maximum lifetime ({max_token_lifetime})."}
|
||||||
|
)
|
||||||
|
elif attrs.get("intent") == TokenIntents.INTENT_API:
|
||||||
|
# For API tokens, expires cannot be overridden
|
||||||
|
attrs["expires"] = default_token_duration()
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
import authentik.core.models
|
import authentik.core.models
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
|
|
||||||
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
@ -16,6 +17,10 @@ def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
|||||||
token.save()
|
token.save()
|
||||||
|
|
||||||
|
|
||||||
|
def default_token_key():
|
||||||
|
return generate_id(60)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
replaces = [
|
replaces = [
|
||||||
("authentik_core", "0012_auto_20201003_1737"),
|
("authentik_core", "0012_auto_20201003_1737"),
|
||||||
@ -62,7 +67,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="token",
|
model_name="token",
|
||||||
name="key",
|
name="key",
|
||||||
field=models.TextField(default=authentik.core.models.default_token_key),
|
field=models.TextField(default=default_token_key),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name="token",
|
name="token",
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import authentik.core.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0033_alter_user_options"),
|
||||||
|
("authentik_tenants", "0002_tenant_default_token_duration_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="authenticatedsession",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="token",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="token",
|
||||||
|
name="key",
|
||||||
|
field=models.TextField(default=authentik.core.models.default_token_key),
|
||||||
|
),
|
||||||
|
]
|
@ -1,6 +1,6 @@
|
|||||||
"""authentik core models"""
|
"""authentik core models"""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from typing import Any, Optional, Self
|
from typing import Any, Optional, Self
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -25,15 +25,16 @@ from authentik.blueprints.models import ManagedModel
|
|||||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||||
from authentik.lib.avatars import get_avatar
|
from authentik.lib.avatars import get_avatar
|
||||||
from authentik.lib.config import CONFIG
|
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.lib.models import (
|
from authentik.lib.models import (
|
||||||
CreatedUpdatedModel,
|
CreatedUpdatedModel,
|
||||||
DomainlessFormattedURLValidator,
|
DomainlessFormattedURLValidator,
|
||||||
SerializerModel,
|
SerializerModel,
|
||||||
)
|
)
|
||||||
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.policies.models import PolicyBindingModel
|
from authentik.policies.models import PolicyBindingModel
|
||||||
from authentik.tenants.utils import get_unique_identifier
|
from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGTH
|
||||||
|
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||||
@ -42,13 +43,13 @@ USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
|||||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
||||||
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
|
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
|
||||||
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
||||||
|
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME = "goauthentik.io/user/token-maximum-lifetime" # nosec
|
||||||
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
||||||
USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
|
USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
|
||||||
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
|
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
|
||||||
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
|
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
|
||||||
USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts"
|
USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts"
|
||||||
|
|
||||||
|
|
||||||
options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
|
options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
|
||||||
# used_by API that allows models to specify if they shadow an object
|
# used_by API that allows models to specify if they shadow an object
|
||||||
# for example the proxy provider which is built on top of an oauth provider
|
# for example the proxy provider which is built on top of an oauth provider
|
||||||
@ -59,16 +60,33 @@ options.DEFAULT_NAMES = options.DEFAULT_NAMES + (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_token_duration():
|
def default_token_duration() -> datetime:
|
||||||
"""Default duration a Token is valid"""
|
"""Default duration a Token is valid"""
|
||||||
return now() + timedelta(minutes=30)
|
current_tenant = get_current_tenant()
|
||||||
|
token_duration = (
|
||||||
|
current_tenant.default_token_duration
|
||||||
|
if hasattr(current_tenant, "default_token_duration")
|
||||||
|
else DEFAULT_TOKEN_DURATION
|
||||||
|
)
|
||||||
|
return now() + timedelta_from_string(token_duration)
|
||||||
|
|
||||||
|
|
||||||
def default_token_key():
|
def token_expires_from_timedelta(dt: timedelta) -> datetime:
|
||||||
|
"""Return a `datetime.datetime` object with the duration of the Token"""
|
||||||
|
return now() + dt
|
||||||
|
|
||||||
|
|
||||||
|
def default_token_key() -> str:
|
||||||
"""Default token key"""
|
"""Default token key"""
|
||||||
|
current_tenant = get_current_tenant()
|
||||||
|
token_length = (
|
||||||
|
current_tenant.default_token_length
|
||||||
|
if hasattr(current_tenant, "default_token_length")
|
||||||
|
else DEFAULT_TOKEN_LENGTH
|
||||||
|
)
|
||||||
# We use generate_id since the chars in the key should be easy
|
# We use generate_id since the chars in the key should be easy
|
||||||
# to use in Emails (for verification) and URLs (for recovery)
|
# to use in Emails (for verification) and URLs (for recovery)
|
||||||
return generate_id(CONFIG.get_int("default_token_length"))
|
return generate_id(token_length)
|
||||||
|
|
||||||
|
|
||||||
class UserTypes(models.TextChoices):
|
class UserTypes(models.TextChoices):
|
||||||
@ -627,7 +645,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
|||||||
class ExpiringModel(models.Model):
|
class ExpiringModel(models.Model):
|
||||||
"""Base Model which can expire, and is automatically cleaned up."""
|
"""Base Model which can expire, and is automatically cleaned up."""
|
||||||
|
|
||||||
expires = models.DateTimeField(default=default_token_duration)
|
expires = models.DateTimeField(default=None, null=True)
|
||||||
expiring = models.BooleanField(default=True)
|
expiring = models.BooleanField(default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -10,7 +10,14 @@ from django.dispatch import receiver
|
|||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider, User
|
from authentik.core.models import (
|
||||||
|
Application,
|
||||||
|
AuthenticatedSession,
|
||||||
|
BackchannelProvider,
|
||||||
|
ExpiringModel,
|
||||||
|
User,
|
||||||
|
default_token_duration,
|
||||||
|
)
|
||||||
|
|
||||||
# Arguments: user: User, password: str
|
# Arguments: user: User, password: str
|
||||||
password_changed = Signal()
|
password_changed = Signal()
|
||||||
@ -61,3 +68,12 @@ def backchannel_provider_pre_save(sender: type[Model], instance: Model, **_):
|
|||||||
if not isinstance(instance, BackchannelProvider):
|
if not isinstance(instance, BackchannelProvider):
|
||||||
return
|
return
|
||||||
instance.is_backchannel = True
|
instance.is_backchannel = True
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save)
|
||||||
|
def expiring_model_pre_save(sender: type[Model], instance: Model, **_):
|
||||||
|
"""Ensure expires is set on ExpiringModels that are set to expire"""
|
||||||
|
if not issubclass(sender, ExpiringModel):
|
||||||
|
return
|
||||||
|
if instance.expiring and instance.expires is None:
|
||||||
|
instance.expires = default_token_duration()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Test token API"""
|
"""Test token API"""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
@ -7,7 +8,13 @@ from guardian.shortcuts import get_anonymous_user
|
|||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.api.tokens import TokenSerializer
|
from authentik.core.api.tokens import TokenSerializer
|
||||||
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User
|
from authentik.core.models import (
|
||||||
|
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
||||||
|
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME,
|
||||||
|
Token,
|
||||||
|
TokenIntents,
|
||||||
|
User,
|
||||||
|
)
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
@ -76,6 +83,77 @@ class TestTokenAPI(APITestCase):
|
|||||||
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||||
self.assertEqual(token.expiring, False)
|
self.assertEqual(token.expiring, False)
|
||||||
|
|
||||||
|
def test_token_create_expiring(self):
|
||||||
|
"""Test token creation endpoint"""
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = True
|
||||||
|
self.user.save()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:token-list"), {"identifier": "test-token"}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
token = Token.objects.get(identifier="test-token")
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||||
|
self.assertEqual(token.expiring, True)
|
||||||
|
|
||||||
|
def test_token_create_expiring_custom_ok(self):
|
||||||
|
"""Test token creation endpoint"""
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = True
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME] = "hours=2"
|
||||||
|
self.user.save()
|
||||||
|
expires = datetime.now() + timedelta(hours=1)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:token-list"),
|
||||||
|
{
|
||||||
|
"identifier": "test-token",
|
||||||
|
"expires": expires,
|
||||||
|
"intent": TokenIntents.INTENT_APP_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
token = Token.objects.get(identifier="test-token")
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
self.assertEqual(token.intent, TokenIntents.INTENT_APP_PASSWORD)
|
||||||
|
self.assertEqual(token.expiring, True)
|
||||||
|
self.assertEqual(token.expires.timestamp(), expires.timestamp())
|
||||||
|
|
||||||
|
def test_token_create_expiring_custom_nok(self):
|
||||||
|
"""Test token creation endpoint"""
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = True
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME] = "hours=2"
|
||||||
|
self.user.save()
|
||||||
|
expires = datetime.now() + timedelta(hours=3)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:token-list"),
|
||||||
|
{
|
||||||
|
"identifier": "test-token",
|
||||||
|
"expires": expires,
|
||||||
|
"intent": TokenIntents.INTENT_APP_PASSWORD,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
def test_token_create_expiring_custom_api(self):
|
||||||
|
"""Test token creation endpoint"""
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_EXPIRING] = True
|
||||||
|
self.user.attributes[USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME] = "hours=2"
|
||||||
|
self.user.save()
|
||||||
|
expires = datetime.now() + timedelta(seconds=3)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:token-list"),
|
||||||
|
{
|
||||||
|
"identifier": "test-token",
|
||||||
|
"expires": expires,
|
||||||
|
"intent": TokenIntents.INTENT_API,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
token = Token.objects.get(identifier="test-token")
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||||
|
self.assertEqual(token.expiring, True)
|
||||||
|
self.assertNotEqual(token.expires.timestamp(), expires.timestamp())
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
"""Test Token List (Test normal authentication)"""
|
"""Test Token List (Test normal authentication)"""
|
||||||
Token.objects.all().delete()
|
Token.objects.all().delete()
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_providers_rac", "0001_squashed_0003_alter_connectiontoken_options_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="connectiontoken",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
21
authentik/events/migrations/0006_alter_systemtask_expires.py
Normal file
21
authentik/events/migrations/0006_alter_systemtask_expires.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"authentik_events",
|
||||||
|
"0004_systemtask_squashed_0005_remove_systemtask_finish_timestamp_and_more",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="systemtask",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -110,7 +110,6 @@ events:
|
|||||||
asn: "/geoip/GeoLite2-ASN.mmdb"
|
asn: "/geoip/GeoLite2-ASN.mmdb"
|
||||||
|
|
||||||
cert_discovery_dir: /certs
|
cert_discovery_dir: /certs
|
||||||
default_token_length: 60
|
|
||||||
|
|
||||||
tenants:
|
tenants:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -8,6 +8,7 @@ from django.http import HttpRequest
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from authentik.core.models import default_token_duration
|
||||||
from authentik.events.signals import get_login_event
|
from authentik.events.signals import get_login_event
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
@ -87,7 +88,9 @@ class IDToken:
|
|||||||
) -> "IDToken":
|
) -> "IDToken":
|
||||||
"""Create ID Token"""
|
"""Create ID Token"""
|
||||||
id_token = IDToken(provider, token, **kwargs)
|
id_token = IDToken(provider, token, **kwargs)
|
||||||
id_token.exp = int(token.expires.timestamp())
|
id_token.exp = int(
|
||||||
|
(token.expires if token.expires is not None else default_token_duration()).timestamp()
|
||||||
|
)
|
||||||
id_token.iss = provider.get_issuer(request)
|
id_token.iss = provider.get_issuer(request)
|
||||||
id_token.aud = provider.client_id
|
id_token.aud = provider.client_id
|
||||||
id_token.claims = {}
|
id_token.claims = {}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"authentik_providers_oauth2",
|
||||||
|
"0017_accesstoken_session_id_authorizationcode_session_id_and_more",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="accesstoken",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="authorizationcode",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="devicetoken",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="refreshtoken",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_stages_consent", "0005_alter_consentstage_mode"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userconsent",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-29 10:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_stages_invitation", "0007_invitation_flow"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="invitation",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -23,6 +23,8 @@ class SettingsSerializer(ModelSerializer):
|
|||||||
"footer_links",
|
"footer_links",
|
||||||
"gdpr_compliance",
|
"gdpr_compliance",
|
||||||
"impersonation",
|
"impersonation",
|
||||||
|
"default_token_duration",
|
||||||
|
"default_token_length",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.0.2 on 2024-02-20 08:26
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import authentik.lib.utils.time
|
||||||
|
from authentik.lib.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_tenants", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tenant",
|
||||||
|
name="default_token_duration",
|
||||||
|
field=models.TextField(
|
||||||
|
default=CONFIG.get("default_token_duration", "minutes=30"),
|
||||||
|
help_text="Default token duration",
|
||||||
|
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tenant",
|
||||||
|
name="default_token_length",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
default=CONFIG.get_int("default_token_length", 60),
|
||||||
|
help_text="Default token length",
|
||||||
|
validators=[django.core.validators.MinValueValidator(1)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -5,6 +5,7 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
@ -22,6 +23,9 @@ LOGGER = get_logger()
|
|||||||
|
|
||||||
VALID_SCHEMA_NAME = re.compile(r"^t_[a-z0-9]{1,61}$")
|
VALID_SCHEMA_NAME = re.compile(r"^t_[a-z0-9]{1,61}$")
|
||||||
|
|
||||||
|
DEFAULT_TOKEN_DURATION = "minutes=30" # nosec
|
||||||
|
DEFAULT_TOKEN_LENGTH = 60
|
||||||
|
|
||||||
|
|
||||||
def _validate_schema_name(name):
|
def _validate_schema_name(name):
|
||||||
if not VALID_SCHEMA_NAME.match(name):
|
if not VALID_SCHEMA_NAME.match(name):
|
||||||
@ -81,6 +85,16 @@ class Tenant(TenantMixin, SerializerModel):
|
|||||||
impersonation = models.BooleanField(
|
impersonation = models.BooleanField(
|
||||||
help_text=_("Globally enable/disable impersonation."), default=True
|
help_text=_("Globally enable/disable impersonation."), default=True
|
||||||
)
|
)
|
||||||
|
default_token_duration = models.TextField(
|
||||||
|
help_text=_("Default token duration"),
|
||||||
|
default=DEFAULT_TOKEN_DURATION,
|
||||||
|
validators=[timedelta_string_validator],
|
||||||
|
)
|
||||||
|
default_token_length = models.PositiveIntegerField(
|
||||||
|
help_text=_("Default token length"),
|
||||||
|
default=DEFAULT_TOKEN_LENGTH,
|
||||||
|
validators=[MinValueValidator(1)],
|
||||||
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.schema_name == "template":
|
if self.schema_name == "template":
|
||||||
|
@ -6025,7 +6025,10 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"expires": {
|
"expires": {
|
||||||
"type": "string",
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"title": "Expires"
|
"title": "Expires"
|
||||||
},
|
},
|
||||||
@ -6794,7 +6797,10 @@
|
|||||||
"title": "Name"
|
"title": "Name"
|
||||||
},
|
},
|
||||||
"expires": {
|
"expires": {
|
||||||
"type": "string",
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"title": "Expires"
|
"title": "Expires"
|
||||||
},
|
},
|
||||||
@ -7985,7 +7991,10 @@
|
|||||||
"title": "Description"
|
"title": "Description"
|
||||||
},
|
},
|
||||||
"expires": {
|
"expires": {
|
||||||
"type": "string",
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"title": "Expires"
|
"title": "Expires"
|
||||||
},
|
},
|
||||||
|
36
schema.yml
36
schema.yml
@ -29976,6 +29976,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- asn
|
- asn
|
||||||
- current
|
- current
|
||||||
@ -32674,6 +32675,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
scope:
|
scope:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -33774,6 +33776,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
fixed_data:
|
fixed_data:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
@ -33810,6 +33813,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
fixed_data:
|
fixed_data:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
@ -38121,6 +38125,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
fixed_data:
|
fixed_data:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
@ -39421,6 +39426,15 @@ components:
|
|||||||
impersonation:
|
impersonation:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Globally enable/disable impersonation.
|
description: Globally enable/disable impersonation.
|
||||||
|
default_token_duration:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
description: Default token duration
|
||||||
|
default_token_length:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 1
|
||||||
|
description: Default token length
|
||||||
PatchedSourceStageRequest:
|
PatchedSourceStageRequest:
|
||||||
type: object
|
type: object
|
||||||
description: SourceStage Serializer
|
description: SourceStage Serializer
|
||||||
@ -39498,6 +39512,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
expiring:
|
expiring:
|
||||||
type: boolean
|
type: boolean
|
||||||
PatchedUserDeleteStageRequest:
|
PatchedUserDeleteStageRequest:
|
||||||
@ -42450,6 +42465,14 @@ components:
|
|||||||
impersonation:
|
impersonation:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Globally enable/disable impersonation.
|
description: Globally enable/disable impersonation.
|
||||||
|
default_token_duration:
|
||||||
|
type: string
|
||||||
|
description: Default token duration
|
||||||
|
default_token_length:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 1
|
||||||
|
description: Default token length
|
||||||
SettingsRequest:
|
SettingsRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Settings Serializer
|
description: Settings Serializer
|
||||||
@ -42481,6 +42504,15 @@ components:
|
|||||||
impersonation:
|
impersonation:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Globally enable/disable impersonation.
|
description: Globally enable/disable impersonation.
|
||||||
|
default_token_duration:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
description: Default token duration
|
||||||
|
default_token_length:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 1
|
||||||
|
description: Default token length
|
||||||
SeverityEnum:
|
SeverityEnum:
|
||||||
enum:
|
enum:
|
||||||
- notice
|
- notice
|
||||||
@ -43148,6 +43180,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
expiring:
|
expiring:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
@ -43173,6 +43206,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
scope:
|
scope:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -43217,6 +43251,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
expiring:
|
expiring:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
@ -43455,6 +43490,7 @@ components:
|
|||||||
expires:
|
expires:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
nullable: true
|
||||||
expiring:
|
expiring:
|
||||||
type: boolean
|
type: boolean
|
||||||
user:
|
user:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
import "@goauthentik/components/ak-number-input";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/components/ak-text-input";
|
import "@goauthentik/components/ak-text-input";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
@ -192,6 +193,24 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
|
|||||||
help=${msg("Globally enable/disable impersonation.")}
|
help=${msg("Globally enable/disable impersonation.")}
|
||||||
>
|
>
|
||||||
</ak-switch-input>
|
</ak-switch-input>
|
||||||
|
<ak-text-input
|
||||||
|
name="defaultTokenDuration"
|
||||||
|
label=${msg("Default token duration")}
|
||||||
|
required
|
||||||
|
value="${ifDefined(this._settings?.defaultTokenDuration)}"
|
||||||
|
.bighelp=${html`<p class="pf-c-form__helper-text">
|
||||||
|
${msg("Default duration for generated tokens")}
|
||||||
|
</p>
|
||||||
|
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
|
||||||
|
>
|
||||||
|
</ak-text-input>
|
||||||
|
<ak-number-input
|
||||||
|
label=${msg("Default token length")}
|
||||||
|
required
|
||||||
|
name="defaultTokenLength"
|
||||||
|
value="${first(this._settings?.defaultTokenLength, 60)}"
|
||||||
|
help=${msg("Default length of generated tokens")}
|
||||||
|
></ak-number-input>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ export class UserTokenForm extends ModelForm<Token, string> {
|
|||||||
|
|
||||||
async send(data: Token): Promise<Token> {
|
async send(data: Token): Promise<Token> {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
|
data.intent = this.instance.intent;
|
||||||
return new CoreApi(DEFAULT_CONFIG).coreTokensUpdate({
|
return new CoreApi(DEFAULT_CONFIG).coreTokensUpdate({
|
||||||
identifier: this.instance.identifier,
|
identifier: this.instance.identifier,
|
||||||
tokenRequest: data,
|
tokenRequest: data,
|
||||||
@ -41,6 +42,14 @@ export class UserTokenForm extends ModelForm<Token, string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
|
const now = new Date();
|
||||||
|
const expiringDate = this.instance?.expires
|
||||||
|
? new Date(
|
||||||
|
this.instance.expires.getTime() -
|
||||||
|
this.instance.expires.getTimezoneOffset() * 60000,
|
||||||
|
)
|
||||||
|
: new Date(now.getTime() + 30 * 60000 - now.getTimezoneOffset() * 60000);
|
||||||
|
|
||||||
return html` <ak-form-element-horizontal
|
return html` <ak-form-element-horizontal
|
||||||
label=${msg("Identifier")}
|
label=${msg("Identifier")}
|
||||||
?required=${true}
|
?required=${true}
|
||||||
@ -59,6 +68,16 @@ export class UserTokenForm extends ModelForm<Token, string> {
|
|||||||
value="${ifDefined(this.instance?.description)}"
|
value="${ifDefined(this.instance?.description)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>`;
|
</ak-form-element-horizontal>
|
||||||
|
${this.intent == IntentEnum.AppPassword
|
||||||
|
? html`<ak-form-element-horizontal label=${msg("Expiring")} name="expires">
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
value="${expiringDate.toISOString().slice(0, -8)}"
|
||||||
|
min="${now.toISOString().slice(0, -8)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
</ak-form-element-horizontal>`
|
||||||
|
: html``}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,11 @@ export class UserTokenList extends Table<Token> {
|
|||||||
<ak-forms-modal>
|
<ak-forms-modal>
|
||||||
<span slot="submit"> ${msg("Update")} </span>
|
<span slot="submit"> ${msg("Update")} </span>
|
||||||
<span slot="header"> ${msg("Update Token")} </span>
|
<span slot="header"> ${msg("Update Token")} </span>
|
||||||
<ak-user-token-form slot="form" .instancePk=${item.identifier}>
|
<ak-user-token-form
|
||||||
|
intent=${item.intent ?? IntentEnum.Api}
|
||||||
|
slot="form"
|
||||||
|
.instancePk=${item.identifier}
|
||||||
|
>
|
||||||
</ak-user-token-form>
|
</ak-user-token-form>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||||
|
@ -272,14 +272,6 @@ Disable the inbuilt update-checker. Defaults to `false`.
|
|||||||
- Kubeconfig
|
- Kubeconfig
|
||||||
- Existence of a docker socket
|
- Existence of a docker socket
|
||||||
|
|
||||||
### `AUTHENTIK_DEFAULT_TOKEN_LENGTH`
|
|
||||||
|
|
||||||
:::info
|
|
||||||
Requires authentik 2022.4.1
|
|
||||||
:::
|
|
||||||
|
|
||||||
Configure the length of generated tokens. Defaults to 60.
|
|
||||||
|
|
||||||
### `AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS`
|
### `AUTHENTIK_LDAP__TASK_TIMEOUT_HOURS`
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
60
website/docs/releases/2024/next.md
Normal file
60
website/docs/releases/2024/next.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Release next
|
||||||
|
slug: /releases/next
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- ## Breaking changes -->
|
||||||
|
|
||||||
|
## Breaking changes
|
||||||
|
|
||||||
|
### Manual action is required
|
||||||
|
|
||||||
|
### Manual action may be required
|
||||||
|
|
||||||
|
- **Configuration options migrated to the Admin interface**
|
||||||
|
|
||||||
|
The following config options have been moved from the config file and can now be set using the Admin interface (under **System** -> **Settings**) or the API:
|
||||||
|
|
||||||
|
- `AUTHENTIK_DEFAULT_TOKEN_LENGTH`
|
||||||
|
|
||||||
|
When upgrading to 2024.next, the currently configured options will be automatically migrated to the database, and can be removed from the `.env` or helm values file afterwards.
|
||||||
|
|
||||||
|
## New features
|
||||||
|
|
||||||
|
- Configurable app password token expiring
|
||||||
|
|
||||||
|
Thanks @jmdilly for contributing this feature!
|
||||||
|
|
||||||
|
Admins can now configure the default token duration (which defaults to `minutes=30`) in the admin interface as specified above. This value can also be overridden per-user with the `goauthentik.io/user/token-maximum-lifetime` attribute.
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
This release does not introduce any new requirements.
|
||||||
|
|
||||||
|
### docker-compose
|
||||||
|
|
||||||
|
To upgrade, download the new docker-compose file and update the Docker stack with the new version, using these commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
wget -O docker-compose.yml https://goauthentik.io/version/xxxx.x/docker-compose.yml
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-O` flag retains the downloaded file's name, overwriting any existing local file with the same name.
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
|
||||||
|
Upgrade the Helm Chart to the new version, using the following commands:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm repo update
|
||||||
|
helm upgrade authentik authentik/authentik -f values.yaml --version ^xxxx.x
|
||||||
|
```
|
||||||
|
|
||||||
|
## Minor changes/fixes
|
||||||
|
|
||||||
|
<!-- _Insert the output of `make gen-changelog` here_ -->
|
||||||
|
|
||||||
|
## API Changes
|
||||||
|
|
||||||
|
<!-- _Insert output of `make gen-diff` here_ -->
|
@ -70,6 +70,14 @@ Optional flag, when set to false, Tokens created by the user will not expire.
|
|||||||
|
|
||||||
Only applies when the token creation is triggered by the user with this attribute set. Additionally, the flag does not apply to superusers.
|
Only applies when the token creation is triggered by the user with this attribute set. Additionally, the flag does not apply to superusers.
|
||||||
|
|
||||||
|
### `goauthentik.io/user/token-maximum-lifetime`:
|
||||||
|
|
||||||
|
Optional flag, when set, defines the maximum lifetime of user-created tokens. Defaults to the system setting if not set.
|
||||||
|
|
||||||
|
Only applies when `goauthentik.io/user/token-expires` set to true.
|
||||||
|
|
||||||
|
Format is string of format `days=10;hours=1;minute=3;seconds=5`.
|
||||||
|
|
||||||
### `goauthentik.io/user/debug`:
|
### `goauthentik.io/user/debug`:
|
||||||
|
|
||||||
See [Troubleshooting access problems](../../troubleshooting/access), when set, the user gets a more detailed explanation of access decisions.
|
See [Troubleshooting access problems](../../troubleshooting/access), when set, the user gets a more detailed explanation of access decisions.
|
||||||
|
Reference in New Issue
Block a user