fix(authentik): deliver tripit email-verify stages via blueprint (provider token_expiry too old)
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
Pipeline 214 failed: the pinned goauthentik 2024.x provider models EmailStage.token_expiry as an integer, but the live 2026.2.x server requires a duration string ('hours=24') and 400s any number (even the provider default 30). Bumping the provider is a global terragrunt.hcl change re-applying every platform stack + breaking 3 other authentik-using stacks' lockfiles — disproportionate. Instead the two email-verification stages + their flow bindings move into an Authentik blueprint (tripit-email-stages.yaml) applied server-side via authentik_blueprint; the server parses token_expiry natively. Validated on the live server + terraform validate. Restores the ADR-0020 email-verification security gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
89eb090be3
commit
e4512f3566
3 changed files with 94 additions and 28 deletions
22
stacks/authentik/tripit-email-blueprint.tf
Normal file
22
stacks/authentik/tripit-email-blueprint.tf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Delivers the TripIt enrollment/recovery email-verification stages + their flow
|
||||
# bindings (tripit-email-stages.yaml) as a server-applied Authentik blueprint.
|
||||
#
|
||||
# Why a blueprint and not authentik_stage_email resources: the globally-pinned
|
||||
# provider (goauthentik 2024.x in terragrunt.hcl) models EmailStage.token_expiry
|
||||
# as an integer, but the live server (2026.2.x) requires a duration string and
|
||||
# 400s any number. The blueprint is parsed by the server, which accepts the
|
||||
# string. Bumping the provider would mean a global terragrunt.hcl change that
|
||||
# re-applies every platform stack — disproportionate. See tripit-flows.tf.
|
||||
#
|
||||
# depends_on the flows so they exist before Authentik resolves the blueprint's
|
||||
# !Find [..., slug, tripit-enrollment|tripit-recovery] references.
|
||||
resource "authentik_blueprint" "tripit_email_stages" {
|
||||
name = "tripit-email-stages"
|
||||
content = file("${path.module}/tripit-email-stages.yaml")
|
||||
enabled = true
|
||||
|
||||
depends_on = [
|
||||
authentik_flow.tripit_enrollment,
|
||||
authentik_flow.tripit_recovery,
|
||||
]
|
||||
}
|
||||
58
stacks/authentik/tripit-email-stages.yaml
Normal file
58
stacks/authentik/tripit-email-stages.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# TripIt enrollment + recovery email-verification stages (tripit ADR-0020).
|
||||
#
|
||||
# Delivered as an Authentik blueprint (applied server-side by Authentik) instead
|
||||
# of via the Terraform provider, because the globally-pinned provider
|
||||
# (goauthentik 2024.x) models EmailStage.token_expiry as an integer while the
|
||||
# live server (2026.2.x) requires a duration string ("hours=24") and rejects any
|
||||
# number. See tripit-flows.tf for the rest of the flow and tripit-email-blueprint.tf
|
||||
# for the delivery resource.
|
||||
#
|
||||
# These two stages + their flow bindings are the SECURITY BOUNDARY of ADR-0020:
|
||||
# enrollment creates the user inactive; only clicking the link from
|
||||
# tripit-enrollment-verify (activate_user_on_success) makes the account usable.
|
||||
# Without this, anyone could self-enroll under an address they don't control and
|
||||
# tripit (which trusts X-authentik-email) would treat them as that identity.
|
||||
version: 1
|
||||
metadata:
|
||||
name: tripit-email-stages
|
||||
entries:
|
||||
# Enrollment: verify the email and ACTIVATE the (initially inactive) user.
|
||||
- model: authentik_stages_email.emailstage
|
||||
state: present
|
||||
identifiers:
|
||||
name: tripit-enrollment-verify
|
||||
attrs:
|
||||
use_global_settings: true # noreply@viktorbarzin.me via mail.viktorbarzin.me
|
||||
activate_user_on_success: true
|
||||
subject: Confirm your TripIt account
|
||||
template: email/account_confirmation.html
|
||||
token_expiry: hours=24
|
||||
# Recovery: prove inbox ownership before letting the user register a new passkey.
|
||||
- model: authentik_stages_email.emailstage
|
||||
state: present
|
||||
identifiers:
|
||||
name: tripit-recovery-email
|
||||
attrs:
|
||||
use_global_settings: true
|
||||
activate_user_on_success: false
|
||||
subject: Recover your TripIt access
|
||||
template: email/account_confirmation.html
|
||||
token_expiry: hours=1
|
||||
# Bind enrollment-verify into tripit-enrollment at order 30
|
||||
# (prompt 10 -> write 20 -> VERIFY 30 -> passkey 40 -> login 50).
|
||||
- model: authentik_flows.flowstagebinding
|
||||
state: present
|
||||
identifiers:
|
||||
target: !Find [authentik_flows.flow, [slug, tripit-enrollment]]
|
||||
stage: !Find [authentik_stages_email.emailstage, [name, tripit-enrollment-verify]]
|
||||
attrs:
|
||||
order: 30
|
||||
# Bind recovery-email into tripit-recovery at order 20
|
||||
# (identify 10 -> EMAIL 20 -> new passkey 30 -> login 40).
|
||||
- model: authentik_flows.flowstagebinding
|
||||
state: present
|
||||
identifiers:
|
||||
target: !Find [authentik_flows.flow, [slug, tripit-recovery]]
|
||||
stage: !Find [authentik_stages_email.emailstage, [name, tripit-recovery-email]]
|
||||
attrs:
|
||||
order: 20
|
||||
|
|
@ -87,17 +87,17 @@ resource "authentik_stage_user_write" "tripit_enroll_write" {
|
|||
user_creation_mode = "always_create"
|
||||
}
|
||||
|
||||
resource "authentik_stage_email" "tripit_enroll_verify" {
|
||||
name = "tripit-enrollment-verify"
|
||||
# Use AUTHENTIK_EMAIL__* (noreply@viktorbarzin.me via mail.viktorbarzin.me).
|
||||
use_global_settings = true
|
||||
# THE security gate: a user becomes active (and thus loginable / trusted by
|
||||
# tripit's X-authentik-email) only after clicking the link sent to their inbox.
|
||||
activate_user_on_success = true
|
||||
subject = "Confirm your TripIt account"
|
||||
template = "email/account_confirmation.html"
|
||||
token_expiry = 1440 # minutes = 24h
|
||||
}
|
||||
# NOTE: the two email-verification stages (enrollment + recovery) AND their flow
|
||||
# bindings are deliberately NOT defined here — they live in an Authentik
|
||||
# BLUEPRINT (tripit-email-blueprint.tf), applied server-side. Reason: the
|
||||
# globally-pinned provider (goauthentik 2024.x, terragrunt.hcl) models
|
||||
# EmailStage.token_expiry as an INTEGER, but the live server (2026.2.x) requires
|
||||
# a duration STRING ("hours=24") and 400s any number — the provider cannot send
|
||||
# a valid value (confirmed: even the unset default `30` is rejected). The
|
||||
# blueprint is parsed by the server, which accepts the string. Bumping the
|
||||
# provider would be a global terragrunt.hcl change that re-applies every platform
|
||||
# stack and breaks 3 other authentik-using app stacks' lockfiles — out of all
|
||||
# proportion to two stages. See tripit ADR-0020.
|
||||
|
||||
resource "authentik_flow" "tripit_enrollment" {
|
||||
name = "Sign up for TripIt"
|
||||
|
|
@ -118,11 +118,7 @@ resource "authentik_flow_stage_binding" "tripit_enroll_20_write" {
|
|||
stage = authentik_stage_user_write.tripit_enroll_write.id
|
||||
order = 20
|
||||
}
|
||||
resource "authentik_flow_stage_binding" "tripit_enroll_30_verify" {
|
||||
target = authentik_flow.tripit_enrollment.uuid
|
||||
stage = authentik_stage_email.tripit_enroll_verify.id
|
||||
order = 30
|
||||
}
|
||||
# order 30 (email-verification binding) is in tripit-email-blueprint.tf — see note above
|
||||
resource "authentik_flow_stage_binding" "tripit_enroll_40_passkey" {
|
||||
target = authentik_flow.tripit_enrollment.uuid
|
||||
stage = authentik_stage_authenticator_webauthn.tripit_passkey.id
|
||||
|
|
@ -143,13 +139,7 @@ resource "authentik_stage_identification" "tripit_recover_ident" {
|
|||
pretend_user_exists = true
|
||||
}
|
||||
|
||||
resource "authentik_stage_email" "tripit_recover_email" {
|
||||
name = "tripit-recovery-email"
|
||||
use_global_settings = true
|
||||
subject = "Recover your TripIt access"
|
||||
template = "email/account_confirmation.html"
|
||||
token_expiry = 60 # minutes = 1h
|
||||
}
|
||||
# (recovery email-verification stage is in tripit-email-blueprint.tf — see note above)
|
||||
|
||||
resource "authentik_flow" "tripit_recovery" {
|
||||
name = "Recover TripIt access"
|
||||
|
|
@ -165,11 +155,7 @@ resource "authentik_flow_stage_binding" "tripit_recover_10_ident" {
|
|||
stage = authentik_stage_identification.tripit_recover_ident.id
|
||||
order = 10
|
||||
}
|
||||
resource "authentik_flow_stage_binding" "tripit_recover_20_email" {
|
||||
target = authentik_flow.tripit_recovery.uuid
|
||||
stage = authentik_stage_email.tripit_recover_email.id
|
||||
order = 20
|
||||
}
|
||||
# order 20 (email-verification binding) is in tripit-email-blueprint.tf — see note above
|
||||
resource "authentik_flow_stage_binding" "tripit_recover_30_passkey" {
|
||||
target = authentik_flow.tripit_recovery.uuid
|
||||
stage = authentik_stage_authenticator_webauthn.tripit_passkey.id
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue