stages/email: token_expiry format (#13394)

* Change token_expiry type from integer to text in Email Stage to unify with timedelta_string_validator

* Add migration file for token_expiry format, change from number to text field in the UI

* Fix token_expiry new format in stage.py in Email Stage

* fix linting

* Update web/src/admin/stages/email/EmailStageForm.ts

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>

* Use db_alias and using() for the queries

* Make valid_delta more readable

* use <ak-utils-time-delta-help> in the UI

* fix missing import

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Marcelo Elizeche Landó <marce@melizeche.com>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Marcelo Elizeche Landó
2025-03-11 13:22:30 -03:00
committed by GitHub
parent f185a41813
commit b0671e26c8
7 changed files with 81 additions and 26 deletions

View File

@ -0,0 +1,54 @@
# Generated by Django 5.0.12 on 2025-02-27 04:32
import authentik.lib.utils.time
from authentik.lib.utils.time import timedelta_from_string
from django.db import migrations, models
def convert_integer_to_string_format(apps, schema_editor):
db_alias = schema_editor.connection.alias
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
for stage in EmailStage.objects.using(db_alias).all():
stage.token_expiry = f"minutes={stage.token_expiry}"
stage.save(using=db_alias)
def convert_string_to_integer_format(apps, schema_editor):
db_alias = schema_editor.connection.alias
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
for stage in EmailStage.objects.using(db_alias).all():
# Check if token_expiry is a string
if isinstance(stage.token_expiry, str):
try:
# Use the timedelta_from_string utility to convert to timedelta
# then convert to minutes by dividing seconds by 60
td = timedelta_from_string(stage.token_expiry)
minutes_value = int(td.total_seconds() / 60)
stage.token_expiry = minutes_value
stage.save(using=db_alias)
except (ValueError, TypeError):
# If the string can't be parsed or converted properly, skip
pass
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_email", "0004_emailstage_activate_user_on_success"),
]
operations = [
migrations.AlterField(
model_name="emailstage",
name="token_expiry",
field=models.TextField(
default="minutes=30",
help_text="Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
migrations.RunPython(
convert_integer_to_string_format,
convert_string_to_integer_format,
),
]

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.flows.models import Stage
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger()
@ -74,8 +75,10 @@ class EmailStage(Stage):
default=False, help_text=_("Activate users upon completion of stage.")
)
token_expiry = models.IntegerField(
default=30, help_text=_("Time in minutes the token sent is valid.")
token_expiry = models.TextField(
default="minutes=30",
validators=[timedelta_string_validator],
help_text=_("Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."),
)
subject = models.TextField(default="authentik")
template = models.TextField(default=EmailTemplates.PASSWORD_RESET)

View File

@ -22,6 +22,7 @@ from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDI
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -73,8 +74,8 @@ class EmailStageView(ChallengeStageView):
"""Get token"""
pending_user = self.get_pending_user()
current_stage: EmailStage = self.executor.current_stage
valid_delta = timedelta(
minutes=current_stage.token_expiry + 1
valid_delta = timedelta_from_string(current_stage.token_expiry) + timedelta(
minutes=1
) # + 1 because django timesince always rounds down
identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists

View File

@ -57,7 +57,7 @@ entries:
use_ssl: false
timeout: 10
from_address: system@authentik.local
token_expiry: 30
token_expiry: minutes=30
subject: authentik
template: email/password_reset.html
activate_user_on_success: true

View File

@ -11369,11 +11369,10 @@
"title": "From address"
},
"token_expiry": {
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"type": "string",
"minLength": 1,
"title": "Token expiry",
"description": "Time in minutes the token sent is valid."
"description": "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
},
"subject": {
"type": "string",

View File

@ -35146,7 +35146,7 @@ paths:
- in: query
name: token_expiry
schema:
type: integer
type: string
- in: query
name: use_global_settings
schema:
@ -42774,10 +42774,8 @@ components:
format: email
maxLength: 254
token_expiry:
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
type: string
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
subject:
type: string
template:
@ -42833,10 +42831,9 @@ components:
minLength: 1
maxLength: 254
token_expiry:
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
type: string
minLength: 1
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
subject:
type: string
minLength: 1
@ -50389,10 +50386,9 @@ components:
minLength: 1
maxLength: 254
token_expiry:
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
type: string
minLength: 1
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
subject:
type: string
minLength: 1

View File

@ -3,6 +3,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
@ -202,19 +203,20 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Token expiry")}
label=${msg("Token expiration")}
?required=${true}
name="tokenExpiry"
>
<input
type="number"
value="${first(this.instance?.tokenExpiry, 30)}"
type="text"
value="${first(this.instance?.tokenExpiry, "minutes=30")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg("Time in minutes the token sent is valid.")}
${msg("Time the token sent is valid.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Subject")}