Skip to content

Commit b0671e2

Browse files
melizecherisssonBeryJu
authored
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 <[email protected]> Signed-off-by: Marcelo Elizeche Landó <[email protected]> * 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 <[email protected]> --------- Signed-off-by: Marcelo Elizeche Landó <[email protected]> Signed-off-by: Jens Langhammer <[email protected]> Co-authored-by: Marc 'risson' Schmitt <[email protected]> Co-authored-by: Jens Langhammer <[email protected]>
1 parent f185a41 commit b0671e2

File tree

7 files changed

+81
-26
lines changed

7 files changed

+81
-26
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Generated by Django 5.0.12 on 2025-02-27 04:32
2+
3+
import authentik.lib.utils.time
4+
from authentik.lib.utils.time import timedelta_from_string
5+
from django.db import migrations, models
6+
7+
8+
def convert_integer_to_string_format(apps, schema_editor):
9+
db_alias = schema_editor.connection.alias
10+
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
11+
for stage in EmailStage.objects.using(db_alias).all():
12+
stage.token_expiry = f"minutes={stage.token_expiry}"
13+
stage.save(using=db_alias)
14+
15+
16+
def convert_string_to_integer_format(apps, schema_editor):
17+
db_alias = schema_editor.connection.alias
18+
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
19+
for stage in EmailStage.objects.using(db_alias).all():
20+
# Check if token_expiry is a string
21+
if isinstance(stage.token_expiry, str):
22+
try:
23+
# Use the timedelta_from_string utility to convert to timedelta
24+
# then convert to minutes by dividing seconds by 60
25+
td = timedelta_from_string(stage.token_expiry)
26+
minutes_value = int(td.total_seconds() / 60)
27+
stage.token_expiry = minutes_value
28+
stage.save(using=db_alias)
29+
except (ValueError, TypeError):
30+
# If the string can't be parsed or converted properly, skip
31+
pass
32+
33+
34+
class Migration(migrations.Migration):
35+
36+
dependencies = [
37+
("authentik_stages_email", "0004_emailstage_activate_user_on_success"),
38+
]
39+
40+
operations = [
41+
migrations.AlterField(
42+
model_name="emailstage",
43+
name="token_expiry",
44+
field=models.TextField(
45+
default="minutes=30",
46+
help_text="Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).",
47+
validators=[authentik.lib.utils.time.timedelta_string_validator],
48+
),
49+
),
50+
migrations.RunPython(
51+
convert_integer_to_string_format,
52+
convert_string_to_integer_format,
53+
),
54+
]

authentik/stages/email/models.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from authentik.flows.models import Stage
1616
from authentik.lib.config import CONFIG
17+
from authentik.lib.utils.time import timedelta_string_validator
1718

1819
LOGGER = get_logger()
1920

@@ -74,8 +75,10 @@ class EmailStage(Stage):
7475
default=False, help_text=_("Activate users upon completion of stage.")
7576
)
7677

77-
token_expiry = models.IntegerField(
78-
default=30, help_text=_("Time in minutes the token sent is valid.")
78+
token_expiry = models.TextField(
79+
default="minutes=30",
80+
validators=[timedelta_string_validator],
81+
help_text=_("Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."),
7982
)
8083
subject = models.TextField(default="authentik")
8184
template = models.TextField(default=EmailTemplates.PASSWORD_RESET)

authentik/stages/email/stage.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from authentik.flows.stage import ChallengeStageView
2323
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
2424
from authentik.lib.utils.errors import exception_to_string
25+
from authentik.lib.utils.time import timedelta_from_string
2526
from authentik.stages.email.models import EmailStage
2627
from authentik.stages.email.tasks import send_mails
2728
from authentik.stages.email.utils import TemplateEmailMessage
@@ -73,8 +74,8 @@ def get_token(self) -> FlowToken:
7374
"""Get token"""
7475
pending_user = self.get_pending_user()
7576
current_stage: EmailStage = self.executor.current_stage
76-
valid_delta = timedelta(
77-
minutes=current_stage.token_expiry + 1
77+
valid_delta = timedelta_from_string(current_stage.token_expiry) + timedelta(
78+
minutes=1
7879
) # + 1 because django timesince always rounds down
7980
identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}")
8081
# Don't check for validity here, we only care if the token exists

blueprints/example/flows-recovery-email-verification.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ entries:
5757
use_ssl: false
5858
timeout: 10
5959
from_address: [email protected]
60-
token_expiry: 30
60+
token_expiry: minutes=30
6161
subject: authentik
6262
template: email/password_reset.html
6363
activate_user_on_success: true

blueprints/schema.json

+3-4
Original file line numberDiff line numberDiff line change
@@ -11369,11 +11369,10 @@
1136911369
"title": "From address"
1137011370
},
1137111371
"token_expiry": {
11372-
"type": "integer",
11373-
"minimum": -2147483648,
11374-
"maximum": 2147483647,
11372+
"type": "string",
11373+
"minLength": 1,
1137511374
"title": "Token expiry",
11376-
"description": "Time in minutes the token sent is valid."
11375+
"description": "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
1137711376
},
1137811377
"subject": {
1137911378
"type": "string",

schema.yml

+9-13
Original file line numberDiff line numberDiff line change
@@ -35146,7 +35146,7 @@ paths:
3514635146
- in: query
3514735147
name: token_expiry
3514835148
schema:
35149-
type: integer
35149+
type: string
3515035150
- in: query
3515135151
name: use_global_settings
3515235152
schema:
@@ -42774,10 +42774,8 @@ components:
4277442774
format: email
4277542775
maxLength: 254
4277642776
token_expiry:
42777-
type: integer
42778-
maximum: 2147483647
42779-
minimum: -2147483648
42780-
description: Time in minutes the token sent is valid.
42777+
type: string
42778+
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
4278142779
subject:
4278242780
type: string
4278342781
template:
@@ -42833,10 +42831,9 @@ components:
4283342831
minLength: 1
4283442832
maxLength: 254
4283542833
token_expiry:
42836-
type: integer
42837-
maximum: 2147483647
42838-
minimum: -2147483648
42839-
description: Time in minutes the token sent is valid.
42834+
type: string
42835+
minLength: 1
42836+
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
4284042837
subject:
4284142838
type: string
4284242839
minLength: 1
@@ -50389,10 +50386,9 @@ components:
5038950386
minLength: 1
5039050387
maxLength: 254
5039150388
token_expiry:
50392-
type: integer
50393-
maximum: 2147483647
50394-
minimum: -2147483648
50395-
description: Time in minutes the token sent is valid.
50389+
type: string
50390+
minLength: 1
50391+
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
5039650392
subject:
5039750393
type: string
5039850394
minLength: 1

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
33
import { first } from "@goauthentik/common/utils";
44
import "@goauthentik/elements/forms/FormGroup";
55
import "@goauthentik/elements/forms/HorizontalFormElement";
6+
import "@goauthentik/elements/utils/TimeDeltaHelp";
67

78
import { msg } from "@lit/localize";
89
import { TemplateResult, html } from "lit";
@@ -202,19 +203,20 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
202203
</p>
203204
</ak-form-element-horizontal>
204205
<ak-form-element-horizontal
205-
label=${msg("Token expiry")}
206+
label=${msg("Token expiration")}
206207
?required=${true}
207208
name="tokenExpiry"
208209
>
209210
<input
210-
type="number"
211-
value="${first(this.instance?.tokenExpiry, 30)}"
211+
type="text"
212+
value="${first(this.instance?.tokenExpiry, "minutes=30")}"
212213
class="pf-c-form-control"
213214
required
214215
/>
215216
<p class="pf-c-form__helper-text">
216-
${msg("Time in minutes the token sent is valid.")}
217+
${msg("Time the token sent is valid.")}
217218
</p>
219+
<ak-utils-time-delta-help></ak-utils-time-delta-help>
218220
</ak-form-element-horizontal>
219221
<ak-form-element-horizontal
220222
label=${msg("Subject")}

0 commit comments

Comments
 (0)